diff --git a/README.md b/README.md index c3567ad..15a679c 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,18 @@ -# StackLend Protocol πŸš€ +# Hayy ProtocolπŸ‘‹ -**Cross-Chain Lending Protocol between Stacks and EVM Networks** +**Cross-Chain Money Market between Stacks and Sui Blockchain** -StackLend is a revolutionary decentralized finance (DeFi) protocol that enables seamless cross-chain lending by using STX as collateral on Stacks blockchain to borrow tokens on EVM-compatible networks. The protocol bridges Bitcoin's security through Stacks with the liquidity of Ethereum-based ecosystems. +Hayy Protocol is a revolutionary decentralized finance (DeFi) protocol that enables seamless cross-chain lending by using STX or sBTC as collateral on Stacks L2 blockchain to borrow tokens on Sui blockchain. The protocol bridges Bitcoin's security through Stacks with Sui's high-performance blockchain. ## 🌟 Features -- **Cross-Chain Lending**: Use STX as collateral to borrow tokens on multiple EVM networks -- **Stacks Integration**: Leverage Bitcoin's security through Stacks blockchain -- **Multi-Network Support**: Currently supports Scroll Sepolia with plans for more networks +- **Cross-Chain Lending**: Use STX or sBTC as collateral to borrow USDC on Sui blockchain +- **Stacks Integration**: Leverage Bitcoin's security through Stacks L2 blockchain +- **Sui Integration**: High-performance blockchain with low transaction fees for borrowing - **Real-Time Relayer**: Automated cross-chain transaction processing - **User-Friendly Interface**: Modern React frontend with wallet integration - **Secure Smart Contracts**: Audited and battle-tested contract architecture +- **EVM Support**: Coming soon - will enable borrowing on EVM-compatible networks ## πŸ—οΈ Architecture @@ -20,21 +21,21 @@ The protocol consists of four main components: ```mermaid graph TB A[Frontend dApp] --> B[Stacks Contracts] - A --> C[EVM Contracts] + A --> C[Sui Contracts] B --> D[Cross-Chain Relayer] D --> C - B --> E[STX Collateral] - C --> F[Token Borrowing] + B --> E[STX/sBTC Collateral] + C --> F[USDC Borrowing] D --> G[Event Processing] ``` ### Components -1. **Frontend (`stacklend-fe`)**: React-based user interface with Stacks and EVM wallet integration -2. **Stacks Contracts (`stacklend-stacks`)**: Clarity smart contracts for collateral management -3. **EVM Contracts (`stacklend-evm`)**: Solidity contracts for token borrowing and lending -4. **Relayer (`stacklend-relayer`)**: Node.js service for cross-chain event processing +1. **Frontend (`hayyprotocol-fe`)**: React-based user interface with Stacks and Sui wallet integration +2. **Stacks Contracts (`hayyprotocol-stacks`)**: Clarity smart contracts for collateral management +3. **Sui Contracts (`hayyprotocol-sui`)**: Move smart contracts for token borrowing and lending +4. **Backend/Relayer (`hayyprotocol-backend`)**: Node.js service for cross-chain event processing ## πŸ“‹ Contract Addresses @@ -42,23 +43,21 @@ graph TB | Contract | Address | |----------|---------| -| **Collateral V1** | `STBGS8Y6KHWQ3D2P9BTQ83VBD3ZCK7BDTWMGJY5Z.collateral-v1` | -| **Lending V1** | `STBGS8Y6KHWQ3D2P9BTQ83VBD3ZCK7BDTWMGJY5Z.lending-v1` | +| **Money Market Core** | `STBGS8Y6KHWQ3D2P9BTQ83VBD3ZCK7BDTWMGJY5Z.collateral-v1` | -### Scroll Sepolia (Testnet) +### Sui Testnet | Contract | Address | Explorer | |----------|---------|----------| -| **BorrowController** | `0xD2b0838ff0818E9aa185a712576Cb3EE0885deda` | [View on Scrollscan](https://sepolia.scrollscan.com/address/0xD2b0838ff0818E9aa185a712576Cb3EE0885deda) | -| **MockUSDC** | `0x953E5610c73C989fE7C75D3D67bE0A1e44a8e797` | [View on Scrollscan](https://sepolia.scrollscan.com/address/0x953E5610c73C989fE7C75D3D67bE0A1e44a8e797) | -| **MockUSDT** | `0x13cF4E3e284d34C575CeeCCb0791Ca535A657da2` | [View on Scrollscan](https://sepolia.scrollscan.com/address/0x13cF4E3e284d34C575CeeCCb0791Ca535A657da2) | -| **MockWBTC** | `0xf12cd252CA50781EC88c2d8832cA4f9c4bF11D82` | [View on Scrollscan](https://sepolia.scrollscan.com/address/0xf12cd252CA50781EC88c2d8832cA4f9c4bF11D82) | +| **USDC Lending Pool** | `Coming Soon` | [View on Sui Explorer](https://testnet.suivision.xyz/package/0xf13ad32a2d462fd5afebf3a844a437b7b97046ebb1d181782b2f11216213ecca) | +| **sBTC Lending Pool** | `Coming Soon` | [View on Sui Explorer](https://testnet.suivision.xyz/object/) | +| **Borrow Controller** | `Coming Soon` | [View on Sui Explorer](https://testnet.suivision.xyz/object/) | ## πŸ”— Explorer Links - **Stacks Testnet Collateral-V1**: [Stacks Explorer](https://explorer.hiro.so/txid/STBGS8Y6KHWQ3D2P9BTQ83VBD3ZCK7BDTWMGJY5Z.collateral-v1?chain=testnet) - **Stacks Testnet Lending-V1**: [Stacks Explorer](https://explorer.hiro.so/txid/STBGS8Y6KHWQ3D2P9BTQ83VBD3ZCK7BDTWMGJY5Z.lending-v1?chain=testnet) -- **Scroll Sepolia**: [Scrollscan Testnet](https://sepolia.scrollscan.com/) +- **Sui Testnet**: [Sui Explorer](https://explorer.sui.io/) ## πŸš€ Quick Start @@ -67,67 +66,71 @@ graph TB - Node.js 18+ and npm/pnpm - Git - Stacks wallet (Hiro Wallet, Leather, etc.) -- MetaMask or compatible EVM wallet +- Sui wallet (Sui Wallet, Suiet, etc.) ### Installation 1. **Clone the repository** ```bash - git clone https://github.com/xfajarr/stacklend.git - cd stacklend + git clone https://github.com/xfajarr/hayy-protocol.git + cd hayy-protocol ``` 2. **Install dependencies for each component** ```bash # Frontend - cd stacklend-fe + cd hayyprotocol-fe npm install - # Relayer - cd ../stacklend-relayer + # Backend/Relayer + cd ../hayyprotocol-backend npm install # Stacks contracts (optional, for development) - cd ../stacklend-stacks + cd ../hayyprotocol-stacks + npm install + + # Sui contracts (optional, for development) + cd ../hayyprotocol-sui npm install ``` 3. **Configure environment variables** ```bash - # In stacklend-relayer/ + # In hayyprotocol-backend/ cp .env.example .env # Edit .env with your RPC URLs and private keys ``` 4. **Start the development servers** ```bash - # Terminal 1: Start relayer - cd stacklend-relayer + # Terminal 1: Start backend/relayer + cd hayyprotocol-backend npm start # Terminal 2: Start frontend - cd stacklend-fe + cd hayyprotocol-fe npm run dev ``` 5. **Access the application** - Frontend: http://localhost:5173 - - Relayer API: http://localhost:3000 + - Backend API: http://localhost:3000 ## πŸ’‘ How It Works -1. **Deposit Collateral**: Users deposit STX tokens as collateral on Stacks blockchain -2. **Request Borrow**: Users specify the token and amount they want to borrow on EVM networks +1. **Deposit Collateral**: Users deposit STX or sBTC tokens as collateral on Stacks blockchain +2. **Request Borrow**: Users specify the amount of USDC they want to borrow on Sui blockchain 3. **Cross-Chain Processing**: The relayer monitors Stacks events and processes requests -4. **Token Minting**: EVM contracts mint/transfer requested tokens to user's EVM address -5. **Repayment**: Users repay borrowed tokens on EVM to unlock their STX collateral +4. **Token Minting**: Sui contracts mint/transfer requested USDC to user's Sui address +5. **Repayment**: Users repay borrowed USDC on Sui to unlock their STX/sBTC collateral ## πŸ› οΈ Development ### Frontend Development ```bash -cd stacklend-fe +cd hayyprotocol-fe npm run dev # Start development server npm run build # Build for production npm run lint # Run linting @@ -137,24 +140,24 @@ npm run lint # Run linting **Stacks Contracts:** ```bash -cd stacklend-stacks +cd hayyprotocol-stacks clarinet check # Check contract syntax clarinet test # Run tests clarinet deploy # Deploy to testnet ``` -**EVM Contracts:** +**Sui Contracts:** ```bash -cd stacklend-evm -forge build # Compile contracts -forge test # Run tests -forge script script/Deploy.s.sol --rpc-url $RPC_URL --broadcast # Deploy +cd hayyprotocol-sui +sui move build # Build contracts +sui client test # Run tests +sui client publish # Deploy to testnet ``` -### Relayer Development +### Backend/Relayer Development ```bash -cd stacklend-relayer +cd hayyprotocol-backend npm run dev # Start with hot reload npm test # Run tests npm run docker # Build Docker image @@ -166,47 +169,24 @@ npm run docker # Build Docker image 1. **Get Testnet Tokens** - STX: [Stacks Testnet Faucet](https://explorer.hiro.so/sandbox/faucet?chain=testnet) - - Scroll Sepolia ETH: [Scroll Faucet](https://sepolia.scroll.io/bridge) + - SUI: [Sui Testnet Faucet](https://faucet.sui.io/) 2. **Connect Wallets** - Configure Stacks wallet for testnet - - Add Scroll Sepolia network to MetaMask - - Network Details: - - Name: Scroll Sepolia - - RPC URL: https://sepolia-rpc.scroll.io/ - - Chain ID: 534351 - - Currency: ETH - - Explorer: https://sepolia.scrollscan.com/ + - Configure Sui wallet for testnet 3. **Test Flow** - - Deposit STX collateral - - Request token borrow - - Verify token receipt on EVM + - Deposit STX or sBTC collateral on Stacks + - Request USDC borrow on Sui + - Verify USDC receipt on Sui - Test repayment flow ## πŸ“š Documentation -- [Frontend Integration Guide](./stacklend-fe/STACKS_INTEGRATION_README.md) -- [Stacks Contract Documentation](./stacklend-stacks/STACKS_INTEGRATION.md) -- [EVM Deployment Guide](./stacklend-evm/DEPLOYMENT.md) -- [Relayer Setup Guide](./stacklend-relayer/README.md) - -## 🀝 Contributing - -We welcome contributions! Please read our contributing guidelines and submit pull requests for any improvements. - -1. Fork the repository -2. Create a feature branch -3. Make your changes -4. Add tests if applicable -5. Submit a pull request - -## πŸ”’ Security - -- All smart contracts have been tested extensively -- Cross-chain transactions are validated by the relayer -- Multi-signature support for critical operations -- Regular security audits and updates +- [Frontend Integration Guide](./hayyprotocol-fe/README.md) +- [Stacks Contract Documentation](./hayyprotocol-stacks/README.md) +- [Sui Contract Documentation](./hayyprotocol-sui/README.md) +- [Backend Setup Guide](./hayyprotocol-backend/README.md) ## πŸ“„ License diff --git a/hayyprotocol-fe/.env.example b/hayyprotocol-fe/.env.example deleted file mode 100644 index f3de6a1..0000000 --- a/hayyprotocol-fe/.env.example +++ /dev/null @@ -1,14 +0,0 @@ -# Sui Network -VITE_ORIGINAL_PACKAGE_ID=0x5d0758d8d0b31570c5d9d1a7d3d58db20fb1514b01b53f6d3ebfde87a5f60278 -VITE_FAUCET_POOL_ID=0x708ab1553eeb1c2ce475f098a71648ccc2fc06ec3ccdd7e5d5d2acaff566aa3f -VITE_USDC_LENDING_POOL_ID=0x4d8839f0dc2e8de2200312a90ac7d2d8fbb4a9e593a7f2896340405bb666a30d -VITE_BORROW_REGISTRY_ID=0x42aa67fc1c179459922ba6a8e55d55da4bdc1aa354eea165d7447943f7f15ac8 - -VITE_FAUCET_POOL_MODULE_NAME=faucet_pool -VITE_USDC_LENDING_POOL_MODULE_NAME=usdc_lending_pool -VITE_BORROW_CONTROLLER_MODULE_NAME=borrow_controller - -# Stacks Network -VITE_STACKS_NETWORK=testnet -VITE_STACKS_CONTRACT_ADDRESS=ST1WVZNYKMK1MS2V2B728ZWJ2C76TAN49C6HSY7JF -VITE_STACKS_COLLATERAL_CONTRACT_NAME=collateral-v2 diff --git a/hayyprotocol-stacks/Clarinet.toml b/hayyprotocol-stacks/Clarinet.toml index 6101a67..8c962fd 100644 --- a/hayyprotocol-stacks/Clarinet.toml +++ b/hayyprotocol-stacks/Clarinet.toml @@ -1,13 +1,29 @@ [project] -name = "stacklend" +name = "hayyprotocol" description = "" authors = [] telemetry = true cache_dir = "./.cache" +<<<<<<< HEAD +[contracts.mock-sbtc-v1] +path = "contracts/mock-sbtc-v1.clar" +clarity_version = 3 +epoch = "3.0" + +[contracts.mock-oracle-v1] +path = "contracts/mock-oracle-v1.clar" +clarity_version = 3 +epoch = "3.0" + +[contracts.money-market-core] +path = "contracts/money-market-core.clar" +clarity_version = 3 +======= [contracts.collateral-v1] path = "contracts/collateral-v1.clar" clarity_version = 2 +>>>>>>> 5a9a85724588c9f8cf2e1b0c0a39972081b8d1cd epoch = "3.0" [repl.analysis] diff --git a/hayyprotocol-stacks/DEPLOY.md b/hayyprotocol-stacks/DEPLOY.md deleted file mode 100644 index c929703..0000000 --- a/hayyprotocol-stacks/DEPLOY.md +++ /dev/null @@ -1,202 +0,0 @@ -# Deploy Stacks Contract to Testnet - -## Prerequisites - -1. **Testnet STX Balance** (untuk gas fees) - - Minimal ~1 STX untuk deployment - - Faucet: https://explorer.hiro.so/sandbox/faucet?chain=testnet - - Atau: https://stacks-testnet-faucet.vercel.app/ - -2. **Stacks Wallet** (Hiro/Leather/Xverse) - - Install dari: https://wallet.hiro.so/ - ---- - -## Step 1: Setup Testnet Config - -Edit file `settings/Testnet.toml`: - -```toml -[network] -name = "testnet" -node_rpc_address = "https://api.testnet.hiro.so" -deployment_fee_rate = 10 - -[accounts.deployer] -# Option 1: Pake Mnemonic (24 words) -mnemonic = "your 24 word mnemonic phrase here ..." - -# Option 2: Pake Secret Key (lebih aman) -# secret_key = "your_secret_key_here" -``` - -### Cara dapat Private Key/Mnemonic: - -**Hiro Wallet:** -1. Buka Hiro Wallet -2. Klik Settings β†’ View Secret Key -3. Copy Mnemonic (24 words) atau Secret Key - -**Leather Wallet:** -1. Buka Leather Wallet -2. Settings β†’ Security β†’ Reveal Secret Key -3. Copy Mnemonic atau Secret Key - -⚠️ **JANGAN SHARE PRIVATE KEY/MNEMONIC KE SIAPAPUN!** - ---- - -## Step 2: Generate Deployment Plan - -```bash -clarinet deployments generate --testnet -``` - -Ini akan create file `deployments/default.testnet-plan.yaml` - ---- - -## Step 3: Check Deployment Plan - -```bash -clarinet deployments check --testnet -``` - -Ini akan validate deployment plan. - ---- - -## Step 4: Deploy ke Testnet - -```bash -clarinet deployments apply --testnet -``` - -atau dengan manual cost: - -```bash -clarinet deployments apply --testnet --manual-cost -``` - ---- - -## Step 5: Verify Deployment - -Cek di Stacks Explorer: -``` -https://explorer.hiro.so/txid/YOUR_TX_ID?chain=testnet -``` - -Atau cek contract address: -``` -https://explorer.hiro.so/address/YOUR_ADDRESS?chain=testnet -``` - ---- - -## Alternative: Deploy via Stacks CLI - -### Install Stacks CLI (if needed) -```bash -npm install -g @stacks/cli -``` - -### Deploy Contract -```bash -stx deploy_contract \ - ./contracts/collateral-v1.clar \ - collateral-v1 \ - 1000 \ - 0 \ - --testnet \ - --privateKey YOUR_PRIVATE_KEY -``` - ---- - -## After Deployment - -1. **Save Contract Address** - - Contract will be at: `YOUR_ADDRESS.collateral-v1` - - Example: `ST1ABC...XYZ.collateral-v1` - -2. **Initialize Admin** - ```bash - stx call YOUR_ADDRESS.collateral-v1 init-admin \ - --testnet \ - --privateKey YOUR_PRIVATE_KEY - ``` - -3. **Update Frontend Config** - - Edit `stacklend-fe/src/lib/config.ts`: - ```typescript - testnet: { - COLLATERAL: { - address: 'YOUR_DEPLOYED_ADDRESS', - name: 'collateral-v1' - } - } - ``` - ---- - -## Troubleshooting - -### Error: "Insufficient balance" -- Request STX from faucet -- Wait beberapa menit untuk konfirmasi - -### Error: "Contract already exists" -- Ganti nama contract di `Clarinet.toml` -- Atau deploy dengan address baru - -### Error: "Invalid private key" -- Check format private key (64 hex characters) -- Pastikan tidak ada spasi atau newline - ---- - -## Cost Estimates - -- **Contract Deployment:** ~0.1 - 0.5 STX -- **init-admin call:** ~0.001 STX -- **Total estimated:** ~0.5 - 1 STX - ---- - -## Security Checklist - -- [ ] βœ… `settings/Testnet.toml` is in `.gitignore` -- [ ] βœ… Never commit private key to git -- [ ] βœ… Use separate wallet for testnet -- [ ] βœ… Keep mainnet keys separate -- [ ] βœ… Test on devnet first if unsure - ---- - -## Quick Commands Reference - -```bash -# 1. Generate deployment -clarinet deployments generate --testnet - -# 2. Check deployment -clarinet deployments check --testnet - -# 3. Deploy -clarinet deployments apply --testnet - -# 4. Check contract on explorer -# https://explorer.hiro.so/address/YOUR_ADDRESS?chain=testnet -``` - ---- - -## Next Steps After Deployment - -1. βœ… Test deposit-collateral on testnet -2. βœ… Test request-withdraw on testnet -3. βœ… Update frontend with deployed address -4. βœ… Build relayer service -5. βœ… Test full cross-chain flow diff --git a/hayyprotocol-stacks/QUICK-TEST.md b/hayyprotocol-stacks/QUICK-TEST.md deleted file mode 100644 index 50861ee..0000000 --- a/hayyprotocol-stacks/QUICK-TEST.md +++ /dev/null @@ -1,81 +0,0 @@ -# Quick Test - Copy & Paste Ready - -## Open Console -```bash -clarinet console -``` - ---- - -## Copy-Paste Test Sequence - -### 1️⃣ Initialize Admin -```clarity -(contract-call? .collateral-v1 init-admin) -(contract-call? .collateral-v1 is-admin tx-sender) -``` - -### 2️⃣ Deposit 10 STX -```clarity -(contract-call? .collateral-v1 get-collateral tx-sender) -(contract-call? .collateral-v1 deposit-collateral u10000000) -(contract-call? .collateral-v1 get-collateral tx-sender) -``` - -### 3️⃣ Request Withdraw 5 STX -```clarity -(contract-call? .collateral-v1 request-withdraw u5000000) -``` - -### 4️⃣ Switch to Wallet 1 -``` -::set_tx_sender ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5 -``` - -### 5️⃣ Deposit from Wallet 1 -```clarity -(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.collateral-v1 deposit-collateral u10000000) -(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.collateral-v1 get-total-collateral) -``` - -### 6️⃣ Switch Back to Admin -``` -::set_tx_sender ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM -``` - -### 7️⃣ Admin Unlock 5 STX for Wallet 1 -```clarity -(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.collateral-v1 admin-unlock-collateral 'ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5 u5000000) -(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.collateral-v1 get-collateral 'ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5) -``` - -### 8️⃣ Check Portfolio -```clarity -(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.collateral-v1 get-portfolio 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM) -(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.collateral-v1 get-portfolio 'ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5) -(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.collateral-v1 get-total-collateral) -``` - ---- - -## βœ… Done! - -All core functions tested. Check HOW-TO-TEST.md for detailed explanations. - ---- - -## ⚠️ Important Note - -**When switching tx-sender:** You must use the **fully qualified contract name** with the deployer's address: - -βœ… **Correct:** -```clarity -(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.collateral-v1 deposit-collateral u10000000) -``` - -❌ **Wrong (will fail when tx-sender β‰  deployer):** -```clarity -(contract-call? .collateral-v1 deposit-collateral u10000000) -``` - -The shorthand `.collateral-v1` only works when you're calling from the deployer's context. diff --git a/hayyprotocol-stacks/TEST-RESULTS.md b/hayyprotocol-stacks/TEST-RESULTS.md deleted file mode 100644 index 2df0853..0000000 --- a/hayyprotocol-stacks/TEST-RESULTS.md +++ /dev/null @@ -1,373 +0,0 @@ -# STX Collateral Contract - Test Results βœ… - -**Date:** October 15, 2025 -**Contract:** `collateral-v1.clar` -**Status:** βœ… **ALL TESTS PASSED** - ---- - -## Test Summary - -| Test Category | Status | Details | -|--------------|--------|---------| -| **Admin Functions** | βœ… PASS | Admin init, unlock, permissions | -| **Deposit Functions** | βœ… PASS | Single & multi-user deposits | -| **Withdrawal Requests** | βœ… PASS | Event emission verified | -| **Balance Tracking** | βœ… PASS | User & total balances correct | -| **Events** | βœ… PASS | All events emit correctly | -| **Permissions** | βœ… PASS | Admin-only functions protected | -| **Math** | βœ… PASS | All calculations accurate | - ---- - -## Detailed Test Results - -### 1. Admin Initialization βœ… - -**Test:** -```clarity -(contract-call? .collateral-v1 init-admin) -``` - -**Result:** `(ok true)` βœ… - -**Verification:** -```clarity -(contract-call? .collateral-v1 is-admin tx-sender) -``` - -**Result:** `true` βœ… - -**Conclusion:** Admin initialization works correctly, deployer is set as admin. - ---- - -### 2. STX Deposit (Single User) βœ… - -**Initial Balance:** -```clarity -(contract-call? .collateral-v1 get-collateral tx-sender) -β†’ u0 βœ… -``` - -**Deposit 10 STX:** -```clarity -(contract-call? .collateral-v1 deposit-collateral u10000000) -β†’ (ok u10000000) βœ… -``` - -**Event Emitted:** -```json -{ - "event": "collateral-deposited", - "user": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", - "amount": u10000000, - "new-balance": u10000000, - "block-height": u1 -} -``` -βœ… Event structure correct, all fields present - -**New Balance:** -```clarity -(contract-call? .collateral-v1 get-collateral tx-sender) -β†’ u10000000 βœ… -``` - -**Conclusion:** Deposit function works correctly, balance updates, events emit properly. - ---- - -### 3. Withdrawal Request βœ… - -**Test:** -```clarity -(contract-call? .collateral-v1 request-withdraw u5000000) -β†’ (ok true) βœ… -``` - -**Event Emitted:** -```json -{ - "event": "withdraw-requested", - "user": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", - "amount": u5000000, - "current-collateral": u10000000, - "block-height": u1 -} -``` -βœ… Event emitted correctly - -**Important:** No STX was transferred (correct behavior - only signals intent) - -**Conclusion:** Withdrawal request works as designed - emits event for relayer monitoring. - ---- - -### 4. Multi-User Deposit βœ… - -**Switch to Wallet_1:** -``` -::set_tx_sender ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5 -``` - -**Deposit 10 STX from Wallet_1:** -```clarity -(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.collateral-v1 deposit-collateral u10000000) -β†’ (ok u10000000) βœ… -``` - -**Event Emitted:** -```json -{ - "event": "collateral-deposited", - "user": "ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5", - "amount": u10000000, - "new-balance": u10000000, - "block-height": u1 -} -``` -βœ… Correct user address in event - -**Total Collateral:** -```clarity -(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.collateral-v1 get-total-collateral) -β†’ u20000000 βœ… -``` - -**Math Check:** 10M (deployer) + 10M (wallet_1) = 20M βœ… - -**Conclusion:** Multi-user deposits work correctly, total collateral tracks accurately. - ---- - -### 5. Admin Unlock Collateral βœ… - -**Switch back to Admin:** -``` -::set_tx_sender ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM -``` - -**Unlock 5 STX for Wallet_1:** -```clarity -(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.collateral-v1 admin-unlock-collateral 'ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5 u5000000) -β†’ (ok u5000000) βœ… -``` -**Returns remaining balance** (10M - 5M = 5M) - -**Event Emitted:** -```json -{ - "event": "collateral-unlocked", - "user": "ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5", - "amount": u5000000, - "new-balance": u5000000, - "unlocked-by": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM", - "block-height": u1 -} -``` -βœ… All fields correct: user, amount, new-balance, unlocked-by - -**STX Transfer Event:** -```json -{ - "type": "stx_transfer_event", - "sender": "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.collateral-v1", - "recipient": "ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5", - "amount": "5000000" -} -``` -βœ… STX transferred from contract to user - -**Wallet_1 New Balance:** -```clarity -(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.collateral-v1 get-collateral 'ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5) -β†’ u5000000 βœ… -``` - -**Total Collateral After Unlock:** -```clarity -(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.collateral-v1 get-total-collateral) -β†’ u15000000 βœ… -``` - -**Math Check:** 20M - 5M (unlocked) = 15M βœ… - -**Conclusion:** Admin unlock works perfectly - STX returned to user, balances updated correctly. - ---- - -### 6. Portfolio Summary βœ… - -**Deployer Portfolio:** -```clarity -(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.collateral-v1 get-portfolio 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM) -β†’ { collateral: u10000000, total-protocol: u15000000 } βœ… -``` - -**Wallet_1 Portfolio:** -```clarity -(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.collateral-v1 get-portfolio 'ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5) -β†’ { collateral: u5000000, total-protocol: u15000000 } βœ… -``` - -**Conclusion:** Portfolio function returns correct individual and total balances. - ---- - -## Final State Verification - -| User | Deposited | Unlocked | Final Balance | -|------|-----------|----------|---------------| -| **Deployer (Admin)** | 10 STX | 0 STX | **10 STX** βœ… | -| **Wallet_1** | 10 STX | 5 STX | **5 STX** βœ… | -| **Total in Contract** | 20 STX | 5 STX | **15 STX** βœ… | - -**Math Verification:** 10 + 5 = 15 βœ… - ---- - -## Events Monitoring βœ… - -All events emitted correctly with proper structure: - -### βœ… collateral-deposited -- Contains: event, user, amount, new-balance, block-height -- Emitted on every deposit -- User address correctly captured - -### βœ… withdraw-requested -- Contains: event, user, amount, current-collateral, block-height -- Emitted on withdrawal request -- No STX transferred (correct behavior) - -### βœ… collateral-unlocked -- Contains: event, user, amount, new-balance, unlocked-by, block-height -- Emitted on admin unlock -- STX transfer event also triggered -- All fields accurate - ---- - -## Security Tests βœ… - -### Admin Permissions -- βœ… Only deployer can initialize admin -- βœ… Only admin can unlock collateral -- βœ… Admin status check works correctly - -### Non-Admin Restrictions -- ⚠️ Not explicitly tested in this session, but function has proper admin check - ---- - -## Important Discovery πŸ” - -**Contract Call Syntax:** - -When `tx-sender` is different from deployer, you **must** use fully qualified contract name: - -βœ… **Correct:** -```clarity -(contract-call? 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.collateral-v1 deposit-collateral u10000000) -``` - -❌ **Wrong (fails when tx-sender β‰  deployer):** -```clarity -(contract-call? .collateral-v1 deposit-collateral u10000000) -``` - -The shorthand `.collateral-v1` only works from deployer's context. - ---- - -## Next Steps πŸš€ - -### 1. βœ… Testing Complete -All manual tests passed successfully. - -### 2. 🎯 Deploy to Testnet -```bash -clarinet deploy --testnet -``` - -### 3. πŸ”§ Build Relayer Service -**Relayer must:** -- Monitor `collateral-deposited` events β†’ Call `register_stacks_collateral()` on Sui -- Monitor `withdraw-requested` events β†’ Verify debt on Sui β†’ Call `admin-unlock-collateral()` on Stacks -- Maintain Stacks ↔ Sui address mapping - -### 4. 🌐 Frontend Integration -**Update Stacks tab:** -- Add STX deposit UI -- Add withdrawal request UI -- Display collateral balance -- Show pending withdrawal requests -- Remove old borrow/repay UI (moved to Sui) - -### 5. πŸ”— End-to-End Testing -**Full cross-chain flow:** -``` -Stacks: Deposit STX - ↓ -Relayer: Detect + Register on Sui - ↓ -Sui: Borrow USDC - ↓ -Sui: Repay USDC - ↓ -Stacks: Request Withdraw - ↓ -Relayer: Verify debt = 0 on Sui - ↓ -Stacks: Admin Unlock β†’ User receives STX -``` - ---- - -## Test Coverage Summary - -- [x] βœ… Admin initialization -- [x] βœ… Admin status check -- [x] βœ… Single user deposit -- [x] βœ… Multi-user deposits -- [x] βœ… Balance tracking (individual) -- [x] βœ… Balance tracking (total) -- [x] βœ… Withdrawal request -- [x] βœ… Admin unlock collateral -- [x] βœ… Portfolio summary -- [x] βœ… Event emissions -- [x] βœ… STX transfers -- [x] βœ… Math accuracy -- [ ] ⚠️ Non-admin access denial (has protection, not explicitly tested) -- [ ] ⚠️ Error cases (zero amount, insufficient funds) - not tested in this session - -**Overall Test Coverage:** ~90% βœ… - ---- - -## Conclusion - -πŸŽ‰ **The `collateral-v1.clar` contract is PRODUCTION-READY for testnet deployment!** - -All core functionality works as designed: -- βœ… Deposits work correctly -- βœ… Balances tracked accurately -- βœ… Events emit properly for relayer monitoring -- βœ… Admin functions protected and functional -- βœ… Multi-user support verified -- βœ… Math is accurate -- βœ… STX transfers execute correctly - -**Contract is ready for:** -1. Testnet deployment -2. Relayer integration -3. Frontend integration -4. End-to-end cross-chain testing - ---- - -**Testing completed by:** Claude Code -**Testing date:** October 15, 2025 -**Contract version:** v1.0 -**Test environment:** Clarinet Console v3.8.1 diff --git a/hayyprotocol-stacks/contracts/collateral-v1.clar b/hayyprotocol-stacks/contracts/collateral-v1.clar deleted file mode 100644 index e6df90d..0000000 --- a/hayyprotocol-stacks/contracts/collateral-v1.clar +++ /dev/null @@ -1,159 +0,0 @@ -;; StackLend Collateral Contract v1 -;; For Stacks <-> Sui Cross-Chain Lending -;; This contract ONLY manages STX collateral on Stacks -;; All borrowing/lending happens on Sui chain - -(define-data-var total-collateral uint u0) -(define-data-var admin (optional principal) none) - -;; Collateral tracking per user -(define-map collateral - { user: principal } - { amount: uint } -) - -;; Error codes -(define-constant err-non-positive u100) -(define-constant err-insufficient-funds u101) -(define-constant err-not-admin u105) - -;; ======================================== -;; ADMIN FUNCTIONS -;; ======================================== - -(define-read-only (is-admin (who principal)) - (let ((a (var-get admin))) - (and (is-some a) (is-eq who (unwrap-panic a))) - ) -) - -(define-public (init-admin) - (let ((a (var-get admin))) - (begin - (asserts! (is-none a) (err err-not-admin)) - (var-set admin (some tx-sender)) - (ok true) - ) - ) -) - -;; ======================================== -;; READ-ONLY FUNCTIONS -;; ======================================== - -(define-read-only (contract-principal) - (as-contract tx-sender) -) - -(define-read-only (get-collateral (user principal)) - (get amount (default-to {amount: u0} (map-get? collateral {user: user}))) -) - -(define-read-only (get-total-collateral) - (var-get total-collateral) -) - -(define-read-only (get-portfolio (user principal)) - { - collateral: (get-collateral user), - total-protocol: (get-total-collateral) - } -) - -;; ======================================== -;; PUBLIC FUNCTIONS (User-facing) -;; ======================================== - -;; Deposit STX as collateral -;; This will be detected by relayer and registered on Sui -(define-public (deposit-collateral (amount uint)) - (begin - (asserts! (> amount u0) (err err-non-positive)) - (try! (stx-transfer? amount tx-sender (contract-principal))) - (let - ( - (prev (get-collateral tx-sender)) - (new-amt (+ prev amount)) - ) - (map-set collateral {user: tx-sender} {amount: new-amt}) - (var-set total-collateral (+ (var-get total-collateral) amount)) - (print { - event: "collateral-deposited", - user: tx-sender, - amount: amount, - new-balance: new-amt, - block-height: block-height - }) - (ok new-amt) - ) - ) -) - -;; Signal withdrawal request -;; Relayer will verify on Sui that user has no debt before unlocking -(define-public (request-withdraw (amount uint)) - (let ((current-collateral (get-collateral tx-sender))) - (begin - (asserts! (> amount u0) (err err-non-positive)) - (asserts! (>= current-collateral amount) (err err-insufficient-funds)) - (print { - event: "withdraw-requested", - user: tx-sender, - amount: amount, - current-collateral: current-collateral, - block-height: block-height - }) - (ok true) - ) - ) -) - -;; ======================================== -;; ADMIN FUNCTIONS (Relayer-only) -;; ======================================== - -;; Unlock collateral after verification on Sui -;; Called by relayer after confirming user has no debt on Sui -(define-public (admin-unlock-collateral (user principal) (amount uint)) - (let ((a (var-get admin)) - (prev (get-collateral user))) - (begin - (asserts! (and (is-some a) (is-eq tx-sender (unwrap-panic a))) (err err-not-admin)) - (asserts! (> amount u0) (err err-non-positive)) - (asserts! (>= prev amount) (err err-insufficient-funds)) - (try! (as-contract (stx-transfer? amount (contract-principal) user))) - (let ((new-amt (- prev amount))) - (map-set collateral {user: user} {amount: new-amt}) - (var-set total-collateral (- (var-get total-collateral) amount)) - (print { - event: "collateral-unlocked", - user: user, - amount: amount, - new-balance: new-amt, - unlocked-by: tx-sender, - block-height: block-height - }) - (ok new-amt) - ) - ) - ) -) - -;; Emergency admin withdrawal (only if needed) -(define-public (admin-emergency-withdraw (recipient principal) (amount uint)) - (let ((a (var-get admin))) - (begin - (asserts! (and (is-some a) (is-eq tx-sender (unwrap-panic a))) (err err-not-admin)) - (asserts! (> amount u0) (err err-non-positive)) - (try! (as-contract (stx-transfer? amount (contract-principal) recipient))) - (print { - event: "emergency-withdrawal", - recipient: recipient, - amount: amount, - by: tx-sender, - block-height: block-height - }) - (ok true) - ) - ) -) diff --git a/hayyprotocol-stacks/contracts/mock-oracle-v1.clar b/hayyprotocol-stacks/contracts/mock-oracle-v1.clar new file mode 100644 index 0000000..5abc73b --- /dev/null +++ b/hayyprotocol-stacks/contracts/mock-oracle-v1.clar @@ -0,0 +1,45 @@ +(define-data-var contract-owner principal tx-sender) + +(define-map prices + (string-ascii 10) + { + price: uint, + last-updated-at: uint + } +) + +(define-constant PRICE_PRECISION u100000000) + +(define-constant ERR_UNAUTHORIZED (err u401)) +(define-constant ERR_PRICE_NOT_AVAILABLE (err u404)) + +(define-public (set-price (symbol (string-ascii 10)) (price uint)) + (begin + (asserts! (is-eq tx-sender (var-get contract-owner)) ERR_UNAUTHORIZED) + (map-set prices symbol { + price: price, + last-updated-at: burn-block-height + }) + (print { topic: "price-update", symbol: symbol, price: price }) + (ok true) + ) +) + +(define-public (transfer-ownership (new-owner principal)) + (begin + (asserts! (is-eq tx-sender (var-get contract-owner)) ERR_UNAUTHORIZED) + (var-set contract-owner new-owner) + (ok true) + ) +) + +(define-read-only (get-price (symbol (string-ascii 10))) + (match (map-get? prices symbol) + price-data (ok price-data) + ERR_PRICE_NOT_AVAILABLE + ) +) + +(define-read-only (get-owner) + (ok (var-get contract-owner)) +) \ No newline at end of file diff --git a/hayyprotocol-stacks/contracts/mock-sbtc-v1.clar b/hayyprotocol-stacks/contracts/mock-sbtc-v1.clar new file mode 100644 index 0000000..1d6e40e --- /dev/null +++ b/hayyprotocol-stacks/contracts/mock-sbtc-v1.clar @@ -0,0 +1,89 @@ +;; Mock sBTC Token Contract (SIP-010 Fungible Token Standard) +;; sBTC uses 8 decimals (same as Bitcoin) + +(define-fungible-token sbtc) + +(define-constant contract-owner tx-sender) +(define-constant err-owner-only (err u100)) +(define-constant err-not-token-owner (err u101)) +(define-constant err-insufficient-balance (err u102)) +(define-constant err-invalid-amount (err u103)) + +(define-data-var token-uri (optional (string-utf8 256)) (some u"https://gateway.pinata.cloud/ipfs/QmSomeHashForSBTC")) + +(define-read-only (get-name) + (ok "sBTC") +) + +(define-read-only (get-symbol) + (ok "sBTC") +) + +(define-read-only (get-decimals) + (ok u8) ;; 8 decimals +) + +(define-read-only (get-balance (account principal)) + (ok (ft-get-balance sbtc account)) +) + +(define-read-only (get-total-supply) + (ok (ft-get-supply sbtc)) +) + +(define-read-only (get-token-uri) + (ok (var-get token-uri)) +) + +;; Transfer function (SIP-010 Standard) +(define-public (transfer (amount uint) (sender principal) (recipient principal) (memo (optional (buff 34)))) + (begin + (asserts! (is-eq tx-sender sender) err-not-token-owner) + (asserts! (> amount u0) err-invalid-amount) + (try! (ft-transfer? sbtc amount sender recipient)) + (match memo to-print (print to-print) 0x) + (ok true) + ) +) + +;; Mint function - simulates sBTC peg-in (wrapping BTC) +(define-public (mint (amount uint) (recipient principal)) + (begin + (asserts! (is-eq tx-sender contract-owner) err-owner-only) + (asserts! (> amount u0) err-invalid-amount) + (ft-mint? sbtc amount recipient) + ) +) + +;; Burn function - simulates sBTC peg-out (unwrapping to BTC) +(define-public (burn (amount uint) (sender principal)) + (begin + (asserts! (is-eq tx-sender sender) err-not-token-owner) + (asserts! (> amount u0) err-invalid-amount) + (ft-burn? sbtc amount sender) + ) +) + +;; Update token URI (owner only) +(define-public (set-token-uri (new-uri (optional (string-utf8 256)))) + (begin + (asserts! (is-eq tx-sender contract-owner) err-owner-only) + (ok (var-set token-uri new-uri)) + ) +) + +;; 1 BTC = 100,000,000 satoshis = 100000000 units in this contract +(define-read-only (btc-to-sbtc (btc-amount uint)) + (ok (* btc-amount u100000000)) +) + +;; Helper function to convert satoshis to BTC (for display) +(define-read-only (sbtc-to-btc (sbtc-amount uint)) + (ok (/ sbtc-amount u100000000)) +) + +;; Initialize with some test tokens (equivalent to 10 BTC) +;; Remove or adjust this for production +(begin + (try! (ft-mint? sbtc u1000000000 contract-owner)) +) \ No newline at end of file diff --git a/hayyprotocol-stacks/contracts/money-market-core.clar b/hayyprotocol-stacks/contracts/money-market-core.clar new file mode 100644 index 0000000..f1bc167 --- /dev/null +++ b/hayyprotocol-stacks/contracts/money-market-core.clar @@ -0,0 +1,353 @@ +(define-constant sbtc-contract .mock-sbtc-v1) +(define-constant oracle-contract .mock-oracle-v1) + +(define-constant contract-owner tx-sender) +(define-constant err-unauthorized (err u100)) +(define-constant err-insufficient-collateral (err u101)) +(define-constant err-insufficient-liquidity (err u102)) +(define-constant err-position-not-found (err u103)) +(define-constant err-invalid-amount (err u104)) +(define-constant err-health-factor-too-low (err u105)) +(define-constant err-not-liquidatable (err u106)) +(define-constant err-asset-not-supported (err u107)) +(define-constant err-oracle-error (err u108)) +(define-constant err-division-by-zero (err u109)) + +;; Helper to return this contract's principal +(define-read-only (contract-principal) + (as-contract tx-sender) +) + +;; Precision constants +(define-constant precision u1000000) ;; 6 decimals for percentages +(define-constant health-factor-threshold u1000000) ;; 1.0 = liquidatable + +;; Risk params (per-asset can be split later if you want) +(define-constant stx-ltv u700000) ;; 70% +(define-constant stx-liquidation-threshold u850000) ;; 85% +(define-constant sbtc-ltv u700000) +(define-constant sbtc-liquidation-threshold u850000) + +;; Protocol-level state +(define-data-var last-update-block uint u0) +(define-data-var total-users uint u0) + +;; Pool-level state for STX and sBTC +(define-data-var stx-total-supply uint u0) +(define-data-var stx-total-borrowed uint u0) +(define-data-var stx-reserves uint u0) + +(define-data-var sbtc-total-supply uint u0) +(define-data-var sbtc-total-borrowed uint u0) +(define-data-var sbtc-reserves uint u0) + +;; User positions +(define-map user-supplies { user: principal, asset: (string-ascii 10) } + { amount: uint, last-update: uint, is-collateral: bool }) + +(define-map user-borrows { user: principal, asset: (string-ascii 10) } + { amount: uint, last-update: uint }) + +;; === ORACLE === +(define-public (get-asset-price (asset (string-ascii 10))) + (let ( + (p (unwrap! (contract-call? oracle-contract get-price asset) err-oracle-error)) + ) + (ok p))) + +;; === HELPERS === +(define-read-only (min (a uint) (b uint)) (if (< a b) a b)) +(define-read-only (max (a uint) (b uint)) (if (> a b) a b)) + +(define-read-only (get-user-supply (user principal) (asset (string-ascii 10))) + (default-to { amount: u0, last-update: u0, is-collateral: false } + (map-get? user-supplies { user: user, asset: asset }))) + +(define-read-only (get-user-borrow (user principal) (asset (string-ascii 10))) + (default-to { amount: u0, last-update: u0 } + (map-get? user-borrows { user: user, asset: asset }))) + +;; total available liquidity per asset +(define-read-only (get-available-liquidity (asset (string-ascii 10))) + (if (is-eq asset "STX") + (ok (- (var-get stx-total-supply) (var-get stx-total-borrowed))) + (if (is-eq asset "sBTC") + (ok (- (var-get sbtc-total-supply) (var-get sbtc-total-borrowed))) + err-asset-not-supported))) + +;; Borrowing power with LTV +(define-public (get-user-borrowing-power (user principal)) + (let ( + (stx-s (get-user-supply user "STX")) + (sbtc-s (get-user-supply user "sBTC")) + (stx-p (get price (unwrap! (get-asset-price "STX") err-oracle-error))) + (sbtc-p (get price (unwrap! (get-asset-price "sBTC") err-oracle-error))) + ) + (ok (+ + (if (get is-collateral stx-s) + (/ (* (* (get amount stx-s) stx-p) stx-ltv) precision) + u0) + (if (get is-collateral sbtc-s) + (/ (* (* (get amount sbtc-s) sbtc-p) sbtc-ltv) precision) + u0)))) +) + +;; Health factor = (sum collateral * liq-threshold) / (sum borrows) +(define-public (get-user-health-factor (user principal)) + (let ( + (stx-s (get-user-supply user "STX")) + (sbtc-s (get-user-supply user "sBTC")) + (stx-b (get-user-borrow user "STX")) + (sbtc-b (get-user-borrow user "sBTC")) + (stx-p (get price (unwrap! (get-asset-price "STX") err-oracle-error))) + (sbtc-p (get price (unwrap! (get-asset-price "sBTC") err-oracle-error))) + (num (+ + (if (get is-collateral stx-s) + (/ (* (* (get amount stx-s) stx-p) stx-liquidation-threshold) precision) + u0) + (if (get is-collateral sbtc-s) + (/ (* (* (get amount sbtc-s) sbtc-p) sbtc-liquidation-threshold) precision) + u0))) + (den (+ (* (get amount stx-b) stx-p) (* (get amount sbtc-b) sbtc-p))) + ) + (ok (if (is-eq den u0) u18446744073709551615 (/ (* num precision) den))))) + +;; === SUPPLY === +(define-public (supply-stx (amount uint) (use-as-collateral bool)) + (begin + (asserts! (> amount u0) err-invalid-amount) + (try! (stx-transfer? amount tx-sender (contract-principal))) + (let ((cur (get amount (get-user-supply tx-sender "STX")))) + (map-set user-supplies { user: tx-sender, asset: "STX" } + { amount: (+ cur amount), last-update: stacks-block-height, is-collateral: use-as-collateral }) + (var-set stx-total-supply (+ (var-get stx-total-supply) amount)) + (print { event: "supply", asset: "STX", user: tx-sender, amount: amount, use-as-collateral: use-as-collateral, block: stacks-block-height }) + (ok true))) +) + +(define-public (supply-sbtc (amount uint) (use-as-collateral bool)) + (begin + (asserts! (> amount u0) err-invalid-amount) + ;; user -> vault + (try! (contract-call? sbtc-contract transfer amount tx-sender (contract-principal) none)) + (let ((cur (get amount (get-user-supply tx-sender "sBTC")))) + (map-set user-supplies { user: tx-sender, asset: "sBTC" } + { amount: (+ cur amount), last-update: stacks-block-height, is-collateral: use-as-collateral }) + (var-set sbtc-total-supply (+ (var-get sbtc-total-supply) amount)) + (print { event: "supply", asset: "sBTC", user: tx-sender, amount: amount, use-as-collateral: use-as-collateral, block: stacks-block-height }) + (ok true)))) + +;; === WITHDRAW === +(define-public (calculate-health-after-withdraw (user principal) (asset (string-ascii 10)) (amount uint)) + (let ( + (s (get-user-supply user asset)) + (new-s (if (> (get amount s) amount) (- (get amount s) amount) u0)) + ) + (if (is-eq asset "STX") + (let ((stx-p (get price (unwrap! (get-asset-price "STX") err-oracle-error))) + (sbtc-p (get price (unwrap! (get-asset-price "sBTC") err-oracle-error))) + (stx-supply new-s) + (sbtc-supply (get amount (get-user-supply user "sBTC"))) + (stx-b (get amount (get-user-borrow user "STX"))) + (sbtc-b (get amount (get-user-borrow user "sBTC")))) + (ok (let ( + (num (+ + (if (get is-collateral s) + (/ (* (* stx-supply stx-p) stx-liquidation-threshold) precision) + u0) + (if (get is-collateral (get-user-supply user "sBTC")) + (/ (* (* sbtc-supply sbtc-p) sbtc-liquidation-threshold) precision) + u0))) + (den (+ (* stx-b stx-p) (* sbtc-b sbtc-p)))) + (if (is-eq den u0) u18446744073709551615 (/ (* num precision) den))))) + ;; asset is sBTC + (let ((stx-p (get price (unwrap! (get-asset-price "STX") err-oracle-error))) + (sbtc-p (get price (unwrap! (get-asset-price "sBTC") err-oracle-error))) + (stx-supply (get amount (get-user-supply user "STX"))) + (sbtc-supply new-s) + (stx-b (get amount (get-user-borrow user "STX"))) + (sbtc-b (get amount (get-user-borrow user "sBTC")))) + (ok (let ( + (num (+ + (if (get is-collateral (get-user-supply user "STX")) + (/ (* (* stx-supply stx-p) stx-liquidation-threshold) precision) + u0) + (if (get is-collateral s) + (/ (* (* sbtc-supply sbtc-p) sbtc-liquidation-threshold) precision) + u0))) + (den (+ (* stx-b stx-p) (* sbtc-b sbtc-p)))) + (if (is-eq den u0) u18446744073709551615 (/ (* num precision) den)))))))) + +(define-public (withdraw-stx (amount uint)) + (let ((pos (get-user-supply tx-sender "STX"))) + (asserts! (> amount u0) err-invalid-amount) + (asserts! (>= (get amount pos) amount) err-insufficient-collateral) + (asserts! (>= (unwrap! (calculate-health-after-withdraw tx-sender "STX" amount) err-oracle-error) health-factor-threshold) err-health-factor-too-low) + (map-set user-supplies { user: tx-sender, asset: "STX" } (merge pos { amount: (- (get amount pos) amount), last-update: stacks-block-height })) + (var-set stx-total-supply (- (var-get stx-total-supply) amount)) + (try! (stx-transfer? amount (contract-principal) tx-sender)) + (print { event: "withdraw", asset: "STX", user: tx-sender, amount: amount, block: stacks-block-height }) + (ok true))) + +(define-public (withdraw-sbtc (amount uint)) + (let ((pos (get-user-supply tx-sender "sBTC"))) + (asserts! (> amount u0) err-invalid-amount) + (asserts! (>= (get amount pos) amount) err-insufficient-collateral) + (asserts! (>= (unwrap! (calculate-health-after-withdraw tx-sender "sBTC" amount) err-oracle-error) health-factor-threshold) err-health-factor-too-low) + (map-set user-supplies { user: tx-sender, asset: "sBTC" } (merge pos { amount: (- (get amount pos) amount), last-update: stacks-block-height })) + (var-set sbtc-total-supply (- (var-get sbtc-total-supply) amount)) + ;; vault -> user (must be called under as-contract, and sender must be the contract principal) + (let ((self (contract-principal))) (try! (as-contract (contract-call? sbtc-contract transfer amount self tx-sender none)))) + (print { event: "withdraw", asset: "sBTC", user: tx-sender, amount: amount, block: stacks-block-height }) + (ok true))) + +;; === BORROW === +(define-public (borrow-stx (amount uint)) + (let ( + (avail (unwrap! (get-available-liquidity "STX") err-insufficient-liquidity)) + (bp (unwrap! (get-user-borrowing-power tx-sender) err-oracle-error)) + (px (get price (unwrap! (get-asset-price "STX") err-oracle-error))) + (cur (get amount (get-user-borrow tx-sender "STX"))) + ) + (asserts! (> amount u0) err-invalid-amount) + (asserts! (>= avail amount) err-insufficient-liquidity) + (asserts! (>= bp (* amount px)) err-insufficient-collateral) + (map-set user-borrows { user: tx-sender, asset: "STX" } { amount: (+ cur amount), last-update: stacks-block-height }) + (var-set stx-total-borrowed (+ (var-get stx-total-borrowed) amount)) + (try! (stx-transfer? amount (contract-principal) tx-sender)) + (print { event: "borrow", asset: "STX", user: tx-sender, amount: amount, block: stacks-block-height }) + (ok true))) + +(define-public (borrow-sbtc (amount uint)) + (let ( + (avail (unwrap! (get-available-liquidity "sBTC") err-insufficient-liquidity)) + (bp (unwrap! (get-user-borrowing-power tx-sender) err-oracle-error)) + (px (get price (unwrap! (get-asset-price "sBTC") err-oracle-error))) + (cur (get amount (get-user-borrow tx-sender "sBTC"))) + ) + (asserts! (> amount u0) err-invalid-amount) + (asserts! (>= avail amount) err-insufficient-liquidity) + (asserts! (>= bp (* amount px)) err-insufficient-collateral) + (map-set user-borrows { user: tx-sender, asset: "sBTC" } { amount: (+ cur amount), last-update: stacks-block-height }) + (var-set sbtc-total-borrowed (+ (var-get sbtc-total-borrowed) amount)) + ;; vault -> user (as-contract with sender=self) + (let ((self (contract-principal))) (try! (as-contract (contract-call? sbtc-contract transfer amount self tx-sender none)))) + (print { event: "borrow", asset: "sBTC", user: tx-sender, amount: amount, block: stacks-block-height }) + (ok true))) + +;; === REPAY === +(define-public (repay-stx (amount uint)) + (let ((cur (get amount (get-user-borrow tx-sender "STX"))) + (repay (if (> amount cur) cur amount))) + (asserts! (> amount u0) err-invalid-amount) + (asserts! (> cur u0) err-position-not-found) + (try! (stx-transfer? repay tx-sender (contract-principal))) + (map-set user-borrows { user: tx-sender, asset: "STX" } { amount: (- cur repay), last-update: stacks-block-height }) + (var-set stx-total-borrowed (- (var-get stx-total-borrowed) repay)) + (print { event: "repay", asset: "STX", user: tx-sender, amount: repay, block: stacks-block-height }) + (ok true))) + +(define-public (repay-sbtc (amount uint)) + (let ((cur (get amount (get-user-borrow tx-sender "sBTC"))) + (repay (if (> amount cur) cur amount))) + (asserts! (> amount u0) err-invalid-amount) + (asserts! (> cur u0) err-position-not-found) + ;; user -> vault + (try! (contract-call? sbtc-contract transfer repay tx-sender (contract-principal) none)) + (map-set user-borrows { user: tx-sender, asset: "sBTC" } { amount: (- cur repay), last-update: stacks-block-height }) + (var-set sbtc-total-borrowed (- (var-get sbtc-total-borrowed) repay)) + (print { event: "repay", asset: "sBTC", user: tx-sender, amount: repay, block: stacks-block-height }) + (ok true))) + +;; === LIQUIDATION (basic) === +(define-public (liquidate-sbtc-with-stx (user principal) (repay-amount uint)) + (let ( + (hf (unwrap! (get-user-health-factor user) err-oracle-error)) + (sbtc-b (get amount (get-user-borrow user "sBTC"))) + (sbtc-s (get-user-supply user "sBTC")) + ) + (asserts! (< hf health-factor-threshold) err-not-liquidatable) + (asserts! (> sbtc-b u0) err-position-not-found) + (let ((actual (min repay-amount sbtc-b))) + ;; liquidator pays STX to protocol to cover some sBTC debt + (try! (stx-transfer? (* actual (get price (unwrap! (get-asset-price "sBTC") err-oracle-error))) tx-sender (contract-principal))) + (map-set user-borrows { user: user, asset: "sBTC" } { amount: (- sbtc-b actual), last-update: stacks-block-height }) + (var-set sbtc-total-borrowed (- (var-get sbtc-total-borrowed) actual)) + ;; seize sBTC collateral from user to liquidator (protocol-held -> liquidator) + (let ((self (contract-principal)) + (collateral-to-seize (min (get amount sbtc-s) actual))) + ;; decrease user supply + (map-set user-supplies { user: user, asset: "sBTC" } (merge sbtc-s { amount: (- (get amount sbtc-s) collateral-to-seize), last-update: stacks-block-height })) + (var-set sbtc-total-supply (- (var-get sbtc-total-supply) collateral-to-seize)) + (try! (as-contract (contract-call? sbtc-contract transfer collateral-to-seize self tx-sender none))) + (print { event: "liquidate", target: user, asset: "sBTC", repaid: actual, seized: collateral-to-seize, by: tx-sender })) + (ok true))) +) + +;; === COLLATERAL TOGGLE === +(define-public (enable-collateral (asset (string-ascii 10))) + (let ((pos (get-user-supply tx-sender asset))) + (asserts! (> (get amount pos) u0) err-position-not-found) + (map-set user-supplies { user: tx-sender, asset: asset } (merge pos { is-collateral: true })) + (print { event: "enable-collateral", user: tx-sender, asset: asset }) + (ok true))) + +(define-public (disable-collateral (asset (string-ascii 10))) + (let ((pos (get-user-supply tx-sender asset))) + (asserts! (> (get amount pos) u0) err-position-not-found) + (asserts! (>= (unwrap! (calculate-health-after-withdraw tx-sender asset u0) err-oracle-error) health-factor-threshold) err-health-factor-too-low) + (map-set user-supplies { user: tx-sender, asset: asset } (merge pos { is-collateral: false })) + (print { event: "disable-collateral", user: tx-sender, asset: asset }) + (ok true))) + +;; === METRICS (TVL, APY placeholders) === +(define-public (get-protocol-metrics) + (let ((stx-p (get price (unwrap! (get-asset-price "STX") err-oracle-error))) + (sbtc-p (get price (unwrap! (get-asset-price "sBTC") err-oracle-error)))) + (ok { + tvl: (+ (* (var-get stx-total-supply) stx-p) (* (var-get sbtc-total-supply) sbtc-p)), + total-borrows: (+ (* (var-get stx-total-borrowed) stx-p) (* (var-get sbtc-total-borrowed) sbtc-p)), + available-liquidity: (+ (* (- (var-get stx-total-supply) (var-get stx-total-borrowed)) stx-p) + (* (- (var-get sbtc-total-supply) (var-get sbtc-total-borrowed)) sbtc-p)), + reserves: (+ (* (var-get stx-reserves) stx-p) (* (var-get sbtc-reserves) sbtc-p)), + unique-users: (var-get total-users) + }))) + +;; User dashboard-lite +(define-public (get-user-dashboard (user principal)) + (let ( + (stx-s (get-user-supply user "STX")) + (sbtc-s (get-user-supply user "sBTC")) + (stx-b (get-user-borrow user "STX")) + (sbtc-b (get-user-borrow user "sBTC")) + (stx-p (get price (unwrap! (get-asset-price "STX") err-oracle-error))) + (sbtc-p (get price (unwrap! (get-asset-price "sBTC") err-oracle-error))) + (hf (unwrap! (get-user-health-factor user) err-oracle-error)) + (bp (unwrap! (get-user-borrowing-power user) err-oracle-error)) + ) + (ok { + health-factor: hf, + borrowing-power: bp, + total-supply-usd: (+ (* (get amount stx-s) stx-p) (* (get amount sbtc-s) sbtc-p)), + total-borrow-usd: (+ (* (get amount stx-b) stx-p) (* (get amount sbtc-b) sbtc-p)), + supplies: { + stx: { amount: (get amount stx-s), is-collateral: (get is-collateral stx-s) }, + sbtc: { amount: (get amount sbtc-s), is-collateral: (get is-collateral sbtc-s) } + }, + borrows: { + stx: { amount: (get amount stx-b) }, + sbtc: { amount: (get amount sbtc-b) } + } + }))) + +;; === ADMIN === +(define-public (admin-skim-sbtc (amount uint)) + (begin + (asserts! (is-eq tx-sender contract-owner) err-unauthorized) + (let ((self (contract-principal))) + (try! (as-contract (contract-call? sbtc-contract transfer amount self contract-owner none)))) + (ok true))) + +;; === INIT === +(begin (var-set last-update-block stacks-block-height)) + diff --git a/hayyprotocol-stacks/deployments/default.devnet-plan.yaml b/hayyprotocol-stacks/deployments/default.devnet-plan.yaml new file mode 100644 index 0000000..f3f6210 --- /dev/null +++ b/hayyprotocol-stacks/deployments/default.devnet-plan.yaml @@ -0,0 +1,32 @@ +--- +id: 0 +name: Devnet deployment +network: devnet +stacks-node: "http://localhost:20443" +bitcoin-node: "http://devnet:devnet@localhost:18443" +plan: + batches: + - id: 0 + transactions: + - contract-publish: + contract-name: mock-oracle + expected-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + cost: 60030 + path: "contracts\\mock-oracle.clar" + anchor-block-only: true + clarity-version: 1 + - contract-publish: + contract-name: mock-sbtc-v1 + expected-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + cost: 25790 + path: "contracts\\mock-sbtc-v1.clar" + anchor-block-only: true + clarity-version: 1 + - contract-publish: + contract-name: money-market-core + expected-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + cost: 316530 + path: "contracts\\money-market-core.clar" + anchor-block-only: true + clarity-version: 1 + epoch: "2.05" diff --git a/hayyprotocol-stacks/deployments/default.testnet-plan.yaml b/hayyprotocol-stacks/deployments/default.testnet-plan.yaml index 3cb0969..273e809 100644 --- a/hayyprotocol-stacks/deployments/default.testnet-plan.yaml +++ b/hayyprotocol-stacks/deployments/default.testnet-plan.yaml @@ -9,10 +9,31 @@ plan: - id: 0 transactions: - contract-publish: +<<<<<<< HEAD + contract-name: mock-oracle-v1 + expected-sender: ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0 + cost: 10520 + path: "contracts\\mock-oracle-v1.clar" +======= contract-name: collateral-v1 expected-sender: ST1WVZNYKMK1MS2V2B728ZWJ2C76TAN49C6HSY7JF cost: 50500 path: contracts/collateral-v1.clar +>>>>>>> 5a9a85724588c9f8cf2e1b0c0a39972081b8d1cd anchor-block-only: true - clarity-version: 2 + clarity-version: 3 + - contract-publish: + contract-name: mock-sbtc-v1 + expected-sender: ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0 + cost: 25790 + path: "contracts\\mock-sbtc-v1.clar" + anchor-block-only: true + clarity-version: 3 + - contract-publish: + contract-name: money-market-core + expected-sender: ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0 + cost: 178460 + path: "contracts\\money-market-core.clar" + anchor-block-only: true + clarity-version: 3 epoch: "3.0" diff --git a/hayyprotocol-stacks/docs/API-DOCUMENTATION.md b/hayyprotocol-stacks/docs/API-DOCUMENTATION.md new file mode 100644 index 0000000..1cbca05 --- /dev/null +++ b/hayyprotocol-stacks/docs/API-DOCUMENTATION.md @@ -0,0 +1,492 @@ +# Hayy Protocol Stacks - API Documentation + +## Overview + +Hayy Protocol is a cross-chain lending protocol that enables users to supply collateral on Stacks and borrow assets on Sui. This documentation covers all smart contracts in the Stacks implementation. + +## Contract Architecture + +### Core Contracts + +1. **money-market-core.clar** - Main lending protocol contract +2. **mock-sbtc-v1.clar** - Mock sBTC token (SIP-010 standard) +3. **mock-oracle-v1.clar** - Mock price oracle for testing + +--- + +## 1. Money Market Core Contract + +### Contract Principal +`.money-market-core` + +### Constants + +| Name | Value | Description | +|------|-------|-------------| +| `sbtc-contract` | `.mock-sbtc-v1` | sBTC token contract reference | +| `oracle-contract` | `.mock-oracle-v1` | Price oracle contract reference | +| `precision` | `1000000` | 6 decimal precision for calculations | +| `health-factor-threshold` | `1000000` | 1.0 - minimum health factor before liquidation | +| `stx-ltv` | `700000` | 70% loan-to-value ratio for STX | +| `stx-liquidation-threshold` | `850000` | 85% liquidation threshold for STX | +| `sbtc-ltv` | `700000` | 70% loan-to-value ratio for sBTC | +| `sbtc-liquidation-threshold` | `850000` | 85% liquidation threshold for sBTC | + +### Error Codes + +| Code | Error | Description | +|------|-------|-------------| +| `u100` | err-unauthorized | Caller not authorized | +| `u101` | err-insufficient-collateral | Insufficient collateral balance | +| `u102` | err-insufficient-liquidity | Insufficient liquidity in pool | +| `u103` | err-position-not-found | User position does not exist | +| `u104` | err-invalid-amount | Amount must be greater than 0 | +| `u105` | err-health-factor-too-low | Health factor below threshold | +| `u106` | err-not-liquidatable | Position not eligible for liquidation | +| `u107` | err-asset-not-supported | Asset not supported | +| `u108` | err-oracle-error | Oracle price fetch failed | +| `u109` | err-division-by-zero | Division by zero error | + +--- + +### Public Functions + +#### Supply Functions + +##### `supply-stx(amount, use-as-collateral)` +- **Type**: Public Function +- **Parameters**: + - `amount` (uint): Amount of STX to supply (in micro-STX) + - `use-as-collateral` (bool): Whether to use supplied STX as collateral +- **Returns**: `(response bool uint)` +- **Description**: Supply STX to the lending protocol +- **Access**: Any user +- **Events**: Emits `supply` event with asset, user, amount, and collateral status + +##### `supply-sbtc(amount, use-as-collateral)` +- **Type**: Public Function +- **Parameters**: + - `amount` (uint): Amount of sBTC to supply + - `use-as-collateral` (bool): Whether to use supplied sBTC as collateral +- **Returns**: `(response bool uint)` +- **Description**: Supply sBTC to the lending protocol +- **Access**: Any user +- **Events**: Emits `supply` event with asset, user, amount, and collateral status + +#### Withdraw Functions + +##### `withdraw-stx(amount)` +- **Type**: Public Function +- **Parameters**: + - `amount` (uint): Amount of STX to withdraw (in micro-STX) +- **Returns**: `(response bool uint)` +- **Description**: Withdraw STX from the protocol +- **Access**: Any user +- **Requirements**: + - Must have sufficient supplied balance + - Health factor must remain above threshold after withdrawal +- **Events**: Emits `withdraw` event + +##### `withdraw-sbtc(amount)` +- **Type**: Public Function +- **Parameters**: + - `amount` (uint): Amount of sBTC to withdraw +- **Returns**: `(response bool uint)` +- **Description**: Withdraw sBTC from the protocol +- **Access**: Any user +- **Requirements**: + - Must have sufficient supplied balance + - Health factor must remain above threshold after withdrawal +- **Events**: Emits `withdraw` event + +#### Borrow Functions + +##### `borrow-stx(amount)` +- **Type**: Public Function +- **Parameters**: + - `amount` (uint): Amount of STX to borrow (in micro-STX) +- **Returns**: `(response bool uint)` +- **Description**: Borrow STX from the protocol +- **Access**: Any user +- **Requirements**: + - Sufficient liquidity available + - Borrowing power must cover the requested amount +- **Events**: Emits `borrow` event + +##### `borrow-sbtc(amount)` +- **Type**: Public Function +- **Parameters**: + - `amount` (uint): Amount of sBTC to borrow +- **Returns**: `(response bool uint)` +- **Description**: Borrow sBTC from the protocol +- **Access**: Any user +- **Requirements**: + - Sufficient liquidity available + - Borrowing power must cover the requested amount +- **Events**: Emits `borrow` event + +#### Repay Functions + +##### `repay-stx(amount)` +- **Type**: Public Function +- **Parameters**: + - `amount` (uint): Amount of STX to repay (in micro-STX) +- **Returns**: `(response bool uint)` +- **Description**: Repay borrowed STX +- **Access**: Any user +- **Note**: Will repay up to the total borrowed amount (excess is not returned) +- **Events**: Emits `repay` event + +##### `repay-sbtc(amount)` +- **Type**: Public Function +- **Parameters**: + - `amount` (uint): Amount of sBTC to repay +- **Returns**: `(response bool uint)` +- **Description**: Repay borrowed sBTC +- **Access**: Any user +- **Note**: Will repay up to the total borrowed amount (excess is not returned) +- **Events**: Emits `repay` event + +#### Collateral Management + +##### `enable-collateral(asset)` +- **Type**: Public Function +- **Parameters**: + - `asset` (string-ascii 10): Asset symbol ("STX" or "sBTC") +- **Returns**: `(response bool uint)` +- **Description**: Enable supplied asset as collateral +- **Access**: Any user +- **Requirements**: Must have supplied balance of the asset +- **Events**: Emits `enable-collateral` event + +##### `disable-collateral(asset)` +- **Type**: Public Function +- **Parameters**: + - `asset` (string-ascii 10): Asset symbol ("STX" or "sBTC") +- **Returns**: `(response bool uint)` +- **Description**: Disable asset as collateral +- **Access**: Any user +- **Requirements**: Health factor must remain above threshold +- **Events**: Emits `disable-collateral` event + +#### Liquidation + +##### `liquidate-sbtc-with-stx(user, repay-amount)` +- **Type**: Public Function +- **Parameters**: + - `user` (principal): Address of the borrower to liquidate + - `repay-amount` (uint): Amount of sBTC debt to repay +- **Returns**: `(response bool uint)` +- **Description**: Liquidate a user's sBTC position using STX +- **Access**: Any liquidator +- **Requirements**: + - User's health factor must be below threshold + - User must have sBTC debt +- **Events**: Emits `liquidate` event + +#### Admin Functions + +##### `admin-skim-sbtc(amount)` +- **Type**: Public Function +- **Parameters**: + - `amount` (uint): Amount of sBTC to skim +- **Returns**: `(response bool uint)` +- **Description**: Admin function to skim sBTC from contract +- **Access**: Contract owner only +- **Security**: Critical admin function + +--- + +### Read-Only Functions + +#### Price Oracle + +##### `get-asset-price(asset)` +- **Type**: Read-Only Function +- **Parameters**: + - `asset` (string-ascii 10): Asset symbol ("STX" or "sBTC") +- **Returns**: `(response {price: uint, last-updated-at: uint} uint)` +- **Description**: Get current price of asset from oracle + +#### User Position Queries + +##### `get-user-borrowing-power(user)` +- **Type**: Public Function (read-only) +- **Parameters**: + - `user` (principal): User address +- **Returns**: `(response uint uint)` +- **Description**: Calculate user's total borrowing power based on collateral + +##### `get-user-health-factor(user)` +- **Type**: Public Function (read-only) +- **Parameters**: + - `user` (principal): User address +- **Returns**: `(response uint uint)` +- **Description**: Calculate user's health factor (max value if no debt) + +##### `get-user-dashboard(user)` +- **Type**: Public Function (read-only) +- **Parameters**: + - `user` (principal): User address +- **Returns**: `(response {health-factor: uint, borrowing-power: uint, total-supply-usd: uint, total-borrow-usd: uint, supplies: {...}, borrows: {...}} uint)` +- **Description**: Get comprehensive user dashboard data + +#### Protocol Metrics + +##### `get-protocol-metrics()` +- **Type**: Public Function (read-only) +- **Parameters**: None +- **Returns**: `(response {tvl: uint, total-borrows: uint, available-liquidity: uint, reserves: uint, unique-users: uint} uint)` +- **Description**: Get protocol-wide metrics + +#### Liquidity Queries + +##### `get-available-liquidity(asset)` +- **Type**: Read-Only Function +- **Parameters**: + - `asset` (string-ascii 10): Asset symbol ("STX" or "sBTC") +- **Returns**: `(response uint uint)` +- **Description**: Get available liquidity for lending + +--- + +## 2. Mock sBTC Token Contract + +### Contract Principal +`.mock-sbtc-v1` + +### Overview +Mock implementation of sBTC following SIP-010 Fungible Token Standard with 8 decimals (same as Bitcoin). + +### Public Functions + +#### Token Standard Functions + +##### `transfer(amount, sender, recipient, memo)` +- **Type**: Public Function +- **Parameters**: + - `amount` (uint): Amount to transfer + - `sender` (principal): Token owner + - `recipient` (principal): Token recipient + - `memo` (optional (buff 34)): Optional memo +- **Returns**: `(response bool uint)` +- **Description**: Transfer tokens between accounts +- **Access**: Token owner only + +#### Mint/Burn Functions + +##### `mint(amount, recipient)` +- **Type**: Public Function +- **Parameters**: + - `amount` (uint): Amount to mint + - `recipient` (principal): Recipient address +- **Returns**: `(response bool uint)` +- **Description**: Mint new tokens (simulates BTC peg-in) +- **Access**: Contract owner only + +##### `burn(amount, sender)` +- **Type**: Public Function +- **Parameters**: + - `amount` (uint): Amount to burn + - `sender` (principal): Token owner +- **Returns**: `(response bool uint)` +- **Description**: Burn tokens (simulates BTC peg-out) +- **Access**: Token owner only + +#### Admin Functions + +##### `set-token-uri(new-uri)` +- **Type**: Public Function +- **Parameters**: + - `new-uri` (optional (string-utf8 256)): New token URI +- **Returns**: `(response bool uint)` +- **Description**: Update token metadata URI +- **Access**: Contract owner only + +### Read-Only Functions + +##### `get-name()` +- **Returns**: `(response (string-ascii 32) uint)` +- **Description**: Returns "sBTC" + +##### `get-symbol()` +- **Returns**: `(response (string-ascii 32) uint)` +- **Description**: Returns "sBTC" + +##### `get-decimals()` +- **Returns**: `(response uint uint)` +- **Description**: Returns 8 (decimals) + +##### `get-balance(account)` +- **Parameters**: + - `account` (principal): Account address +- **Returns**: `(response uint uint)` +- **Description**: Get token balance of account + +##### `get-total-supply()` +- **Returns**: `(response uint uint)` +- **Description**: Get total token supply + +##### `get-token-uri()` +- **Returns**: `(response (optional (string-utf8 256)) uint)` +- **Description**: Get token metadata URI + +#### Utility Functions + +##### `btc-to-sbtc(btc-amount)` +- **Parameters**: + - `btc-amount` (uint): Amount in BTC +- **Returns**: `(response uint uint)` +- **Description**: Convert BTC to sBTC units (multiply by 100,000,000) + +##### `sbtc-to-btc(sbtc-amount)` +- **Parameters**: + - `sbtc-amount` (uint): Amount in sBTC units +- **Returns**: `(response uint uint)` +- **Description**: Convert sBTC units to BTC (divide by 100,000,000) + +--- + +## 3. Mock Oracle Contract + +### Contract Principal +`.mock-oracle-v1` + +### Overview +Mock price oracle for testing purposes. In production, this should be replaced with a real price feed. + +### Constants + +| Name | Value | Description | +|------|-------|-------------| +| `PRICE_PRECISION` | `100000000` | 8 decimal precision for prices | + +### Public Functions + +##### `set-price(symbol, price)` +- **Type**: Public Function +- **Parameters**: + - `symbol` (string-ascii 10): Asset symbol + - `price` (uint): Price in USD with 8 decimals +- **Returns**: `(response bool uint)` +- **Description**: Set price for an asset +- **Access**: Contract owner only +- **Events**: Emits `price-update` event + +##### `transfer-ownership(new-owner)` +- **Type**: Public Function +- **Parameters**: + - `new-owner` (principal): New owner address +- **Returns**: `(response bool uint)` +- **Description**: Transfer contract ownership +- **Access**: Contract owner only + +### Read-Only Functions + +##### `get-price(symbol)` +- **Parameters**: + - `symbol` (string-ascii 10): Asset symbol +- **Returns**: `(response {price: uint, last-updated-at: uint} uint)` +- **Description**: Get price data for asset + +##### `get-owner()` +- **Returns**: `(response principal uint)` +- **Description**: Get contract owner address + +--- + +## Event Structure + +All events follow a consistent structure for monitoring and indexing: + +### Supply Event +```clarity +{ + event: "supply", + asset: "STX" | "sBTC", + user: principal, + amount: uint, + use-as-collateral: bool, + block: uint +} +``` + +### Withdraw Event +```clarity +{ + event: "withdraw", + asset: "STX" | "sBTC", + user: principal, + amount: uint, + block: uint +} +``` + +### Borrow Event +```clarity +{ + event: "borrow", + asset: "STX" | "sBTC", + user: principal, + amount: uint, + block: uint +} +``` + +### Repay Event +```clarity +{ + event: "repay", + asset: "STX" | "sBTC", + user: principal, + amount: uint, + block: uint +} +``` + +### Liquidate Event +```clarity +{ + event: "liquidate", + target: principal, + asset: "sBTC", + repaid: uint, + seized: uint, + by: principal +} +``` + +### Collateral Events +```clarity +{ + event: "enable-collateral" | "disable-collateral", + user: principal, + asset: "STX" | "sBTC" +} +``` + +--- + +## Contract Interactions + +### Typical User Flow + +1. **Supply Assets**: Call `supply-stx()` or `supply-sbtc()` +2. **Enable Collateral**: Call `enable-collateral()` for supplied assets +3. **Borrow**: Call `borrow-stx()` or `borrow-sbtc()` up to borrowing power +4. **Repay**: Call `repay-stx()` or `repay-sbtc()` to reduce debt +5. **Withdraw**: Call `withdraw-stx()` or `withdraw-sbtc()` to retrieve assets + +### Liquidator Flow + +1. **Monitor**: Watch for positions with health factor < 1.0 +2. **Liquidate**: Call `liquidate-sbtc-with-stx()` to liquidate underwater positions +3. **Profit**: Receive discounted collateral in exchange for repaying debt + +### Cross-Chain Integration + +The Stacks contracts are designed to work with Sui contracts through a relayer system: + +- Stacks manages STX collateral deposits/withdrawals +- Sui handles all borrowing, lending, and sBTC collateral +- Relayer monitors events and synchronizes state between chains \ No newline at end of file diff --git a/hayyprotocol-stacks/docs/ARCHITECTURE-OVERVIEW.md b/hayyprotocol-stacks/docs/ARCHITECTURE-OVERVIEW.md new file mode 100644 index 0000000..5169118 --- /dev/null +++ b/hayyprotocol-stacks/docs/ARCHITECTURE-OVERVIEW.md @@ -0,0 +1,462 @@ +# Hayy Protocol Stacks - Architecture Overview + +## System Architecture + +### High-Level Architecture + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Frontend UI β”‚ β”‚ Backend API β”‚ β”‚ Relayer Serviceβ”‚ +β”‚ (React/Next) β”‚ β”‚ (Express.js) β”‚ β”‚ (Node.js) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ + β”‚ HTTP/WebSocket β”‚ HTTP/WebSocket β”‚ WebSocket + β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Stacks Blockchain β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Money Market β”‚ β”‚ Mock sBTC Token β”‚ β”‚ Mock Oracle β”‚ β”‚ +β”‚ β”‚ Core Contract β”‚ β”‚ Contract β”‚ β”‚ Contract β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ + β”‚ Cross-Chain Events β”‚ Price Feed + β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Sui Blockchain β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Borrow β”‚ β”‚ Lending Pools β”‚ β”‚ Faucet Pool β”‚ β”‚ +β”‚ β”‚ Controller β”‚ β”‚ (USDC/sBTC) β”‚ β”‚ (Test Tokens) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Cross-Chain Flow Architecture + +### 1. Deposit Flow (Stacks β†’ Sui) + +``` +User Action (Frontend) Stacks Contract Relayer Sui Contract +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ User deposits STX β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ via UI β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ 1. Click "Deposit" β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ 2. Enter amount β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ 3. Confirm wallet β”‚ ─────────────► β”‚ 4. supply-stx() β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ transaction β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ 5. Emit event: β”‚ ─────────────► β”‚ 6. Monitor β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ "supply" β”‚ β”‚ events β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ 7. Call Sui β”‚ ─────────────► β”‚ 8. Register β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ contract β”‚ β”‚ collateral β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ 9. Update user β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ position β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### 2. Borrow Flow (Sui Only) + +``` +User Action (Frontend) Sui Contract Backend API +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ User wants to borrowβ”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ USDC β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ 1. Navigate to β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ Borrow page β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ 2. Select USDC β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ 3. Enter amount β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ 4. Confirm β”‚ ─────────────► β”‚ 5. borrow-usdc()β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ 6. Check β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ collateral β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ 7. Transfer USDC β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ to user β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ 8. Update debt β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ 9. Emit event β”‚ ─────────────► β”‚ 10. Update β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ cache β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ 11. Notify β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ UI β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### 3. Withdraw Flow (Stacks ← Sui) + +``` +User Action (Frontend) Sui Contract Relayer Stacks Contract +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ User wants to β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ withdraw STX β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ 1. Click "Withdraw" β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ 2. Enter amount β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ 3. Submit request β”‚ ─────────────► β”‚ 4. request- β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ withdraw() β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ 5. Check debt β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ = 0? β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ 6. If yes: β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ emit event β”‚ ─────────────► β”‚ 7. Monitor β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ events β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ 8. Verify β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ debt = 0 β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ 9. Call β”‚ ─────────────► β”‚ 10. admin- β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ Stacks β”‚ β”‚ unlock- β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ contract β”‚ β”‚ collateral() β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ 11. Transfer STX β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ to user β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ 12. Emit event β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### 4. Liquidation Flow (Cross-Chain) + +``` +Liquidator Bot Sui Contract Relayer Stacks Contract +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Bot monitors liquid-β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ atable positions β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ 1. Scan all users β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ 2. Calculate health β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ factors β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ 3. Find HF < 1.0 β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ 4. Execute liquid- β”‚ ─────────────► β”‚ 5. liquidate() β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ ation on Sui β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ 6. Repay debt β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ 7. Seize β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ collateral β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ 8. Emit event β”‚ ─────────────► β”‚ 9. Monitor β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ events β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ 10. Update β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ Stacks β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ position β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ 11. Notify β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ frontend β”‚ β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Contract Interaction Flow + +### User Journey Complete Flow + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ USER JOURNEY β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Onboardingβ”‚ β”‚ Supply β”‚ β”‚ Borrow β”‚ β”‚ Repay β”‚ β”‚ Withdraw β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Connect Wallet β”‚ β”‚ Supply STX on β”‚ β”‚ Borrow USDC on β”‚ β”‚ Repay USDC on β”‚ β”‚ Withdraw STX β”‚ +β”‚ (Hiro Wallet) β”‚ β”‚ Stacks β”‚ β”‚ Sui β”‚ β”‚ Sui β”‚ β”‚ from Stacks β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Get Address β”‚ β”‚ call supply-stx()β”‚ β”‚ call borrow- β”‚ β”‚ call repay- β”‚ β”‚ call request- β”‚ +β”‚ & Balance β”‚ β”‚ β”‚ β”‚ usdc() β”‚ β”‚ usdc() β”‚ β”‚ withdraw() β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Show Dashboard β”‚ β”‚ STX β†’ Stacks β”‚ β”‚ USDC β†’ User β”‚ β”‚ USDC β†’ Protocol β”‚ β”‚ Verify debt = 0 β”‚ +β”‚ with TVL, β”‚ β”‚ Contract β”‚ β”‚ wallet β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ APY, Health β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Enable β”‚ β”‚ Register on Sui β”‚ β”‚ Update debt β”‚ β”‚ Reduce debt β”‚ β”‚ Admin unlocks β”‚ +β”‚ Notifications β”‚ β”‚ via Relayer β”‚ β”‚ on Sui β”‚ β”‚ on Sui β”‚ β”‚ STX on Stacks β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Data Flow Architecture + +### 1. Real-time Data Flow + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Frontend UI β”‚ β”‚ Backend API β”‚ β”‚ Event Stream β”‚ β”‚ Blockchain β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ Processor β”‚ β”‚ Events β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ β”‚ + β”‚ WebSocket/HTTP β”‚ WebSocket β”‚ WebSocket β”‚ WebSocket + β”‚ β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ React State β”‚ β”‚ Cache Layer β”‚ β”‚ Event Buffer β”‚ β”‚ New Blocks β”‚ +β”‚ Management β”‚ β”‚ (Redis/Memory) β”‚ β”‚ (Queue) β”‚ β”‚ & Transactions β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ β”‚ + β”‚ State Updates β”‚ Cache Invalidation β”‚ Event Processing β”‚ Event Emission + β”‚ β”‚ β”‚ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + Real-time UI Updates +``` + +### 2. Cache Architecture + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Cache Strategy β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ L1 Cache β”‚ β”‚ L2 Cache β”‚ β”‚ L3 Cache β”‚ +β”‚ (Frontend) β”‚ β”‚ (Backend) β”‚ β”‚ (Database) β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β€’ React State β”‚ β”‚ β€’ Redis β”‚ β”‚ β€’ PostgreSQL β”‚ +β”‚ β€’ Local Storage β”‚ β”‚ β€’ Memory Store β”‚ β”‚ β€’ Time Series β”‚ +β”‚ β€’ Session Data β”‚ β”‚ β€’ Query Results β”‚ β”‚ β€’ Historical β”‚ +β”‚ TTL: 5-30s β”‚ β”‚ TTL: 1-5min β”‚ β”‚ TTL: Permanent β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ + β”‚ State Sync β”‚ Cache Sync β”‚ Query Results + β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Cache Invalidation β”‚ +β”‚ β”‚ +β”‚ β€’ WebSocket Events β†’ Invalidate Related Cache β”‚ +β”‚ β€’ Time-based TTL β†’ Auto-expire β”‚ +β”‚ β€’ Manual Cache Clear β†’ Admin Operations β”‚ +β”‚ β€’ Chain Reorgs β†’ Invalidate Affected Blocks β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Security Architecture + +### 1. Trust Model + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Trust Boundaries β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ User Wallet β”‚ β”‚ Frontend β”‚ β”‚ Backend β”‚ +β”‚ (Trusted) β”‚ β”‚ (Semi-Trusted)β”‚ β”‚ (Trusted) β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β€’ Private Keys β”‚ β€’ No secrets β”‚ β€’ API Keys β”‚ +β”‚ β€’ Transaction β”‚ β€’ Client-side β”‚ β€’ Admin Keys β”‚ +β”‚ Signing β”‚ β€’ Validation β”‚ β€’ Multi-sig β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ + β”‚ Signed Transactions β”‚ HTTP/HTTPS β”‚ Internal API + β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Smart Contracts β”‚ +β”‚ (Trustless / Verified) β”‚ +β”‚ β”‚ +β”‚ β€’ Immutable Logic β€’ Audited Code β€’ On-chain Validationβ”‚ +β”‚ β€’ Transparent State β€’ Open Source β€’ Economic Security β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### 2. Attack Vector Defense + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Security Layers β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Input β”‚ β”‚ Transaction β”‚ β”‚ Contract β”‚ +β”‚ Validation β”‚ β”‚ Validation β”‚ β”‚ Validation β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β€’ Type Checking β”‚ β€’ Signature β”‚ β€’ Access Control β”‚ +β”‚ β€’ Range Checks β”‚ β€’ Nonce Check β”‚ β€’ State Checks β”‚ +β”‚ β€’ Sanitization β”‚ β€’ Gas Limits β”‚ β€’ Reentrancy β”‚ +β”‚ β€’ Rate Limiting β”‚ β€’ Post Conditionsβ”‚ β€’ Integer Overflowβ”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ + β”‚ Clean Data β”‚ Valid Transactions β”‚ Secure Execution + β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Monitoring & Response β”‚ +β”‚ β”‚ +β”‚ β€’ Real-time Alerts β€’ Incident Response β€’ Forensic Analysis β”‚ +β”‚ β€’ Anomaly Detection β€’ Emergency Pause β€’ Recovery Plans β”‚ +β”‚ β€’ Audit Logging β€’ Multi-sig Controls β€’ Insurance β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Component Architecture + +### 1. Frontend Architecture + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Frontend Architecture β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ UI Components β”‚ β”‚ State β”‚ β”‚ Services β”‚ +β”‚ β”‚ β”‚ Management β”‚ β”‚ β”‚ +β”‚ β€’ React Hooks β”‚ β€’ Zustand/Redux β”‚ β€’ Wallet Connect β”‚ +β”‚ β€’ Web3 Utils β”‚ β€’ Query Cache β”‚ β€’ API Client β”‚ +β”‚ β€’ Charts β”‚ β€’ Event Store β”‚ β€’ Event Stream β”‚ +β”‚ β€’ Forms β”‚ β€’ User Profile β”‚ β€’ Notifications β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ + β”‚ Props/Events β”‚ State Updates β”‚ Data Fetching + β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Application Router β”‚ +β”‚ β”‚ +β”‚ β€’ Page Routes β€’ Protected Routes β€’ Error Boundaries β”‚ +β”‚ β€’ Query Parameters β€’ SEO Optimization β€’ Analytics β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### 2. Backend Architecture + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Backend Architecture β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ API Layer β”‚ β”‚ Business β”‚ β”‚ Data Layer β”‚ +β”‚ β”‚ β”‚ Logic β”‚ β”‚ β”‚ +β”‚ β€’ REST/GraphQL β”‚ β€’ Lending Logic β”‚ β€’ PostgreSQL β”‚ +β”‚ β€’ WebSocket β”‚ β€’ Risk Engine β”‚ β€’ Redis Cache β”‚ +β”‚ β€’ Auth/AuthZ β”‚ β€’ Event Handler β”‚ β€’ Time Series β”‚ +β”‚ β€’ Rate Limiting β”‚ β€’ Relayer Logic β”‚ β€’ File Storage β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ + β”‚ HTTP/WebSocket β”‚ Business Rules β”‚ Data Persistence + β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Infrastructure β”‚ +β”‚ β”‚ +β”‚ β€’ Load Balancer β€’ Container Orchestration β€’ Monitoring β”‚ +β”‚ β€’ API Gateway β€’ Service Mesh β€’ Logging β”‚ +β”‚ β€’ CDN β€’ Secret Management β€’ Backup β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Deployment Architecture + +### 1. Stacks Deployment + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Stacks Deployment β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Development β”‚ β”‚ Testnet β”‚ β”‚ Mainnet β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β€’ Local Simnet β”‚ β€’ Public Testnet β”‚ β€’ Production β”‚ +β”‚ β€’ Clarinet β”‚ β€’ Stacks 2.4 β”‚ β€’ Audited Code β”‚ +β”‚ β€’ Unit Tests β”‚ β€’ Test STX β”‚ β€’ Multi-sig β”‚ +β”‚ β€’ Integration β”‚ β€’ Test Tokens β”‚ β€’ Monitoring β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ + β”‚ Development β”‚ Testing β”‚ Production + β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Contract Addresses β”‚ +β”‚ β”‚ +β”‚ β€’ Dev: Localhost β€’ Test: ST27SK3... β€’ Main: TBD β”‚ +β”‚ β€’ Mock Oracle β€’ Mock sBTC β€’ Real Oracle β”‚ +β”‚ β€’ Test Tokens β€’ Test Faucet β€’ Real Tokens β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### 2. Infrastructure Deployment + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Infrastructure Deployment β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Frontend β”‚ β”‚ Backend β”‚ β”‚ Relayer β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β€’ Vercel/Netlifyβ”‚ β€’ AWS/GCP β”‚ β€’ Dedicated VM β”‚ +β”‚ β€’ CDN β”‚ β€’ Docker/K8s β”‚ β€’ High Uptime β”‚ +β”‚ β€’ Static Assets β”‚ β€’ Auto-scaling β”‚ β€’ Monitoring β”‚ +β”‚ β€’ SPA β”‚ β€’ Load Balancer β”‚ β€’ Alerts β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ + β”‚ Global CDN β”‚ API Endpoints β”‚ Cross-Chain + β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Monitoring & Observability β”‚ +β”‚ β”‚ +β”‚ β€’ Prometheus/Grafana β€’ ELK Stack β€’ Sentry β€’ APM β”‚ +β”‚ β€’ AlertManager β€’ Log Aggregation β€’ Error Tracking β€’ Uptimeβ”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Performance Architecture + +### 1. Request Flow Optimization + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Performance Optimization β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + +User Request β†’ CDN β†’ Edge Cache β†’ API Cache β†’ Database β†’ Blockchain + β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ <100ms β”‚ β”‚ <50ms β”‚ β”‚ <10ms β”‚ β”‚ <100ms β”‚ β”‚ <200ms β”‚ β”‚ 1-5s β”‚ +β”‚ Static β”‚ β”‚ Static β”‚ β”‚ API β”‚ β”‚ Query β”‚ β”‚ DB β”‚ β”‚ On-chainβ”‚ +β”‚ Assets β”‚ β”‚ Content β”‚ β”‚ Results β”‚ β”‚ Cache β”‚ β”‚ Query β”‚ β”‚ Call β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### 2. Caching Strategy + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Multi-Level Caching β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Browser β”‚ β”‚ CDN β”‚ β”‚ Edge β”‚ +β”‚ Cache β”‚ β”‚ Cache β”‚ β”‚ Cache β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β€’ Static Assets β”‚ β€’ Static Content β”‚ β€’ API Responses β”‚ +β”‚ β€’ API Responses β”‚ β€’ Pages β”‚ β€’ User Sessions β”‚ +β”‚ β€’ 5-30 min TTL β”‚ β€’ 1-24 hour TTL β”‚ β€’ 1-5 min TTL β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ + β”‚ Cache Miss β”‚ Cache Miss β”‚ Cache Miss + β”‚ β”‚ β”‚ + β–Ό β–Ό β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Origin Server β”‚ +β”‚ β”‚ +β”‚ β€’ Application Cache β€’ Database Cache β€’ Blockchain Cache β”‚ +β”‚ β€’ Redis/Memory β€’ Query Results β€’ Read-only Calls β”‚ +β”‚ β€’ 1-5 min TTL β€’ 10-60 min TTL β€’ 30-60 sec TTL β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +This architecture overview provides a comprehensive view of the Hayy Protocol system, including cross-chain flows, security models, deployment strategies, and performance optimizations. \ No newline at end of file diff --git a/hayyprotocol-stacks/docs/DEPLOYMENT-GUIDE.md b/hayyprotocol-stacks/docs/DEPLOYMENT-GUIDE.md new file mode 100644 index 0000000..b37d277 --- /dev/null +++ b/hayyprotocol-stacks/docs/DEPLOYMENT-GUIDE.md @@ -0,0 +1,1225 @@ +# Hayy Protocol Stacks - Deployment Guide + +## Overview + +This guide provides comprehensive instructions for deploying the Hayy Protocol Stacks contracts across different environments, along with integration steps for frontend and backend systems. + +## Prerequisites + +### Development Environment + +1. **Node.js** (v18+) +2. **Clarinet CLI** (latest version) +3. **Git** +4. **Stacks Wallet** (Hiro Wallet or compatible) + +### Installation + +```bash +# Install Clarinet +curl -L https://github.com/hirosystems/clarinet/releases/latest/download/clarinet-linux-x64.tar.gz | tar xz +sudo mv clarinet-linux-x64/clarinet /usr/local/bin/ + +# Verify installation +clarinet --version + +# Clone repository +git clone +cd hayyprotocol-stacks + +# Install dependencies +npm install +``` + +--- + +## 1. Local Development Setup + +### 1.1 Initialize Local Environment + +```bash +# Create new Clarinet project (if starting fresh) +clarinet new hayyprotocol-stacks + +# Or use existing project +cd hayyprotocol-stacks + +# Start local development environment +clarinet console +``` + +### 1.2 Local Configuration + +The `Clarinet.toml` file should be configured as follows: + +```toml +[project] +name = "hayyprotocol" +description = "Hayy Protocol - Cross-chain lending protocol" +authors = ["Your Name"] +telemetry = true +cache_dir = "./.cache" + +[contracts.mock-sbtc-v1] +path = "contracts/mock-sbtc-v1.clar" +clarity_version = 3 +epoch = "3.0" + +[contracts.mock-oracle-v1] +path = "contracts/mock-oracle-v1.clar" +clarity_version = 3 +epoch = "3.0" + +[contracts.money-market-core] +path = "contracts/money-market-core.clar" +clarity_version = 3 +epoch = "3.0" + +[repl.analysis] +passes = ["check_checker"] +check_checker = { trusted_sender = false, trusted_caller = false, callee_filter = false } +``` + +### 1.3 Local Testing + +```bash +# Run all tests +clarinet test + +# Run specific test file +clarinet test --match "supply" + +# Run tests with coverage +clarinet test --coverage + +# Run tests with cost analysis +clarinet test --costs +``` + +### 1.4 Local Development Console + +```bash +# Start interactive console +clarinet console + +# In console, deploy contracts +::deploy-contracts + +# Test functions +(stx-transfer? u1000000 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.money-market-core) + +(contract-call? .money-market-core supply-stx u1000000 true) +``` + +--- + +## 2. Testnet Deployment + +### 2.1 Testnet Configuration + +Create `settings/Testnet.toml`: + +```toml +[network] +name = "testnet" +stacks_node_rpc = "https://api.testnet.hiro.so" +bitcoin_node_rpc = "http://blockstack:blockstacksystem@bitcoind.testnet.stacks.co:18332" + +[accounts.deployer] +mnemonic = "your testnet wallet mnemonic phrase here" + +[deployment] +plan = "deployments/default.testnet-plan.yaml" +``` + +### 2.2 Deployment Plan + +The `deployments/default.testnet-plan.yaml` should include: + +```yaml +--- +id: 0 +name: Testnet deployment +network: testnet +stacks-node: "https://api.testnet.hiro.so" +bitcoin-node: "http://blockstack:blockstacksystem@bitcoind.testnet.stacks.co:18332" +plan: + batches: + - id: 0 + transactions: + - contract-publish: + contract-name: mock-oracle-v1 + expected-sender: ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0 + cost: 10520 + path: "contracts/mock-oracle-v1.clar" + anchor-block-only: true + clarity-version: 3 + - contract-publish: + contract-name: mock-sbtc-v1 + expected-sender: ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0 + cost: 25790 + path: "contracts/mock-sbtc-v1.clar" + anchor-block-only: true + clarity-version: 3 + - contract-publish: + contract-name: money-market-core + expected-sender: ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0 + cost: 178460 + path: "contracts/money-market-core.clar" + anchor-block-only: true + clarity-version: 3 + epoch: "3.0" +``` + +### 2.3 Deploy to Testnet + +```bash +# Deploy contracts +clarinet deploy --testnet + +# Verify deployment +clarinet check-contracts --testnet +``` + +### 2.4 Post-Deployment Setup + +After deployment, you need to initialize the contracts: + +```bash +# Initialize oracle prices +clarinet console --testnet + +# In console, set initial prices +(contract-call? .mock-oracle-v1 set-price "STX" u3000000000) // $30 STX +(contract-call? .mock-oracle-v1 set-price "sBTC" u300000000000) // $30,000 sBTC + +# Mint test sBTC (if you're the contract owner) +(contract-call? .mock-sbtc-v1 mint u1000000000 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM) +``` + +### 2.5 Testnet Verification + +```bash +# Verify contract deployment +curl "https://api.testnet.hiro.so/v2/contracts/source/ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0.money-market-core" + +# Check contract state +curl "https://api.testnet.hiro.so/v2/contracts/ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0.money-market-core" +``` + +--- + +## 3. Mainnet Deployment + +### 3.1 Security Preparations + +Before mainnet deployment: + +1. **Security Audit**: Complete third-party security audit +2. **Multi-sig Setup**: Set up multi-signature wallet for contract ownership +3. **Test Coverage**: Ensure >95% test coverage +4. **Stress Testing**: Perform load testing on testnet +5. **Economic Modeling**: Validate economic parameters + +### 3.2 Mainnet Configuration + +Create `settings/Mainnet.toml`: + +```toml +[network] +name = "mainnet" +stacks_node_rpc = "https://api.hiro.so" +bitcoin_node_rpc = "https://bitcoin.mainnet.stacks.co:8332" + +[accounts.deployer] +mnemonic = "your mainnet multi-sig mnemonic phrase here" + +[deployment] +plan = "deployments/default.mainnet-plan.yaml" + +[security] +require_multisig = true +min_signers = 3 +confirmation_blocks = 100 +``` + +### 3.3 Mainnet Deployment Plan + +```yaml +--- +id: 0 +name: Mainnet deployment +network: mainnet +stacks-node: "https://api.hiro.so" +bitcoin-node: "https://bitcoin.mainnet.stacks.co:8332" +plan: + batches: + - id: 0 + transactions: + - contract-publish: + contract-name: price-oracle-v1 + expected-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + cost: 10520 + path: "contracts/price-oracle-v1.clar" + anchor-block-only: true + clarity-version: 3 + - contract-publish: + contract-name: sbtc-token-v1 + expected-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + cost: 25790 + path: "contracts/sbtc-token-v1.clar" + anchor-block-only: true + clarity-version: 3 + - contract-publish: + contract-name: money-market-core + expected-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + cost: 178460 + path: "contracts/money-market-core.clar" + anchor-block-only: true + clarity-version: 3 + epoch: "3.0" +``` + +### 3.4 Mainnet Deployment Steps + +```bash +# 1. Deploy to mainnet (requires multi-sig approval) +clarinet deploy --mainnet + +# 2. Verify deployment +clarinet check-contracts --mainnet + +# 3. Initialize with real price oracle +# 4. Transfer ownership to multi-sig +# 5. Set up monitoring and alerting +``` + +--- + +## 4. Frontend Integration + +### 4.1 Environment Configuration + +Create `.env.local` for frontend: + +```bash +# Stacks Configuration +VITE_STACKS_NETWORK=mainnet +VITE_STACKS_API_URL=https://api.hiro.so +VITE_STACKS_EXPLORER_URL=https://explorer.stacks.co + +# Contract Addresses +VITE_MONEY_MARKET_CORE=ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.money-market-core +VITE_SBTC_TOKEN=ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.sbtc-token-v1 +VITE_PRICE_ORACLE=ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.price-oracle-v1 + +# Backend API +VITE_API_URL=https://api.hayyprotocol.com +VITE_WS_URL=wss://api.hayyprotocol.com/ws +``` + +### 4.2 Stacks.js Integration + +Install required packages: + +```bash +npm install @stacks/connect @stacks/transactions @stacks/auth +``` + +Configuration file (`src/lib/stacks-config.ts`): + +```typescript +import { StacksMainnet, StacksTestnet } from '@stacks/network'; + +const network = import.meta.env.VITE_STACKS_NETWORK === 'mainnet' + ? new StacksMainnet() + : new StacksTestnet(); + +export const stacksConfig = { + network, + appDetails: { + name: 'Hayy Protocol', + icon: '/logo.png', + }, + contractAddresses: { + moneyMarketCore: import.meta.env.VITE_MONEY_MARKET_CORE, + sbtcToken: import.meta.env.VITE_SBTC_TOKEN, + priceOracle: import.meta.env.VITE_PRICE_ORACLE, + }, +}; +``` + +### 4.3 Wallet Connection + +```typescript +// src/lib/wallet.ts +import { connect, UserSession } from '@stacks/connect'; + +export class WalletService { + private userSession: UserSession; + + constructor() { + this.userSession = new UserSession({ + appConfig: { + appDetails: { + name: 'Hayy Protocol', + icon: '/logo.png', + }, + }, + }); + } + + async connectWallet() { + return new Promise((resolve, reject) => { + connect({ + appDetails: { + name: 'Hayy Protocol', + icon: '/logo.png', + }, + onFinish: ({ userSession }) => { + this.userSession = userSession; + resolve(userSession); + }, + onCancel: () => { + reject(new Error('Wallet connection cancelled')); + }, + }); + }); + } + + getAddress() { + return this.userSession.loadUserData().profile.stxAddress.mainnet; + } + + isConnected() { + return this.userSession.isUserSignedIn(); + } +} +``` + +### 4.4 Contract Interaction Service + +```typescript +// src/lib/contract-service.ts +import { + makeContractCall, + callReadOnlyFunction, + cvToJSON, + uintCV, + boolCV, + principalCV, + stringAsciiCV, +} from '@stacks/transactions'; +import { stacksConfig } from './stacks-config'; + +export class ContractService { + async supplySTX(amount: number, useAsCollateral: boolean) { + const tx = await makeContractCall({ + contractAddress: stacksConfig.contractAddresses.moneyMarketCore.split('.')[0], + contractName: stacksConfig.contractAddresses.moneyMarketCore.split('.')[1], + functionName: 'supply-stx', + functionArgs: [ + uintCV(amount), + boolCV(useAsCollateral) + ], + network: stacksConfig.network, + appDetails: stacksConfig.appDetails, + onFinish: (data) => { + console.log('Transaction completed:', data.txId); + }, + }); + + return tx; + } + + async getUserDashboard(userAddress: string) { + const result = await callReadOnlyFunction({ + contractAddress: stacksConfig.contractAddresses.moneyMarketCore.split('.')[0], + contractName: stacksConfig.contractAddresses.moneyMarketCore.split('.')[1], + functionName: 'get-user-dashboard', + functionArgs: [principalCV(userAddress)], + senderAddress: userAddress, + network: stacksConfig.network, + }); + + return cvToJSON(result); + } + + async getProtocolMetrics() { + const result = await callReadOnlyFunction({ + contractAddress: stacksConfig.contractAddresses.moneyMarketCore.split('.')[0], + contractName: stacksConfig.contractAddresses.moneyMarketCore.split('.')[1], + functionName: 'get-protocol-metrics', + functionArgs: [], + senderAddress: stacksConfig.contractAddresses.moneyMarketCore.split('.')[0], + network: stacksConfig.network, + }); + + return cvToJSON(result); + } +} +``` + +### 4.5 React Hooks Integration + +```typescript +// src/hooks/useContractData.ts +import { useState, useEffect } from 'react'; +import { ContractService } from '../lib/contract-service'; + +export function useUserDashboard(address: string) { + const [dashboard, setDashboard] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const contractService = new ContractService(); + + const fetchData = async () => { + try { + setLoading(true); + const data = await contractService.getUserDashboard(address); + setDashboard(data); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + } + }; + + if (address) { + fetchData(); + const interval = setInterval(fetchData, 30000); // Refresh every 30s + return () => clearInterval(interval); + } + }, [address]); + + return { dashboard, loading, error }; +} +``` + +--- + +## 5. Backend Integration + +### 5.1 API Server Setup + +Install dependencies: + +```bash +npm install express cors helmet morgan redis ioredis +npm install @stacks/transactions @stacks/network +``` + +Server setup (`src/server.ts`): + +```typescript +import express from 'express'; +import cors from 'cors'; +import helmet from 'helmet'; +import morgan from 'morgan'; +import Redis from 'ioredis'; +import { StacksMainnet, StacksTestnet } from '@stacks/network'; + +const app = express(); +const redis = new Redis(process.env.REDIS_URL); + +// Middleware +app.use(helmet()); +app.use(cors()); +app.use(morgan('combined')); +app.use(express.json()); + +// Stacks network configuration +const network = process.env.NODE_ENV === 'production' + ? new StacksMainnet() + : new StacksTestnet(); + +// Contract addresses +const contracts = { + moneyMarketCore: process.env.MONEY_MARKET_CORE_ADDRESS, + sbtcToken: process.env.SBTC_TOKEN_ADDRESS, + priceOracle: process.env.PRICE_ORACLE_ADDRESS, +}; + +// API Routes +app.get('/api/protocol/metrics', async (req, res) => { + try { + const cacheKey = 'protocol:metrics'; + const cached = await redis.get(cacheKey); + + if (cached) { + return res.json(JSON.parse(cached)); + } + + const metrics = await getProtocolMetrics(); + await redis.setex(cacheKey, 60, JSON.stringify(metrics)); // Cache for 60s + + res.json(metrics); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +app.get('/api/user/:address/dashboard', async (req, res) => { + try { + const { address } = req.params; + const cacheKey = `user:${address}:dashboard`; + const cached = await redis.get(cacheKey); + + if (cached) { + return res.json(JSON.parse(cached)); + } + + const dashboard = await getUserDashboard(address); + await redis.setex(cacheKey, 30, JSON.stringify(dashboard)); // Cache for 30s + + res.json(dashboard); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +const PORT = process.env.PORT || 3000; +app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`); +}); +``` + +### 5.2 Contract Service for Backend + +```typescript +// src/services/contract-service.ts +import { + callReadOnlyFunction, + cvToJSON, + principalCV, +} from '@stacks/transactions'; +import { network } from '../config/network'; + +export class ContractService { + async getProtocolMetrics() { + const result = await callReadOnlyFunction({ + contractAddress: contracts.moneyMarketCore.split('.')[0], + contractName: contracts.moneyMarketCore.split('.')[1], + functionName: 'get-protocol-metrics', + functionArgs: [], + senderAddress: contracts.moneyMarketCore.split('.')[0], + network, + }); + + return cvToJSON(result); + } + + async getUserDashboard(userAddress: string) { + const result = await callReadOnlyFunction({ + contractAddress: contracts.moneyMarketCore.split('.')[0], + contractName: contracts.moneyMarketCore.split('.')[1], + functionName: 'get-user-dashboard', + functionArgs: [principalCV(userAddress)], + senderAddress: userAddress, + network, + }); + + return cvToJSON(result); + } + + async getUserHealthFactor(userAddress: string) { + const result = await callReadOnlyFunction({ + contractAddress: contracts.moneyMarketCore.split('.')[0], + contractName: contracts.moneyMarketCore.split('.')[1], + functionName: 'get-user-health-factor', + functionArgs: [principalCV(userAddress)], + senderAddress: userAddress, + network, + }); + + return cvToJSON(result); + } +} +``` + +### 5.3 Event Monitoring Service + +```typescript +// src/services/event-monitor.ts +import WebSocket from 'ws'; +import { ContractService } from './contract-service'; + +export class EventMonitor { + private ws: WebSocket; + private contractService: ContractService; + + constructor() { + this.contractService = new ContractService(); + this.ws = new WebSocket('wss://api.hiro.so/v2/ws'); + this.setupEventHandlers(); + } + + private setupEventHandlers() { + this.ws.on('open', () => { + console.log('Connected to Stacks WebSocket'); + this.subscribeToEvents(); + }); + + this.ws.on('message', async (data) => { + const event = JSON.parse(data.toString()); + await this.processEvent(event); + }); + + this.ws.on('error', (error) => { + console.error('WebSocket error:', error); + this.reconnect(); + }); + } + + private subscribeToEvents() { + this.ws.send(JSON.stringify({ + action: 'subscribe', + query: { + type: 'contract_call', + contract_id: contracts.moneyMarketCore, + }, + })); + } + + private async processEvent(event: any) { + if (event.type === 'contract_call') { + const { contract_call } = event; + + switch (contract_call.function_name) { + case 'supply-stx': + case 'supply-sbtc': + await this.handleSupplyEvent(event); + break; + case 'borrow-stx': + case 'borrow-sbtc': + await this.handleBorrowEvent(event); + break; + case 'withdraw-stx': + case 'withdraw-sbtc': + await this.handleWithdrawEvent(event); + break; + case 'repay-stx': + case 'repay-sbtc': + await this.handleRepayEvent(event); + break; + } + } + } + + private async handleSupplyEvent(event: any) { + const userAddress = event.contract_call.sender_address; + + // Update cache + await redis.del(`user:${userAddress}:dashboard`); + + // Send notification + await this.sendNotification(userAddress, 'supply', event); + + // Update analytics + await this.updateAnalytics('supply', event); + } + + private async sendNotification(userAddress: string, type: string, event: any) { + // Implement notification logic (WebSocket, push notification, etc.) + } + + private async updateAnalytics(type: string, event: any) { + // Implement analytics tracking + } + + private reconnect() { + setTimeout(() => { + this.ws = new WebSocket('wss://api.hiro.so/v2/ws'); + this.setupEventHandlers(); + }, 5000); + } +} +``` + +--- + +## 6. Relayer Service Setup + +### 6.1 Relayer Configuration + +Create `relayer/config.toml`: + +```toml +[network] +stacks_node = "https://api.hiro.so" +sui_node = "https://fullnode.mainnet.sui.io:443" + +[relayer] +private_key = "your relayer private key" +stacks_address = "your relayer stacks address" +sui_address = "your relayer sui address" + +[contracts] +stacks_money_market = "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.money-market-core" +sui_borrow_controller = "0x..." + +[monitoring] +check_interval = 5000 # 5 seconds +max_retries = 3 +retry_delay = 1000 +``` + +### 6.2 Relayer Service Implementation + +```typescript +// relayer/src/index.ts +import { RelayerService } from './services/relayer-service'; + +const relayer = new RelayerService(); + +async function main() { + console.log('Starting Hayy Protocol Relayer...'); + + try { + await relayer.start(); + } catch (error) { + console.error('Relayer failed to start:', error); + process.exit(1); + } +} + +main().catch(console.error); +``` + +```typescript +// relayer/src/services/relayer-service.ts +import WebSocket from 'ws'; +import { SuiClient } from '@mysten/sui/client'; + +export class RelayerService { + private stacksWs: WebSocket; + private suiClient: SuiClient; + private isRunning = false; + + constructor() { + this.stacksWs = new WebSocket('wss://api.hiro.so/v2/ws'); + this.suiClient = new SuiClient({ url: 'https://fullnode.mainnet.sui.io:443' }); + } + + async start() { + console.log('Starting relayer service...'); + + // Connect to Stacks WebSocket + await this.connectToStacks(); + + // Start monitoring events + this.startEventMonitoring(); + + this.isRunning = true; + console.log('Relayer service started successfully'); + } + + private async connectToStacks() { + return new Promise((resolve, reject) => { + this.stacksWs.on('open', () => { + console.log('Connected to Stacks WebSocket'); + resolve(); + }); + + this.stacksWs.on('error', reject); + }); + } + + private startEventMonitoring() { + this.stacksWs.on('message', async (data) => { + const event = JSON.parse(data.toString()); + await this.processStacksEvent(event); + }); + + // Subscribe to relevant events + this.subscribeToEvents(); + } + + private async processStacksEvent(event: any) { + if (event.type === 'contract_call') { + const { contract_call } = event; + + if (contract_call.contract_id.includes('money-market-core')) { + switch (contract_call.function_name) { + case 'supply-stx': + case 'supply-sbtc': + await this.handleSupplyEvent(event); + break; + case 'request-withdraw': + await this.handleWithdrawRequest(event); + break; + } + } + } + } + + private async handleSupplyEvent(event: any) { + const { contract_call } = event; + const userAddress = contract_call.sender_address; + const asset = contract_call.function_name.includes('stx') ? 'STX' : 'sBTC'; + const amount = this.extractAmount(contract_call.function_args[0]); + + console.log(`Processing supply event: ${userAddress} supplied ${amount} ${asset}`); + + try { + // Register collateral on Sui + const tx = await this.registerCollateralOnSui(userAddress, asset, amount); + console.log(`Collateral registered on Sui: ${tx}`); + + // Update mapping + await this.updateAddressMapping(userAddress, asset, amount); + } catch (error) { + console.error('Failed to register collateral on Sui:', error); + } + } + + private async handleWithdrawRequest(event: any) { + const { contract_call } = event; + const userAddress = contract_call.sender_address; + const amount = this.extractAmount(contract_call.function_args[0]); + + console.log(`Processing withdraw request: ${userAddress} wants to withdraw ${amount}`); + + try { + // Check user's debt on Sui + const position = await this.getUserPositionOnSui(userAddress); + + if (position.debt === 0) { + // Approve withdrawal on Stacks + await this.approveWithdrawalOnStacks(userAddress, amount); + console.log(`Withdrawal approved for ${userAddress}`); + } else { + console.log(`Withdrawal denied: user has outstanding debt`); + } + } catch (error) { + console.error('Failed to process withdraw request:', error); + } + } + + private async registerCollateralOnSui(userAddress: string, asset: string, amount: number) { + // Implement Sui contract call to register collateral + const tx = await this.suiClient.executeTransaction({ + transactionKind: 'programmableTransaction', + ... // Transaction details + }); + + return tx.digest; + } + + private async getUserPositionOnSui(userAddress: string) { + // Implement Sui contract call to get user position + const result = await this.suiClient.queryEvents({ + query: { MoveEventType: `${this.config.contracts.sui_borrow_controller}::PositionUpdated` }, + }); + + return this.parsePositionFromEvents(result.data, userAddress); + } + + private async approveWithdrawalOnStacks(userAddress: string, amount: number) { + // Implement Stacks contract call to approve withdrawal + // This would be an admin function call + } + + private extractAmount(arg: any): number { + // Parse amount from contract argument + return parseInt(arg.hex, 16); + } + + private subscribeToEvents() { + this.stacksWs.send(JSON.stringify({ + action: 'subscribe', + query: { + type: 'contract_call', + contract_id: this.config.contracts.stacks_money_market, + }, + })); + } +} +``` + +--- + +## 7. Monitoring and Alerting + +### 7.1 Health Check Endpoint + +```typescript +// src/routes/health.ts +import express from 'express'; +import Redis from 'ioredis'; + +const router = express.Router(); + +router.get('/health', async (req, res) => { + const health = { + status: 'ok', + timestamp: new Date().toISOString(), + services: { + api: 'ok', + redis: await checkRedis(), + stacks: await checkStacks(), + sui: await checkSui(), + }, + }; + + const allHealthy = Object.values(health.services).every(status => status === 'ok'); + res.status(allHealthy ? 200 : 503).json(health); +}); + +async function checkRedis(): Promise { + try { + const redis = new Redis(process.env.REDIS_URL); + await redis.ping(); + return 'ok'; + } catch { + return 'error'; + } +} + +async function checkStacks(): Promise { + try { + const response = await fetch('https://api.hiro.so/v2/info'); + return response.ok ? 'ok' : 'error'; + } catch { + return 'error'; + } +} + +async function checkSui(): Promise { + try { + const client = new SuiClient({ url: 'https://fullnode.mainnet.sui.io:443' }); + await client.getLatestSuiSystemState(); + return 'ok'; + } catch { + return 'error'; + } +} + +export default router; +``` + +### 7.2 Prometheus Metrics + +```typescript +// src/metrics.ts +import client from 'prom-client'; + +// Create metrics +const httpRequestDuration = new client.Histogram({ + name: 'http_request_duration_seconds', + help: 'Duration of HTTP requests in seconds', + labelNames: ['method', 'route', 'status'], +}); + +const contractCallsTotal = new client.Counter({ + name: 'contract_calls_total', + help: 'Total number of contract calls', + labelNames: ['contract', 'function', 'status'], +}); + +const protocolTVL = new client.Gauge({ + name: 'protocol_tvl_usd', + help: 'Total Value Locked in USD', +}); + +const activeUsers = new client.Gauge({ + name: 'active_users_total', + help: 'Number of active users', +}); + +export { + httpRequestDuration, + contractCallsTotal, + protocolTVL, + activeUsers, + client, +}; +``` + +### 7.3 Alert Configuration + +```yaml +# prometheus/alerts.yml +groups: + - name: hayy-protocol + rules: + - alert: HighErrorRate + expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.1 + for: 5m + labels: + severity: critical + annotations: + summary: High error rate detected + description: "Error rate is {{ $value }} errors per second" + + - alert: ContractCallFailure + expr: increase(contract_calls_total{status="error"}[5m]) > 10 + for: 2m + labels: + severity: warning + annotations: + summary: Contract call failures detected + description: "Contract calls are failing at rate {{ $value }} per 5 minutes" + + - alert: LowTVL + expr: protocol_tvl_usd < 1000000 + for: 10m + labels: + severity: info + annotations: + summary: TVL below threshold + description: "TVL is ${{ $value }}, below minimum threshold" +``` + +--- + +## 8. Deployment Automation + +### 8.1 GitHub Actions Workflow + +```yaml +# .github/workflows/deploy.yml +name: Deploy Hayy Protocol + +on: + push: + branches: [main, develop] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '18' + - run: npm ci + - run: npm run test + - run: npm run lint + + deploy-testnet: + needs: test + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/develop' + steps: + - uses: actions/checkout@v3 + - name: Deploy to Testnet + run: | + clarinet deploy --testnet + env: + MNEMONIC: ${{ secrets.TESTNET_MNEMONIC }} + + deploy-mainnet: + needs: test + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + steps: + - uses: actions/checkout@v3 + - name: Deploy to Mainnet + run: | + clarinet deploy --mainnet + env: + MNEMONIC: ${{ secrets.MAINNET_MNEMONIC }} +``` + +### 8.2 Docker Configuration + +```dockerfile +# Dockerfile +FROM node:18-alpine + +WORKDIR /app + +COPY package*.json ./ +RUN npm ci --only=production + +COPY . . + +EXPOSE 3000 + +CMD ["npm", "start"] +``` + +```yaml +# docker-compose.yml +version: '3.8' + +services: + api: + build: . + ports: + - "3000:3000" + environment: + - NODE_ENV=production + - REDIS_URL=redis://redis:6379 + depends_on: + - redis + + redis: + image: redis:alpine + ports: + - "6379:6379" + + relayer: + build: ./relayer + environment: + - RELAYER_PRIVATE_KEY=${RELAYER_PRIVATE_KEY} + depends_on: + - api +``` + +--- + +## 9. Troubleshooting + +### 9.1 Common Issues + +**Deployment Fails with "Insufficient STX"** +- Ensure deployer wallet has enough STX for gas fees +- Check gas cost estimates in deployment plan +- Use `clarinet check-spend` to verify costs + +**Contract Calls Fail with "Unauthorized"** +- Verify contract addresses are correct +- Check if function requires admin permissions +- Ensure caller is authorized for the operation + +**Price Oracle Returns Errors** +- Verify oracle contract is deployed and initialized +- Check if prices are set for required assets +- Ensure oracle contract owner is calling set-price + +**Cross-Chain Events Not Processing** +- Verify relayer is running and connected +- Check WebSocket connections to both chains +- Ensure address mapping is correct + +### 9.2 Debug Commands + +```bash +# Check contract deployment +clarinet check-contracts --testnet + +# Verify contract state +curl "https://api.testnet.hiro.so/v2/contracts/CONTRACT_ADDRESS" + +# Check transaction status +curl "https://api.testnet.hiro.so/v2/transactions/TX_ID" + +# Debug contract calls +clarinet console --testnet +``` + +### 9.3 Performance Optimization + +- Use Redis caching for frequently accessed data +- Implement connection pooling for database connections +- Optimize WebSocket subscriptions to reduce bandwidth +- Use CDN for static assets +- Implement rate limiting for API endpoints + +This deployment guide provides comprehensive instructions for deploying Hayy Protocol across all environments and integrating with frontend and backend systems. \ No newline at end of file diff --git a/hayyprotocol-stacks/docs/EXPLORER-TESTING-GUIDE.md b/hayyprotocol-stacks/docs/EXPLORER-TESTING-GUIDE.md new file mode 100644 index 0000000..11ba27d --- /dev/null +++ b/hayyprotocol-stacks/docs/EXPLORER-TESTING-GUIDE.md @@ -0,0 +1,631 @@ +# Hayy Protocol Stacks - Explorer Testing Guide + +## Overview + +This guide provides step-by-step instructions on how to test all contract functions using the Stacks Explorer. Each function includes the exact parameters to use and expected results. + +## Prerequisites + +1. **Stacks Wallet**: Install [Hiro Wallet](https://www.hiro.so/wallet) or compatible Stacks wallet +2. **Testnet STX**: Get testnet STX from the [faucet](https://explorer.stacks.co/txid/ST2F5BKZGK1ZYEQJMFH3WQSVEE7J5P3Z4JQJDQ44V.bridge-v1?chain=testnet) +3. **Explorer Access**: Use [Stacks Explorer](https://explorer.stacks.co/) (testnet) + +## Contract Addresses (Testnet) + +- **Money Market Core**: `ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0.money-market-core` +- **Mock sBTC**: `ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0.mock-sbtc-v1` +- **Mock Oracle**: `ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0.mock-oracle-v1` + +--- + +## 1. Money Market Core Contract Testing + +### 1.1 Supply Functions + +#### Supply STX + +**Function**: `supply-stx` + +**Steps**: +1. Go to [Money Market Core Contract](https://explorer.stacks.co/contract/ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0.money-market-core?chain=testnet) +2. Click "Call Contract" tab +3. Select function: `supply-stx` +4. Enter parameters: + ``` + amount: 1000000 // 1 STX (6 decimals) + use-as-collateral: true + ``` +5. Click "Submit Transaction" +6. Confirm in your wallet + +**Expected Result**: Transaction succeeds, STX transferred from your wallet to contract + +**Verify**: +```clarity +// Call get-user-supply to verify +function: get-user-supply +parameters: + user: [your wallet address] + asset: "STX" +``` + +#### Supply sBTC + +**Function**: `supply-sbtc` + +**Prerequisites**: You need sBTC tokens first (see sBTC Mint section) + +**Steps**: +1. Go to [Money Market Core Contract](https://explorer.stacks.co/contract/ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0.money-market-core?chain=testnet) +2. Click "Call Contract" tab +3. Select function: `supply-sbtc` +4. Enter parameters: + ``` + amount: 100000000 // 1 sBTC (8 decimals) + use-as-collateral: true + ``` +5. Click "Submit Transaction" +6. Confirm in your wallet + +**Expected Result**: Transaction succeeds, sBTC transferred to contract + +### 1.2 Withdraw Functions + +#### Withdraw STX + +**Function**: `withdraw-stx` + +**Steps**: +1. Go to Money Market Core Contract +2. Select function: `withdraw-stx` +3. Enter parameters: + ``` + amount: 500000 // 0.5 STX + ``` +4. Submit transaction + +**Expected Result**: STX transferred back to your wallet + +#### Withdraw sBTC + +**Function**: `withdraw-sbtc` + +**Steps**: +1. Go to Money Market Core Contract +2. Select function: `withdraw-sbtc` +3. Enter parameters: + ``` + amount: 50000000 // 0.5 sBTC + ``` +4. Submit transaction + +**Expected Result**: sBTC transferred back to your wallet + +### 1.3 Borrow Functions + +#### Borrow STX + +**Function**: `borrow-stx` + +**Prerequisites**: Must have collateral enabled and sufficient borrowing power + +**Steps**: +1. Go to Money Market Core Contract +2. Select function: `borrow-stx` +3. Enter parameters: + ``` + amount: 500000 // 0.5 STX + ``` +4. Submit transaction + +**Expected Result**: STX transferred to your wallet + +#### Borrow sBTC + +**Function**: `borrow-sbtc` + +**Steps**: +1. Go to Money Market Core Contract +2. Select function: `borrow-sbtc` +3. Enter parameters: + ``` + amount: 50000000 // 0.5 sBTC + ``` +4. Submit transaction + +**Expected Result**: sBTC transferred to your wallet + +### 1.4 Repay Functions + +#### Repay STX + +**Function**: `repay-stx` + +**Steps**: +1. Go to Money Market Core Contract +2. Select function: `repay-stx` +3. Enter parameters: + ``` + amount: 250000 // 0.25 STX + ``` +4. Submit transaction + +**Expected Result**: Your STX debt decreases + +#### Repay sBTC + +**Function**: `repay-sbtc` + +**Steps**: +1. Go to Money Market Core Contract +2. Select function: `repay-sbtc` +3. Enter parameters: + ``` + amount: 25000000 // 0.25 sBTC + ``` +4. Submit transaction + +**Expected Result**: Your sBTC debt decreases + +### 1.5 Collateral Management + +#### Enable Collateral + +**Function**: `enable-collateral` + +**Steps**: +1. Go to Money Market Core Contract +2. Select function: `enable-collateral` +3. Enter parameters: + ``` + asset: "STX" + ``` +4. Submit transaction + +**Expected Result**: Your STX supply is now enabled as collateral + +#### Disable Collateral + +**Function**: `disable-collateral` + +**Steps**: +1. Go to Money Market Core Contract +2. Select function: `disable-collateral` +3. Enter parameters: + ``` + asset: "STX" + ``` +4. Submit transaction + +**Expected Result**: Your STX supply is no longer used as collateral + +### 1.6 Liquidation + +#### Liquidate Position + +**Function**: `liquidate-sbtc-with-stx` + +**Note**: This requires a position with health factor < 1.0 + +**Steps**: +1. Go to Money Market Core Contract +2. Select function: `liquidate-sbtc-with-stx` +3. Enter parameters: + ``` + user: [borrower's address] + repay-amount: 100000000 // 1 sBTC debt to repay + ``` +4. Submit transaction + +**Expected Result**: You repay sBTC debt and receive sBTC collateral at a discount + +### 1.7 Read-Only Functions + +#### Get User Dashboard + +**Function**: `get-user-dashboard` + +**Steps**: +1. Go to Money Market Core Contract +2. Click "Read Function" tab +3. Select function: `get-user-dashboard` +4. Enter parameters: + ``` + user: [your wallet address] + ``` +5. Click "Call Read-Only Function" + +**Expected Result**: Returns your complete position data including health factor, borrowing power, supplies, and borrows + +#### Get User Health Factor + +**Function**: `get-user-health-factor` + +**Steps**: +1. Go to Money Market Core Contract +2. Click "Read Function" tab +3. Select function: `get-user-health-factor` +4. Enter parameters: + ``` + user: [your wallet address] + ``` +5. Click "Call Read-Only Function" + +**Expected Result**: Returns your health factor (1.0 = 1000000, higher is safer) + +#### Get Borrowing Power + +**Function**: `get-user-borrowing-power` + +**Steps**: +1. Go to Money Market Core Contract +2. Click "Read Function" tab +3. Select function: `get-user-borrowing-power` +4. Enter parameters: + ``` + user: [your wallet address] + ``` +5. Click "Call Read-Only Function" + +**Expected Result**: Returns your maximum borrowing capacity in USD + +#### Get Protocol Metrics + +**Function**: `get-protocol-metrics` + +**Steps**: +1. Go to Money Market Core Contract +2. Click "Read Function" tab +3. Select function: `get-protocol-metrics` +4. Click "Call Read-Only Function" + +**Expected Result**: Returns protocol-wide TVL, total borrows, liquidity, etc. + +#### Get Asset Price + +**Function**: `get-asset-price` + +**Steps**: +1. Go to Money Market Core Contract +2. Click "Read Function" tab +3. Select function: `get-asset-price` +4. Enter parameters: + ``` + asset: "STX" + ``` +5. Click "Call Read-Only Function" + +**Expected Result**: Returns current STX price with 8 decimals + +--- + +## 2. Mock sBTC Token Testing + +### 2.1 Token Information + +**Contract**: `ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0.mock-sbtc-v1` + +### 2.2 Mint sBTC (Contract Owner Only) + +**Function**: `mint` + +**Note**: Only contract owner can mint + +**Steps**: +1. Go to [sBTC Contract](https://explorer.stacks.co/contract/ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0.mock-sbtc-v1?chain=testnet) +2. Click "Call Contract" tab +3. Select function: `mint` +4. Enter parameters: + ``` + amount: 1000000000 // 10 sBTC + recipient: [your wallet address] + ``` +5. Submit transaction + +**Expected Result**: sBTC minted to your address + +### 2.3 Transfer sBTC + +**Function**: `transfer` + +**Steps**: +1. Go to sBTC Contract +2. Select function: `transfer` +3. Enter parameters: + ``` + amount: 100000000 // 1 sBTC + sender: [your wallet address] + recipient: [recipient address] + memo: (optional) + ``` +4. Submit transaction + +**Expected Result**: sBTC transferred to recipient + +### 2.4 Burn sBTC + +**Function**: `burn` + +**Steps**: +1. Go to sBTC Contract +2. Select function: `burn` +3. Enter parameters: + ``` + amount: 100000000 // 1 sBTC + sender: [your wallet address] + ``` +4. Submit transaction + +**Expected Result**: sBTC burned from your balance + +### 2.5 Read-Only Functions + +#### Get Balance + +**Function**: `get-balance` + +**Steps**: +1. Go to sBTC Contract +2. Click "Read Function" tab +3. Select function: `get-balance` +4. Enter parameters: + ``` + account: [your wallet address] + ``` +5. Click "Call Read-Only Function" + +**Expected Result**: Returns your sBTC balance + +#### Get Token Info + +**Functions**: `get-name`, `get-symbol`, `get-decimals`, `get-total-supply` + +**Steps**: +1. Go to sBTC Contract +2. Click "Read Function" tab +3. Select any of the above functions +4. Click "Call Read-Only Function" + +**Expected Results**: +- `get-name`: "sBTC" +- `get-symbol`: "sBTC" +- `get-decimals`: 8 +- `get-total-supply`: Total sBTC in circulation + +--- + +## 3. Mock Oracle Testing + +### 3.1 Set Price (Contract Owner Only) + +**Contract**: `ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0.mock-oracle-v1` + +**Function**: `set-price` + +**Steps**: +1. Go to [Oracle Contract](https://explorer.stacks.co/contract/ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0.mock-oracle-v1?chain=testnet) +2. Click "Call Contract" tab +3. Select function: `set-price` +4. Enter parameters: + ``` + symbol: "STX" + price: 3000000000 // $30 STX price (8 decimals) + ``` +5. Submit transaction + +**Expected Result**: Price updated for STX + +### 3.2 Read-Only Functions + +#### Get Price + +**Function**: `get-price` + +**Steps**: +1. Go to Oracle Contract +2. Click "Read Function" tab +3. Select function: `get-price` +4. Enter parameters: + ``` + symbol: "STX" + ``` +5. Click "Call Read-Only Function" + +**Expected Result**: Returns STX price data with timestamp + +--- + +## 4. Complete Testing Workflow + +### 4.1 Full User Journey Test + +**Step 1: Setup Oracle Prices** +```clarity +// Set STX price to $30 +set-price("STX", 3000000000) + +// Set sBTC price to $30,000 +set-price("sBTC", 300000000000) +``` + +**Step 2: Get sBTC for Testing** +```clarity +// Mint 10 sBTC (if you're contract owner) +mint(1000000000, [your-address]) +``` + +**Step 3: Supply Collateral** +```clarity +// Supply 5 STX as collateral +supply-stx(5000000, true) + +// Supply 1 sBTC as collateral +supply-sbtc(100000000, true) +``` + +**Step 4: Check Position** +```clarity +// Get user dashboard +get-user-dashboard([your-address]) + +// Check borrowing power +get-user-borrowing-power([your-address]) + +// Check health factor +get-user-health-factor([your-address]) +``` + +**Step 5: Borrow Assets** +```clarity +// Borrow 2 STX +borrow-stx(2000000) + +// Borrow 0.5 sBTC +borrow-sbtc(50000000) +``` + +**Step 6: Repay Partially** +```clarity +// Repay 1 STX +repay-stx(1000000) + +// Repay 0.25 sBTC +repay-sbtc(25000000) +``` + +**Step 7: Withdraw Some Collateral** +```clarity +// Withdraw 2 STX +withdraw-stx(2000000) + +// Withdraw 0.5 sBTC +withdraw-sbtc(50000000) +``` + +**Step 8: Final Position Check** +```clarity +// Get final dashboard +get-user-dashboard([your-address]) +``` + +### 4.2 Liquidation Test + +**Prerequisites**: Two accounts needed (borrower and liquidator) + +**Step 1: Setup Borrower Position** +```clarity +// Borrower supplies 1 STX +supply-stx(1000000, true) + +// Borrower borrows maximum sBTC (creates risky position) +borrow-sbtc(20000000) // High borrow against low collateral +``` + +**Step 2: Check Health Factor** +```clarity +// Should be < 1.0 (liquidatable) +get-user-health-factor([borrower-address]) +``` + +**Step 3: Liquidate** +```clarity +// Liquidator repays debt and receives collateral +liquidate-sbtc-with-stx([borrower-address], 10000000) +``` + +**Step 4: Verify Results** +```clarity +// Check both positions after liquidation +get-user-dashboard([borrower-address]) +get-user-dashboard([liquidator-address]) +``` + +--- + +## 5. Common Issues & Troubleshooting + +### 5.1 Transaction Failures + +**Error: `err-insufficient-collateral`** +- Cause: Trying to withdraw more than supplied +- Fix: Check your supply balance first with `get-user-supply` + +**Error: `err-health-factor-too-low`** +- Cause: Withdrawal would make position unsafe +- Fix: Repay some debt first or withdraw less + +**Error: `err-insufficient-liquidity`** +- Cause: Not enough assets available to borrow +- Fix: Wait for more liquidity or borrow less + +**Error: `err-not-liquidatable`** +- Cause: Position health factor >= 1.0 +- Fix: Only liquidate positions with health factor < 1.0 + +### 5.2 Price Issues + +**Oracle returns error** +- Cause: Price not set for asset +- Fix: Contract owner must set price using `set-price` + +### 5.3 Balance Issues + +**sBTC balance shows 0** +- Cause: Haven't received sBTC tokens +- Fix: Mint sBTC (if owner) or receive from someone + +--- + +## 6. Testing Checklist + +### 6.1 Basic Functionality +- [ ] Supply STX +- [ ] Supply sBTC +- [ ] Enable/disable collateral +- [ ] Borrow STX +- [ ] Borrow sBTC +- [ ] Repay STX +- [ ] Repay sBTC +- [ ] Withdraw STX +- [ ] Withdraw sBTC + +### 6.2 Read-Only Functions +- [ ] Get user dashboard +- [ ] Get health factor +- [ ] Get borrowing power +- [ ] Get protocol metrics +- [ ] Get asset prices + +### 6.3 Edge Cases +- [ ] Withdraw with insufficient balance +- [ ] Borrow with insufficient collateral +- [ ] Repay more than owed +- [ ] Liquidation scenario +- [ ] Disable collateral with active borrows + +### 6.4 Admin Functions +- [ ] Set oracle prices +- [ ] Mint sBTC tokens +- [ ] Transfer oracle ownership + +--- + +## 7. Integration Testing + +### 7.1 Frontend Integration + +For frontend integration, focus on these read-only functions for UI updates: +- `get-user-dashboard` - Complete position view +- `get-protocol-metrics` - Global statistics +- `get-asset-price` - Current prices +- `get-available-liquidity` - Available amounts for borrowing + +### 7.2 Backend Integration + +For backend systems: +- Monitor events for real-time updates +- Use `get-user-health-factor` for liquidation monitoring +- Implement retry logic for failed transactions +- Cache read-only function results for performance + +This guide provides comprehensive coverage of all contract functions and their testing procedures using the Stacks Explorer. \ No newline at end of file diff --git a/hayyprotocol-stacks/docs/FRONTEND-BACKEND-INTEGRATION.md b/hayyprotocol-stacks/docs/FRONTEND-BACKEND-INTEGRATION.md new file mode 100644 index 0000000..cbac4dc --- /dev/null +++ b/hayyprotocol-stacks/docs/FRONTEND-BACKEND-INTEGRATION.md @@ -0,0 +1,779 @@ +# Hayy Protocol Stacks - Frontend & Backend Integration Guide + +## Overview + +This guide categorizes all contract functions based on their suitability for frontend (client-side) and backend (server-side) use cases, providing integration patterns and best practices for each. + +## Function Classification + +### 🎨 Frontend-Only Functions +*Direct user interactions that require wallet connection* + +### πŸ“Š Frontend + Backend Functions +*Functions used by both, but with different patterns* + +### βš™οΈ Backend-Only Functions +*Server-side operations, monitoring, and automation* + +### πŸ” Admin Functions +*Privileged operations requiring special permissions* + +--- + +## 1. Frontend-Only Functions + +These functions require direct user interaction and wallet signatures. They should only be called from the frontend. + +### 1.1 Supply Functions + +#### `supply-stx(amount, use-as-collateral)` +- **Use Case**: User deposits STX into the protocol +- **Frontend Integration**: + ```typescript + import { openSTXTransfer } from '@stacks/connect'; + + const supplySTX = async (amount: number, useAsCollateral: boolean) => { + const options = { + contractAddress: 'ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0', + contractName: 'money-market-core', + functionName: 'supply-stx', + functionArgs: [ + contractPrincipalCV(amount), // micro-STX + boolCV(useAsCollateral) + ], + appDetails: { + name: 'Hayy Protocol', + icon: window.location.origin + '/logo.png', + }, + onFinish: (data) => { + console.log('Transaction ID:', data.txId); + // Refresh user data + fetchUserData(); + } + }; + + await openSTXTransfer(options); + }; + ``` + +#### `supply-sbtc(amount, use-as-collateral)` +- **Use Case**: User deposits sBTC into the protocol +- **Frontend Integration**: + ```typescript + const supplySBTC = async (amount: number, useAsCollateral: boolean) => { + const options = { + contractAddress: 'ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0', + contractName: 'money-market-core', + functionName: 'supply-sbtc', + functionArgs: [ + contractPrincipalCV(amount), // sBTC units (8 decimals) + boolCV(useAsCollateral) + ], + // ... same configuration as above + }; + + await openContractCall(options); + }; + ``` + +### 1.2 Withdraw Functions + +#### `withdraw-stx(amount)` +- **Use Case**: User withdraws STX from the protocol +- **Frontend Integration**: + ```typescript + const withdrawSTX = async (amount: number) => { + // Pre-flight check: verify sufficient balance + const balance = await getUserBalance('STX'); + if (balance < amount) { + throw new Error('Insufficient balance'); + } + + const options = { + contractAddress: 'ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0', + contractName: 'money-market-core', + functionName: 'withdraw-stx', + functionArgs: [uintCV(amount)], + // ... configuration + }; + + await openContractCall(options); + }; + ``` + +#### `withdraw-sbtc(amount)` +- **Use Case**: User withdraws sBTC from the protocol +- **Frontend Integration**: Similar to `withdraw-stx` + +### 1.3 Borrow Functions + +#### `borrow-stx(amount)` +- **Use Case**: User borrows STX against their collateral +- **Frontend Integration**: + ```typescript + const borrowSTX = async (amount: number) => { + // Pre-flight checks + const borrowingPower = await getBorrowingPower(userAddress); + const availableLiquidity = await getAvailableLiquidity('STX'); + + if (amount > borrowingPower) { + throw new Error('Insufficient borrowing power'); + } + + if (amount > availableLiquidity) { + throw new Error('Insufficient liquidity'); + } + + const options = { + contractAddress: 'ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0', + contractName: 'money-market-core', + functionName: 'borrow-stx', + functionArgs: [uintCV(amount)], + // ... configuration + }; + + await openContractCall(options); + }; + ``` + +#### `borrow-sbtc(amount)` +- **Use Case**: User borrows sBTC against their collateral +- **Frontend Integration**: Similar to `borrow-stx` + +### 1.4 Repay Functions + +#### `repay-stx(amount)` +- **Use Case**: User repays their STX debt +- **Frontend Integration**: + ```typescript + const repaySTX = async (amount: number) => { + // Get current debt to show user + const currentDebt = await getUserDebt('STX'); + const repayAmount = Math.min(amount, currentDebt); + + const options = { + contractAddress: 'ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0', + contractName: 'money-market-core', + functionName: 'repay-stx', + functionArgs: [uintCV(repayAmount)], + // ... configuration + }; + + await openContractCall(options); + }; + ``` + +#### `repay-sbtc(amount)` +- **Use Case**: User repays their sBTC debt +- **Frontend Integration**: Similar to `repay-stx` + +### 1.5 Collateral Management + +#### `enable-collateral(asset)` / `disable-collateral(asset)` +- **Use Case**: User toggles collateral status for their supplied assets +- **Frontend Integration**: + ```typescript + const toggleCollateral = async (asset: string, enable: boolean) => { + const functionName = enable ? 'enable-collateral' : 'disable-collateral'; + + const options = { + contractAddress: 'ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0', + contractName: 'money-market-core', + functionName, + functionArgs: [stringAsciiCV(asset)], + // ... configuration + }; + + await openContractCall(options); + }; + ``` + +--- + +## 2. Frontend + Backend Functions + +These functions are used by both frontend and backend, but with different patterns and purposes. + +### 2.1 User Position Queries + +#### `get-user-dashboard(user)` +- **Frontend Use**: Display user's complete position in the UI +- **Backend Use**: User profile data, API endpoints, analytics + +**Frontend Integration**: +```typescript +const fetchUserDashboard = async (userAddress: string) => { + const result = await callReadOnlyFunction({ + contractAddress: 'ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0', + contractName: 'money-market-core', + functionName: 'get-user-dashboard', + functionArgs: [principalCV(userAddress)], + senderAddress: userAddress, + }); + + return cvToJSON(result); +}; + +// React hook for real-time updates +const useUserDashboard = (address: string) => { + const [dashboard, setDashboard] = useState(null); + + useEffect(() => { + const fetchData = async () => { + const data = await fetchUserDashboard(address); + setDashboard(data); + }; + + fetchData(); + const interval = setInterval(fetchData, 30000); // Refresh every 30s + + return () => clearInterval(interval); + }, [address]); + + return dashboard; +}; +``` + +**Backend Integration**: +```typescript +// Express.js endpoint +app.get('/api/user/:address/dashboard', async (req, res) => { + try { + const { address } = req.params; + const dashboard = await fetchUserDashboard(address); + + // Cache for 30 seconds + await cache.set(`dashboard:${address}`, dashboard, 30); + + res.json(dashboard); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); +``` + +#### `get-user-health-factor(user)` +- **Frontend Use**: Display health indicator, warnings +- **Backend Use**: Liquidation monitoring, risk assessment + +**Frontend Integration**: +```typescript +const HealthFactorIndicator = ({ userAddress }) => { + const [healthFactor, setHealthFactor] = useState(null); + + useEffect(() => { + const fetchHealthFactor = async () => { + const result = await callReadOnlyFunction({ + contractAddress: 'ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0', + contractName: 'money-market-core', + functionName: 'get-user-health-factor', + functionArgs: [principalCV(userAddress)], + senderAddress: userAddress, + }); + + const hf = cvToValue(result); + setHealthFactor(hf); + }; + + fetchHealthFactor(); + }, [userAddress]); + + if (!healthFactor) return
Loading...
; + + const isHealthy = healthFactor >= 1000000; // 1.0 + const color = isHealthy ? 'green' : healthFactor >= 1100000 ? 'yellow' : 'red'; + + return ( +
+ Health Factor: {(healthFactor / 1000000).toFixed(2)} + {!isHealthy && } +
+ ); +}; +``` + +**Backend Integration**: +```typescript +// Liquidation monitoring service +class LiquidationMonitor { + async checkPositions() { + const atRiskUsers = await this.getAtRiskUsers(); + + for (const user of atRiskUsers) { + const healthFactor = await this.getHealthFactor(user.address); + + if (healthFactor < 1000000) { + await this.notifyLiquidators(user, healthFactor); + await this.logLiquidationOpportunity(user, healthFactor); + } + } + } + + async getHealthFactor(userAddress: string): Promise { + const result = await callReadOnlyFunction({ + contractAddress: 'ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0', + contractName: 'money-market-core', + functionName: 'get-user-health-factor', + functionArgs: [principalCV(userAddress)], + senderAddress: this.backendAddress, + }); + + return cvToValue(result); + } +} +``` + +#### `get-user-borrowing-power(user)` +- **Frontend Use**: Show available borrowing capacity +- **Backend Use**: Risk calculations, lending limits + +### 2.2 Protocol Metrics + +#### `get-protocol-metrics()` +- **Frontend Use**: Display TVL, total borrows on dashboard +- **Backend Use**: Analytics, monitoring, reporting + +**Frontend Integration**: +```typescript +const ProtocolStats = () => { + const [metrics, setMetrics] = useState(null); + + useEffect(() => { + const fetchMetrics = async () => { + const result = await callReadOnlyFunction({ + contractAddress: 'ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0', + contractName: 'money-market-core', + functionName: 'get-protocol-metrics', + functionArgs: [], + senderAddress: 'ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0', + }); + + const data = cvToJSON(result); + setMetrics(data); + }; + + fetchMetrics(); + const interval = setInterval(fetchMetrics, 60000); // Refresh every minute + + return () => clearInterval(interval); + }, []); + + if (!metrics) return
Loading metrics...
; + + return ( +
+ + + +
+ ); +}; +``` + +**Backend Integration**: +```typescript +// Analytics service +class AnalyticsService { + async collectMetrics() { + const metrics = await this.getProtocolMetrics(); + + // Store in time-series database + await this.timeSeriesDB.insert({ + timestamp: new Date(), + tvl: metrics.tvl, + totalBorrows: metrics.total-borrows, + availableLiquidity: metrics.available-liquidity, + reserves: metrics.reserves, + uniqueUsers: metrics.unique-users + }); + + // Trigger alerts if needed + await this.checkThresholds(metrics); + } + + async getHistoricalMetrics(timeRange: string) { + return await this.timeSeriesDB.query({ + timeRange, + metrics: ['tvl', 'total-borrows', 'available-liquidity'] + }); + } +} +``` + +--- + +## 3. Backend-Only Functions + +These functions are primarily used by backend services for monitoring, automation, and operations. + +### 3.1 Liquidation Functions + +#### `liquidate-sbtc-with-stx(user, repay-amount)` +- **Use Case**: Automated liquidation bots +- **Backend Integration**: +```typescript +class LiquidationBot { + private privateKey: string; + private stxAddress: string; + + async liquidatePosition(borrowerAddress: string, repayAmount: number) { + const tx = await makeSTXTokenTransfer({ + recipient: 'ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0.money-market-core', + amount: repayAmount, + memo: Buffer.from('liquidation'), + senderKey: this.privateKey, + network: new StacksTestnet(), + anchorMode: AnchorMode.Any, + postConditionMode: PostConditionMode.Deny, + // Function call for liquidation + sponsored: false, + nonce: await this.getNextNonce(), + }); + + // Add contract call for liquidation + const contractCall = await makeContractCall({ + contractAddress: 'ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0', + contractName: 'money-market-core', + functionName: 'liquidate-sbtc-with-stx', + functionArgs: [ + principalCV(borrowerAddress), + uintCV(repayAmount) + ], + senderKey: this.privateKey, + network: new StacksTestnet(), + anchorMode: AnchorMode.Any, + postConditionMode: PostConditionMode.Deny, + }); + + // Broadcast transaction + const result = await broadcastTransaction(contractCall); + return result; + } + + async runLiquidationLoop() { + while (true) { + try { + const atRiskPositions = await this.findAtRiskPositions(); + + for (const position of atRiskPositions) { + const profitability = await this.calculateProfitability(position); + + if (profitability > this.minProfitThreshold) { + await this.liquidatePosition(position.user, position.debtAmount); + await this.logLiquidation(position, profitability); + } + } + + await this.sleep(10000); // Check every 10 seconds + } catch (error) { + console.error('Liquidation error:', error); + await this.sleep(30000); // Wait longer on error + } + } + } +} +``` + +### 3.2 Event Monitoring + +**Backend Integration**: +```typescript +class EventMonitor { + async startMonitoring() { + const socket = new WebSocket('wss://api.testnet.hiro.so/v2/ws'); + + socket.onmessage = async (event) => { + const data = JSON.parse(event.data); + + if (data.type === 'mempool' || data.type === 'block') { + await this.processEvents(data); + } + }; + } + + async processEvents(data: any) { + for (const tx of data.transactions) { + if (tx.tx_type === 'contract_call') { + await this.handleContractCall(tx); + } + } + } + + async handleContractCall(tx: any) { + const { contract_call } = tx; + + if (contract_call.contract_id.includes('money-market-core')) { + switch (contract_call.function_name) { + case 'supply-stx': + case 'supply-sbtc': + await this.handleSupplyEvent(tx); + break; + case 'borrow-stx': + case 'borrow-sbtc': + await this.handleBorrowEvent(tx); + break; + case 'withdraw-stx': + case 'withdraw-sbtc': + await this.handleWithdrawEvent(tx); + break; + case 'repay-stx': + case 'repay-sbtc': + await this.handleRepayEvent(tx); + break; + } + } + } + + async handleSupplyEvent(tx: any) { + // Update user profile + await this.updateUserProfile(tx.sender_address); + + // Send notification + await this.notificationService.sendSupplyConfirmation(tx); + + // Update analytics + await this.analyticsService.recordSupply(tx); + + // Check for referral rewards + await this.referralService.processSupply(tx); + } +} +``` + +### 3.3 Price Feed Management + +**Backend Integration**: +```typescript +class PriceFeedService { + async updatePrices() { + const prices = await this.fetchPricesFromExchanges(); + + for (const [symbol, price] of Object.entries(prices)) { + await this.updateOraclePrice(symbol, price); + } + } + + private async updateOraclePrice(symbol: string, price: number) { + const priceInPrecision = Math.floor(price * 100000000); // 8 decimals + + const tx = await makeContractCall({ + contractAddress: 'ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0', + contractName: 'mock-oracle-v1', + functionName: 'set-price', + functionArgs: [ + stringAsciiCV(symbol), + uintCV(priceInPrecision) + ], + senderKey: this.oraclePrivateKey, + network: new StacksTestnet(), + }); + + await broadcastTransaction(tx); + await this.logPriceUpdate(symbol, price); + } + + async startPriceFeed() { + // Update prices every 30 seconds + setInterval(() => this.updatePrices(), 30000); + } +} +``` + +--- + +## 4. Admin Functions + +These functions require special permissions and should only be called by authorized administrators. + +### 4.1 Oracle Management + +#### `set-price(symbol, price)` +- **Use Case**: Updating asset prices +- **Access**: Contract owner only +- **Backend Integration**: +```typescript +class OracleAdmin { + private adminKey: string; + + async updatePrice(symbol: string, price: number) { + const tx = await makeContractCall({ + contractAddress: 'ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0', + contractName: 'mock-oracle-v1', + functionName: 'set-price', + functionArgs: [ + stringAsciiCV(symbol), + uintCV(price * 100000000) // 8 decimals + ], + senderKey: this.adminKey, + network: new StacksMainnet(), // Use mainnet for production + }); + + const result = await broadcastTransaction(tx); + + // Log for audit + await this.auditLog.log({ + action: 'price_update', + symbol, + price, + txId: result.txid, + timestamp: new Date(), + admin: this.getAddress() + }); + + return result; + } +} +``` + +### 4.2 Token Management + +#### `mint(amount, recipient)` (sBTC) +- **Use Case**: Minting new sBTC tokens +- **Access**: Contract owner only +- **Backend Integration**: +```typescript +class TokenAdmin { + async mintSBTC(amount: number, recipient: string) { + // Multi-sig approval check + const approval = await this.getMultiSigApproval('mint', amount, recipient); + if (!approval) { + throw new Error('Multi-sig approval required'); + } + + const tx = await makeContractCall({ + contractAddress: 'ST27SK3XEXY9QW51FJHKBR3QEHR5RFDBH3C16RTW0', + contractName: 'mock-sbtc-v1', + functionName: 'mint', + functionArgs: [ + uintCV(amount), + principalCV(recipient) + ], + senderKey: this.adminKey, + network: new StacksMainnet(), + }); + + const result = await broadcastTransaction(tx); + + // Compliance logging + await this.complianceLog.logMint({ + amount, + recipient, + txId: result.txid, + timestamp: new Date(), + approvedBy: approval.approvers + }); + + return result; + } +} +``` + +--- + +## 5. Integration Best Practices + +### 5.1 Frontend Best Practices + +1. **Pre-flight Validation**: Always validate user inputs before transactions +2. **Error Handling**: Provide clear error messages for failed transactions +3. **Loading States**: Show appropriate loading indicators during transactions +4. **Real-time Updates**: Use subscriptions or polling for real-time data +5. **Gas Estimation**: Show transaction costs before confirmation + +```typescript +const useTransactionCost = (functionName: string, args: any[]) => { + const [cost, setCost] = useState(null); + + useEffect(() => { + const estimateCost = async () => { + try { + const estimate = await estimateTransactionCost(functionName, args); + setCost(estimate); + } catch (error) { + console.error('Cost estimation failed:', error); + } + }; + + estimateCost(); + }, [functionName, args]); + + return cost; +}; +``` + +### 5.2 Backend Best Practices + +1. **Caching**: Cache read-only function results to improve performance +2. **Rate Limiting**: Implement rate limiting for API endpoints +3. **Monitoring**: Set up comprehensive monitoring and alerting +4. **Error Recovery**: Implement retry logic with exponential backoff +5. **Security**: Use multi-sig for critical operations + +```typescript +class RobustAPIClient { + async callReadOnlyFunction(fn: string, args: any[], retries = 3): Promise { + for (let i = 0; i < retries; i++) { + try { + const result = await callReadOnlyFunction({ + contractAddress: this.contractAddress, + contractName: this.contractName, + functionName: fn, + functionArgs: args, + senderAddress: this.senderAddress, + }); + + return cvToJSON(result); + } catch (error) { + if (i === retries - 1) throw error; + + const delay = Math.pow(2, i) * 1000; // Exponential backoff + await this.sleep(delay); + } + } + } + + private sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } +} +``` + +### 5.3 Security Considerations + +1. **Input Validation**: Validate all inputs on both frontend and backend +2. **Access Control**: Implement proper access control for admin functions +3. **Audit Logging**: Log all sensitive operations for audit trails +4. **Multi-sig**: Use multi-signature wallets for critical operations +5. **Rate Limiting**: Prevent abuse with appropriate rate limiting + +--- + +## 6. Integration Checklist + +### 6.1 Frontend Integration +- [ ] Wallet connection handling +- [ ] Transaction signing and broadcasting +- [ ] Real-time data updates +- [ ] Error handling and user feedback +- [ ] Loading states and progress indicators +- [ ] Responsive design for mobile/desktop +- [ ] Accessibility compliance + +### 6.2 Backend Integration +- [ ] API rate limiting +- [ ] Caching strategy +- [ ] Error monitoring and alerting +- [ ] Database connection pooling +- [ ] Security middleware +- [ ] API documentation +- [ ] Load testing + +### 6.3 Cross-Chain Integration +- [ ] Relayer service setup +- [ ] Event monitoring across chains +- [ ] State synchronization +- [ ] Failure handling and retries +- [ ] Security audits +- [ ] Performance optimization + +This guide provides comprehensive integration patterns for all contract functions, ensuring secure and efficient frontend and backend implementations. \ No newline at end of file diff --git a/hayyprotocol-stacks/docs/INTEGRATION.md b/hayyprotocol-stacks/docs/INTEGRATION.md deleted file mode 100644 index 812c035..0000000 --- a/hayyprotocol-stacks/docs/INTEGRATION.md +++ /dev/null @@ -1,305 +0,0 @@ -# StackLend Cross-Chain MVP Integration Guide - -This guide shows how to integrate the Clarity contracts from this repo with a simple EVM setup (Base Sepolia) so users can borrow EVM tokens using real STX testnet collateral. - -- On Stacks: STX collateral and positions tracked in Clarity. -- On EVM: mock ERC20s minted/burned by a minimal controller callable by a relayer. -- Interest/APY is informational (fixed bps); no on-chain accrual in MVP. - ---- - -## Contracts overview (Stacks) - -Clarity contracts in `contracts/`: - -- `collateral-v1.clar` - - deposit-collateral(amount) - - withdraw-collateral(amount) β€” blocked while any borrow > 0 (MVP safety) - - request-borrow(token-id, amount, evm-opt) β€” emits event with EVM recipient - - signal-repay(token-id, amount) - - get-collateral(user), get-borrowed(user, token-id), get-borrowed-total(user) - - get-portfolio(user) β€” snapshot for UI - - get-borrow-apy-bps(), get-liquidation-threshold-bps() - - Dynamic token registry (admin only): - - init-admin(), add-token(token-id, chain, apy-bps, liquidity, status) - - set-token-meta(token-id, chain, apy-bps, liquidity, status) - - get-borrow-token-meta(token-id) - -- `lending-v1.clar` - - deposit-lend-collateral(amount) - - withdraw-lend-collateral(amount) - - get-lend-balance(user), get-total-lend(), get-lend-apy-bps() - -Notes -- STX movement uses the Clarity builtin `stx-transfer?` internally. You do not attach STX as `amount` to contract-calls. -- Token IDs are short ASCII strings, e.g., `"USDC"`, `"USDT"`, `"ETH"`. - ---- - -## Frontend integration (Stacks) - -Recommended lib: `@stacks/transactions`. - -- Deposit STX collateral - - function: `deposit-collateral` - - args: `[uintCV(amountUstx)]` - -- Borrow intent (with EVM recipient) - - function: `request-borrow` - - args: `[ - stringAsciiCV(tokenId), - uintCV(borrowAmount), - someCV(bufferCV(Buffer.from(evmAddress.slice(2), 'hex'))) // 20 bytes - ]` - - Event emitted: `{ event: "borrow-request", user, token-id, amount, evm-recipient }` - -- Repay reflection - - function: `signal-repay` - - args: `[stringAsciiCV(tokenId), uintCV(repayAmount)]` - -- Withdraw collateral - - function: `withdraw-collateral` - - args: `[uintCV(amountUstx)]` - - Precondition: `get-borrowed-total(user) == 0` (MVP rule) - -- Lend STX (deposit/withdraw) - - `lending-v1.deposit-lend-collateral(amount)` / `withdraw-lend-collateral(amount)` - -- Read-only for UI - - Totals: `get-total-collateral`, `get-total-borrowed`, `get-total-lend` - - Positions: `get-portfolio(user)` or individual getters - - APY: `get-borrow-apy-bps`, `get-lend-apy-bps` (fixed bps) - - Token meta: `get-borrow-token-meta(token-id)` - ---- - -## Health Factor (off-chain) - -Formula: `HF = (collateralUsd * liqThresholdBps / 10000) / borrowedUsd`. - -Inputs -- `collateralUsd = get-collateral(user) * priceSTXUSD` -- `borrowedUsd = sum(get-borrowed(user, token) * priceTokenUSD)` -- `liqThresholdBps = get-liquidation-threshold-bps()` - -Interpretation: `HF > 1` safe; `HF < 1` risky. No liquidation in MVP. - ---- - -## Borrow market (dynamic) - -Admin flow -1) `init-admin()` once. -2) `add-token("USDC", CHAIN_ETH, 800, 100000000000, STATUS_AVAILABLE)` β€” repeat per token. -3) Update later with `set-token-meta`. - -UI listing -- Keep a small frontend list of supported `token-id`s. -- For each, call `get-borrow-token-meta(token-id)`. - ---- - -## Relayer (Stacks β†’ EVM) β€” minimal setup - -Goal: Mint EVM tokens after a borrow intent on Stacks; later reflect repayment. - -Components -- Stacks API endpoint to watch events. -- Base Sepolia RPC, relayer EVM key (funded test ETH). -- Mapping `token-id` β†’ EVM token address. -- EVM `BorrowController` (below) with RELAYER-only methods. - -Event to watch -- `borrow-request` with fields: `user`, `token-id`, `amount`, `evm-recipient`. - -High-level loop (Node.js) -1) Poll Stacks events for your `collateral-v1` contract. -2) For each `borrow-request` not yet processed: - - Resolve `token-id` β†’ ERC20 address. - - Call EVM controller: `borrow(token, evmRecipient, amount)`. - - Store/log EVM tx hash. - -Repay (MVP) -- User approves controller and transfers allowance, relayer calls `repay(token, from, amount)`. -- Then frontend calls `signal-repay(token-id, amount)` on Stacks. - ---- - -## Solidity (Base Sepolia) - -BorrowController.sol β€” relayer-only mint/burn via allowed tokens - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -interface IMintableBurnableERC20 { - function mint(address to, uint256 amount) external; - function burnFrom(address account, uint256 amount) external; - function allowance(address owner, address spender) external view returns (uint256); -} - -contract BorrowController { - address public owner; - address public relayer; - mapping(address => bool) public allowedToken; - - event RelayerUpdated(address indexed relayer); - event TokenAllowed(address indexed token, bool allowed); - event Borrowed(address indexed token, address indexed to, uint256 amount); - event Repaid(address indexed token, address indexed from, uint256 amount); - - modifier onlyOwner() { - require(msg.sender == owner, "not-owner"); - _; - } - modifier onlyRelayer() { - require(msg.sender == relayer, "not-relayer"); - _; - } - - constructor(address _relayer) { - owner = msg.sender; - relayer = _relayer; - } - - function setRelayer(address _relayer) external onlyOwner { - relayer = _relayer; - emit RelayerUpdated(_relayer); - } - - function setAllowedToken(address token, bool allowed) external onlyOwner { - allowedToken[token] = allowed; - emit TokenAllowed(token, allowed); - } - - function borrow(address token, address to, uint256 amount) external onlyRelayer { - require(allowedToken[token], "token-not-allowed"); - require(to != address(0), "bad-to"); - require(amount > 0, "bad-amount"); - IMintableBurnableERC20(token).mint(to, amount); - emit Borrowed(token, to, amount); - } - - function repay(address token, address from, uint256 amount) external onlyRelayer { - require(allowedToken[token], "token-not-allowed"); - require(from != address(0), "bad-from"); - require(amount > 0, "bad-amount"); - uint256 allowed = IMintableBurnableERC20(token).allowance(from, address(this)); - require(allowed >= amount, "insufficient-allowance"); - IMintableBurnableERC20(token).burnFrom(from, amount); - emit Repaid(token, from, amount); - } -} -``` - -MockERC20.sol β€” simple token with mint/burnFrom - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -contract MockERC20 { - string public name; - string public symbol; - uint8 public immutable decimals; - uint256 public totalSupply; - - mapping(address => uint256) public balanceOf; - mapping(address => mapping(address => uint256)) public allowance; - - event Transfer(address indexed from, address indexed to, uint256 value); - event Approval(address indexed owner, address indexed spender, uint256 value); - - constructor(string memory _name, string memory _symbol, uint8 _decimals) { - name = _name; - symbol = _symbol; - decimals = _decimals; - } - - function approve(address spender, uint256 amount) external returns (bool) { - allowance[msg.sender][spender] = amount; - emit Approval(msg.sender, spender, amount); - return true; - } - - function transfer(address to, uint256 amount) external returns (bool) { - _transfer(msg.sender, to, amount); - return true; - } - - function transferFrom(address from, address to, uint256 amount) external returns (bool) { - uint256 allowed = allowance[from][msg.sender]; - require(allowed >= amount, "allowance"); - if (allowed != type(uint256).max) { - allowance[from][msg.sender] = allowed - amount; - emit Approval(from, msg.sender, allowance[from][msg.sender]); - } - _transfer(from, to, amount); - return true; - } - - function mint(address to, uint256 amount) external { - require(to != address(0), "zero-to"); - totalSupply += amount; - balanceOf[to] += amount; - emit Transfer(address(0), to, amount); - } - - function burnFrom(address from, uint256 amount) external { - uint256 allowed = allowance[from][msg.sender]; - require(allowed >= amount, "allowance"); - if (allowed != type(uint256).max) { - allowance[from][msg.sender] = allowed - amount; - emit Approval(from, msg.sender, allowance[from][msg.sender]); - } - require(balanceOf[from] >= amount, "balance"); - balanceOf[from] -= amount; - totalSupply -= amount; - emit Transfer(from, address(0), amount); - } - - function _transfer(address from, address to, uint256 amount) internal { - require(to != address(0), "zero-to"); - require(balanceOf[from] >= amount, "balance"); - balanceOf[from] -= amount; - balanceOf[to] += amount; - emit Transfer(from, to, amount); - } -} -``` - ---- - -## Try it (sequence) - -1) Stacks testnet: deploy contracts; run `init-admin` and `add-token` for `USDC`, `USDT`, `ETH`. -2) Base Sepolia: deploy `MockERC20` tokens and `BorrowController(relayer)`; allow tokens. -3) Frontend: user deposits STX collateral (`deposit-collateral`). -4) Frontend: user calls `request-borrow("USDC", amount, some(buff20(EVM)))`. -5) Relayer: sees `borrow-request`, calls `borrow(mockUSDC, evmRecipient, amount)` on Base. -6) Repay: user approves controller and repays on Base; frontend calls `signal-repay` on Stacks. -7) Withdraw STX collateral when `get-borrowed-total(user) == 0`. - ---- - -## FAQ - -- What is `stx-transfer?`? - - A Clarity builtin that moves STX: `(stx-transfer? amount sender recipient) -> (response bool uint)`. - - The contracts use it to move STX into/out of the contract safely. - -- Is interest calculated on-chain? - - No (MVP). APY is fixed (bps) for display; balances track principal only. - -- Do I need a relayer? - - For MVP, yes. Later you can upgrade to guardian approvals or a messaging bridge. - ---- - -## Hardening roadmap (optional) - -- Add request IDs and pending/finalized states for borrow/repay. -- Add guardian M-of-N finalize on Stacks for EVM outcomes. -- Integrate a cross-chain messaging bridge or oracle for attestations. -- Introduce on-chain rate indexes if you want on-chain interest accrual later. diff --git a/hayyprotocol-stacks/docs/RELAYER-EXPRESS.md b/hayyprotocol-stacks/docs/RELAYER-EXPRESS.md deleted file mode 100644 index 9fb7dcc..0000000 --- a/hayyprotocol-stacks/docs/RELAYER-EXPRESS.md +++ /dev/null @@ -1,546 +0,0 @@ -# Cross-Chain Relayer with Express.js (Stacks β†’ EVM via viem) - -This guide shows how to build and run a production-friendly relayer using Express.js and viem. It listens for Stacks `borrow-request` events and mints ERC20s on an EVM chain (Base Sepolia) through a `BorrowController`. - -- Why viem: modern, typed, small surface, easy chain presets. Ethers also works; viem is used here by default. -- Trust model (MVP): Relayer is trusted to reflect outcomes on EVM. Upgrade paths: M-of-N guardians, bridge/oracle attestations. - ---- - -## 1) Prerequisites - -- Node.js 18+ (20+ recommended) -- Base Sepolia RPC endpoint (Alchemy/Infura/etc.) -- Relayer EVM private key funded with test ETH -- Stacks Testnet API endpoint (Hiro): `https://stacks-node-api.testnet.stacks.co` -- Deployed contracts: - - Stacks: `collateral-v1` (emits `borrow-request` with `evm-recipient`), `lending-v1` - - EVM: `BorrowController` + mock ERC20s (see `docs/INTEGRATION.md`) - ---- - -## 2) Project scaffold - -Create a new folder for the relayer service (outside your contracts repo or inside `relayer/`). - -```bash -mkdir -p relayer-express/src -cd relayer-express -npm init -y -npm pkg set type=module -npm i express viem dotenv zod cross-fetch pino -npm i -D typescript ts-node @types/node @types/express -npx tsc --init --rootDir src --outDir dist --esModuleInterop true --resolveJsonModule true --module nodenext --moduleResolution nodenext --target es2022 -``` - -Minimal `package.json`: - -```json -{ - "name": "stacklend-relayer-express", - "version": "0.1.0", - "private": true, - "type": "module", - "scripts": { - "dev": "ts-node src/index.ts", - "build": "tsc -p .", - "start": "node dist/index.js" - }, - "dependencies": { - "cross-fetch": "^4.0.0", - "dotenv": "^16.4.5", - "express": "^4.19.2", - "pino": "^9.3.1", - "viem": "^2.16.0", - "zod": "^3.23.8" - }, - "devDependencies": { - "@types/express": "^4.17.21", - "@types/node": "^22.0.0", - "ts-node": "^10.9.2", - "typescript": "^5.5.4" - } -} -``` - -> You can also use JavaScript; TypeScript is recommended for better types with viem. - ---- - -## 3) Environment variables - -Create `.env` in the project root: - -```bash -PORT=8080 -# Stacks -STACKS_API_URL=https://stacks-node-api.testnet.stacks.co -COLLATERAL_CONTRACT_ID=SPxxxx.collateral-v1 -STACKS_CONFIRMATIONS=1 -# EVM -BASE_RPC_URL=https://base-sepolia.example -RELAYER_PRIVATE_KEY=0x... -BORROW_CONTROLLER=0xControllerAddress -# Token-id β†’ ERC20 address mapping (JSON) -TOKEN_MAP={"USDC":"0xUSDC...","USDT":"0xUSDT...","ETH":"0xWETH..."} -# Polling -POLL_INTERVAL_MS=6000 -# Persistence -STATE_FILE=./state.json -``` - -Notes -- `COLLATERAL_CONTRACT_ID` format: `SP...-TESTNET.collateral-v1` on testnet. -- Ensure `RELAYER_PRIVATE_KEY` is hex-prefixed (0x) and funded on Base Sepolia. - ---- - -## 4) ABI for BorrowController - -Paste this ABI into `src/abi/borrowController.json`: - -```json -[ - { - "inputs": [ - { "internalType": "address", "name": "token", "type": "address" }, - { "internalType": "address", "name": "to", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "borrow", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "token", "type": "address" }, - { "internalType": "address", "name": "from", "type": "address" }, - { "internalType": "uint256", "name": "amount", "type": "uint256" } - ], - "name": "repay", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] -``` - ---- - -## 5) Source files - -Create these files under `src/`. - -### 5.1 config.ts - -```ts -import 'dotenv/config'; -import { z } from 'zod'; - -const Env = z.object({ - PORT: z.string().default('8080'), - STACKS_API_URL: z.string().url(), - COLLATERAL_CONTRACT_ID: z.string(), - STACKS_CONFIRMATIONS: z.string().default('1'), - BASE_RPC_URL: z.string().url(), - RELAYER_PRIVATE_KEY: z.string().regex(/^0x[0-9a-fA-F]{64}$/), - BORROW_CONTROLLER: z.string().regex(/^0x[0-9a-fA-F]{40}$/), - TOKEN_MAP: z.string().default('{}'), - POLL_INTERVAL_MS: z.string().default('6000'), - STATE_FILE: z.string().default('./state.json'), -}); - -export const env = Env.parse(process.env); -export const tokenMap: Record = JSON.parse(env.TOKEN_MAP); -``` - -### 5.2 evm.ts (viem clients) - -```ts -import { createPublicClient, createWalletClient, http } from 'viem'; -import { baseSepolia } from 'viem/chains'; -import { privateKeyToAccount } from 'viem/accounts'; -import controllerAbi from './abi/borrowController.json' assert { type: 'json' }; -import { env } from './config'; - -const account = privateKeyToAccount(env.RELAYER_PRIVATE_KEY as `0x${string}`); - -export const publicClient = createPublicClient({ - chain: baseSepolia, - transport: http(env.BASE_RPC_URL), -}); - -export const walletClient = createWalletClient({ - account, - chain: baseSepolia, - transport: http(env.BASE_RPC_URL), -}); - -export async function borrow(token: `0x${string}`, to: `0x${string}`, amount: bigint) { - return walletClient.writeContract({ - address: env.BORROW_CONTROLLER as `0x${string}`, - abi: controllerAbi, - functionName: 'borrow', - args: [token, to, amount], - }); -} - -export async function repay(token: `0x${string}`, from: `0x${string}`, amount: bigint) { - return walletClient.writeContract({ - address: env.BORROW_CONTROLLER as `0x${string}`, - abi: controllerAbi, - functionName: 'repay', - args: [token, from, amount], - }); -} -``` - -### 5.3 state.ts (idempotency store) - -```ts -import fs from 'fs'; -import { env } from './config'; - -export type State = { lastHeight: number; processed: Record; }; - -export function loadState(): State { - try { return JSON.parse(fs.readFileSync(env.STATE_FILE, 'utf8')); } - catch { return { lastHeight: 0, processed: {} }; } -} - -export function saveState(s: State) { - fs.writeFileSync(env.STATE_FILE, JSON.stringify(s, null, 2)); -} -``` - -### 5.4 stacks.ts (event sourcing – concrete via Hiro API) - -```ts -import fetch from 'cross-fetch'; -import { env } from './config'; - -export type BorrowEvent = { - id: string; // idempotency key: txid:index - txid: string; - height: number; - user: string; // Stacks principal - tokenId: string; // e.g., 'USDC' - amount: bigint; // normalize to ERC20 base units externally if needed - evmRecipient: `0x${string}` | null; // 20-byte recipient (hex) -}; - -type TxSummary = { - tx_id: string; - block_height: number; - tx_status: 'success' | string; - tx_type: string; - contract_call?: { contract_id: string }; - sender_address: string; -}; - -type TxDetails = { - tx_id: string; - block_height: number; - events: Array<{ - event_type: string; // e.g., 'print' | 'smart_contract_log' | ... - contract_log?: { value?: { repr?: string } }; - // Some API versions nest under 'print_event' with { value: { repr } } - print_event?: { value?: { repr?: string } }; - }>; -}; - -async function getTipHeight(): Promise { - const r = await fetch(`${env.STACKS_API_URL}/v2/info`); - if (!r.ok) throw new Error(`info failed: ${r.status}`); - const j = await r.json(); - return Number(j?.stacks_tip_height ?? j?.tip_height ?? 0); -} - -function parseBorrowPrintRepr(repr?: string): null | { event: string; tokenId: string; amount: bigint; evmRecipient: `0x${string}` | null; user?: string } { - if (!repr) return null; - // Expect a tuple-like repr: {event: "borrow-request", token-id: "USDC", amount: u1000, evm-recipient: 0xabc..., user: SP...} - if (!repr.includes('borrow-request')) return null; - const getStr = (key: string) => { - const m = repr.match(new RegExp(`${key}[^\S\r\n]*:[^\S\r\n]*"([^"]+)"`)); - return m?.[1] ?? ''; - }; - const getUint = (key: string) => { - const m = repr.match(new RegExp(`${key}[^\S\r\n]*:[^\S\r\n]*u(\d+)`)); - return m?.[1] ? BigInt(m[1]) : 0n; - }; - const getHex = (key: string) => { - const m = repr.match(new RegExp(`${key}[^\S\r\n]*:[^\S\r\n]*(0x[0-9a-fA-F]{40})`)); - return (m?.[1] as `0x${string}` | undefined) ?? null; - }; - return { - event: 'borrow-request', - tokenId: getStr('token-id'), - amount: getUint('amount'), - evmRecipient: getHex('evm-recipient'), - user: getStr('user') || undefined, - }; -} - -async function listContractTxs(limit = 50, offset = 0): Promise { - // Address transactions for the contract principal; filter to calls touching this contract - const url = `${env.STACKS_API_URL}/extended/v1/address/${encodeURIComponent(env.COLLATERAL_CONTRACT_ID)}/transactions?limit=${limit}&offset=${offset}&unanchored=false`; - const r = await fetch(url); - if (!r.ok) throw new Error(`tx list failed: ${r.status}`); - const j = await r.json(); - return (j?.results ?? []) as TxSummary[]; -} - -async function getTx(txid: string): Promise { - const r = await fetch(`${env.STACKS_API_URL}/extended/v1/tx/${txid}?event_limit=200`); - if (!r.ok) throw new Error(`tx ${txid} failed: ${r.status}`); - return (await r.json()) as TxDetails; -} - -export async function fetchBorrowEventsSince(height: number, confirmations = 1): Promise { - const tip = await getTipHeight(); - const minConfirmed = tip - confirmations; - const out: BorrowEvent[] = []; - - // Page through recent transactions for the contract address - let offset = 0; - const pageSize = 50; - while (true) { - const txs = await listContractTxs(pageSize, offset); - if (txs.length === 0) break; - - for (const t of txs) { - // Only confirmed successful contract calls - if (t.tx_status !== 'success') continue; - if (t.block_height == null || t.block_height <= height) continue; - if (t.block_height > minConfirmed) continue; // wait required confirmations - - // Ensure it is a call that touches this contract (defensive) - if (t.tx_type !== 'contract_call') continue; - if (t.contract_call && t.contract_call.contract_id !== env.COLLATERAL_CONTRACT_ID) continue; - - const det = await getTx(t.tx_id); - det.events?.forEach((ev, idx) => { - const repr = ev.print_event?.value?.repr ?? ev.contract_log?.value?.repr; - const parsed = parseBorrowPrintRepr(repr); - if (!parsed) return; - const user = parsed.user || t.sender_address; - out.push({ - id: `${t.tx_id}:${idx}`, - txid: t.tx_id, - height: det.block_height ?? t.block_height, - user, - tokenId: parsed.tokenId, - amount: parsed.amount, - evmRecipient: parsed.evmRecipient, - }); - }); - } - - // Stop if we've paged beyond the confirmation window or far beyond last height - const oldest = txs[txs.length - 1]?.block_height ?? 0; - if (oldest <= height) break; - offset += pageSize; - // Safety cap on pages per poll to avoid long loops - if (offset >= 500) break; - } - - // Sort by height then txid:index for stable processing - out.sort((a, b) => (a.height - b.height) || a.id.localeCompare(b.id)); - return out; -} -``` - -> Notes -> - Endpoints can vary slightly by node version; if `print_event` vs `smart_contract_log` differs, adjust the `repr` extraction accordingly. -> - If your endpoint doesn’t support contract principal in the address tx list, fall back to listing recent blocks or maintain a dedicated indexer. -> - Always key idempotency by `txid:index` and gate by confirmations to avoid reorg issues. - -### 5.5 worker.ts (process events) - -```ts -import { borrow } from './evm'; -import { tokenMap } from './config'; -import type { BorrowEvent } from './stacks'; - -export async function processBorrow(ev: BorrowEvent) { - const token = tokenMap[ev.tokenId]; - if (!token) throw new Error(`Unknown token-id: ${ev.tokenId}`); - if (!ev.evmRecipient) throw new Error('Missing EVM recipient'); - - // NOTE: Ensure amount units match token decimals (e.g., 6 or 18). Convert as needed before calling. - const hash = await borrow(token, ev.evmRecipient, ev.amount); - return hash; // EVM tx hash -} -``` - -### 5.6 server.ts (Express API) - -```ts -import express from 'express'; -import pino from 'pino'; -import { env } from './config'; -import { loadState, saveState } from './state'; -import { fetchBorrowEventsSince } from './stacks'; -import { processBorrow } from './worker'; - -const log = pino({ level: 'info' }); -const app = express(); -app.use(express.json()); - -let state = loadState(); - -app.get('/health', (_req, res) => res.json({ ok: true })); -app.get('/stats', (_req, res) => res.json({ lastHeight: state.lastHeight, processed: Object.keys(state.processed).length })); - -// Optional manual trigger to scan and process now -app.post('/trigger-sync', async (_req, res) => { - try { - const events = await fetchBorrowEventsSince(state.lastHeight, Number(env.STACKS_CONFIRMATIONS)); - const results: Record = {}; - for (const ev of events) { - const key = `borrow:${ev.id}`; - if (state.processed[key]) continue; - const hash = await processBorrow(ev); - state.processed[key] = { hash, t: Date.now() }; - state.lastHeight = Math.max(state.lastHeight, ev.height); - results[key] = hash; - } - saveState(state); - res.json({ ok: true, results }); - } catch (e: any) { - log.error(e, 'trigger-sync failed'); - res.status(500).json({ ok: false, error: e?.message || 'unknown' }); - } -}); - -export function startServer() { - app.listen(Number(env.PORT), () => { - log.info({ port: env.PORT }, 'relayer listening'); - }); -} - -export async function pollLoop() { - const interval = Number(env.POLL_INTERVAL_MS); - const confirmations = Number(env.STACKS_CONFIRMATIONS); - while (true) { - try { - const events = await fetchBorrowEventsSince(state.lastHeight, confirmations); - for (const ev of events) { - const key = `borrow:${ev.id}`; - if (state.processed[key]) continue; - const hash = await processBorrow(ev); - state.processed[key] = { hash, t: Date.now() }; - state.lastHeight = Math.max(state.lastHeight, ev.height); - saveState(state); - } - } catch (e) { - console.error('poll error', e); - } - await new Promise(r => setTimeout(r, interval)); - } -} -``` - -### 5.7 index.ts (entrypoint) - -```ts -import { startServer, pollLoop } from './server'; - -startServer(); -// Fire-and-forget background loop -void pollLoop(); -``` - ---- - -## 6) Running the service - -```bash -npm run dev -# or build & run -npm run build -npm start -``` - -- Health: GET http://localhost:8080/health β†’ `{ ok: true }` -- Stats: GET http://localhost:8080/stats -- Manual sync: POST http://localhost:8080/trigger-sync - -Deploy with PM2 or systemd as in `docs/RELAYER.md`. - ---- - -## 7) Implementing Stacks event fetch - -Depending on your Hiro node/API version, choose one strategy: - -- Contract events endpoint (if available): fetch events for `COLLATERAL_CONTRACT_ID`, filter confirmed height β‰₯ current tip - confirmations, and parse `print` entries where `event == "borrow-request"`. -- Block scan: Get recent blocks from last height, list txs that interact with your contract, and parse events from `/extended/v1/tx/{txid}`. -- Custom indexer or websocket: subscribe to confirmed events and push into your service. - -Parsing prints -- Extract `user`, `token-id`, `amount`, `evm-recipient` (20-byte). Convert buffer to EVM address: `0x...`. -- Build idempotency key as `{txid}:{log_index}` to avoid double processing. -- Set `height` from the confirmed block height. - ---- - -## 8) Repayment path (optional) - -For the MVP: -- User repays on EVM. -- Frontend (or relayer) calls Stacks `signal-repay(token-id, amount)` to reduce principal. - -Later hardening options: guardians, bridge/oracle attestations, or optimistic finalize. - ---- - -## 9) Best practices: viem vs ethers - -- viem (recommended here): modern API, strong types, explicit chain config, easier to tree-shake. Great DX and safety. -- ethers: mature and widely used, great docs and tooling. Still perfectly fine. -- Either works; pick one stack. If your team already uses ethers elsewhere, you can swap `evm.ts` to ethers with minimal changes. - ---- - -## 10) Common pitfalls - -- Recipient type: ensure `evm-recipient` is a 20-byte buffer on Stacks, converted to a checksummed `0x` address. -- Amount units: normalize amounts to match ERC20 decimals (6 vs 18). Consider per-token metadata and conversion. -- Idempotency: always key by `txid:index` and persist in `STATE_FILE`. -- Confirmations: wait 1–2 confirmations to avoid reorgs before executing on EVM. -- Controller config: allowlist tokens and set the relayer role; fund relayer account on Base Sepolia. - ---- - -## 11) Next steps - -- Wire `fetchBorrowEventsSince` to your Stacks API flow and test end-to-end. -- Add metrics (/metrics) and structured logs (Pino) shipped to your observability stack. -- Containerize and deploy behind a small reverse proxy; enable basic auth for write endpoints. -- Extend to support repay events if you later emit them on Stacks. - ---- - -## 12) Ethers alternative (quick swap) - -If you prefer ethers, replace `evm.ts` with: - -```ts -import { JsonRpcProvider, Wallet, Contract } from 'ethers'; -import controllerAbi from './abi/borrowController.json'; -import { env } from './config'; - -const provider = new JsonRpcProvider(env.BASE_RPC_URL); -const wallet = new Wallet(env.RELAYER_PRIVATE_KEY, provider); -const controller = new Contract(env.BORROW_CONTROLLER, controllerAbi, wallet); - -export async function borrow(token: string, to: string, amount: bigint) { - const tx = await controller.borrow(token, to, amount); - const rcpt = await tx.wait(); - return rcpt?.hash ?? tx.hash; -} -``` - -Everything else stays the same. diff --git a/hayyprotocol-stacks/docs/RELAYER.md b/hayyprotocol-stacks/docs/RELAYER.md deleted file mode 100644 index 1537672..0000000 --- a/hayyprotocol-stacks/docs/RELAYER.md +++ /dev/null @@ -1,305 +0,0 @@ -# Cross-Chain Relayer Setup Guide (Stacks ↔ EVM) - -This guide walks you through building a minimal, reliable relayer so users can borrow EVM tokens using real STX testnet collateral. - -- Stacks (testnet): Clarity contracts track STX collateral and debt principal. -- EVM (Base Sepolia): Mock ERC20s minted/burned by a BorrowController with relayer-only methods. -- Relayer (Node.js): Listens to Stacks events and submits corresponding EVM txs. - -> MVP trust model: The relayer is trusted to reflect EVM outcomes. You can later upgrade to guardians (M-of-N) or a bridge/oracle. - ---- - -## 1) Architecture - -- User deposits STX collateral on Stacks: `deposit-collateral`. -- User signals borrow: `request-borrow(token-id, amount, some(evmRecipient20))`. - - Contract emits: `borrow-request { user, token-id, amount, evm-recipient }`. -- Relayer sees the event and calls EVM `BorrowController.borrow(token, recipient, amount)`. -- Repay (MVP): user repays on EVM; frontend (or user) calls `signal-repay(token-id, amount)` on Stacks to reduce principal. - ---- - -## 2) Prerequisites - -- Node.js v18+ (v20 recommended) -- Base Sepolia RPC endpoint (Alchemy/Infura/Ankr/etc.) -- Relayer EVM key with test ETH -- Stacks Testnet API endpoint (Hiro): e.g., `https://stacks-node-api.testnet.stacks.co` -- Deployed contracts: - - Stacks: `collateral-v1`, `lending-v1` - - Base Sepolia: `BorrowController`, mock ERC20 tokens (USDC, USDT, WETH) - ---- - -## 3) Configuration - -Create a `.env` file for the relayer: - -``` -# Stacks -STACKS_API_URL=https://stacks-node-api.testnet.stacks.co -COLLATERAL_CONTRACT_ID=SPxxxxxxx.collateral-v1 -NETWORK=mainnet:testnet # use testnet - -# EVM (Base Sepolia) -BASE_RPC_URL=https://base-sepolia.example -RELAYER_PRIVATE_KEY=0x... -BORROW_CONTROLLER=0xControllerAddress - -# Token mapping (JSON string): token-id β†’ ERC20 address -TOKEN_MAP={"USDC":"0xUSDC...","USDT":"0xUSDT...","ETH":"0xWETH..."} - -# Relayer behavior -POLL_INTERVAL_MS=6000 -CONFIRMATIONS=1 -STATE_DB=./relayer-state.json -``` - -> CONTRACT_ID format is `SP...-TESTNET.contract-name` on testnet. Confirm your deployed IDs. - ---- - -## 4) EVM contracts - -You’ll need the `BorrowController` and mock tokens. See `docs/INTEGRATION.md` for full source. - -Controller ABI fragment (paste into the relayer): - -```json -[ - {"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"borrow","outputs":[],"stateMutability":"nonpayable","type":"function"}, - {"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"from","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"repay","outputs":[],"stateMutability":"nonpayable","type":"function"} -] -``` - ---- - -## 5) Project bootstrap - -Initialize a minimal Node project (local or on a small VM): - -- Dependencies: `ethers`, `cross-fetch` -- Optional: `dotenv`, `pino` or `winston` for logging - -Example `package.json`: - -```json -{ - "name": "stacklend-relayer", - "version": "0.1.0", - "type": "module", - "private": true, - "scripts": { - "start": "node src/index.js" - }, - "dependencies": { - "cross-fetch": "^4.0.0", - "dotenv": "^16.4.5", - "ethers": "^6.12.0" - } -} -``` - ---- - -## 6) Relayer logic (template) - -Create `src/index.js`: - -```js -import 'dotenv/config'; -import fetch from 'cross-fetch'; -import { ethers } from 'ethers'; -import fs from 'fs'; - -const { - STACKS_API_URL, - COLLATERAL_CONTRACT_ID, - BASE_RPC_URL, - RELAYER_PRIVATE_KEY, - BORROW_CONTROLLER, - TOKEN_MAP, - POLL_INTERVAL_MS = '6000', - CONFIRMATIONS = '1', - STATE_DB = './relayer-state.json', -} = process.env; - -const tokenMap = JSON.parse(TOKEN_MAP || '{}'); - -// EVM setup -const provider = new ethers.JsonRpcProvider(BASE_RPC_URL); -const wallet = new ethers.Wallet(RELAYER_PRIVATE_KEY, provider); -const controllerAbi = [ - { - name: 'borrow', - type: 'function', - stateMutability: 'nonpayable', - inputs: [ - { name: 'token', type: 'address' }, - { name: 'to', type: 'address' }, - { name: 'amount', type: 'uint256' }, - ], - outputs: [], - }, - { - name: 'repay', - type: 'function', - stateMutability: 'nonpayable', - inputs: [ - { name: 'token', type: 'address' }, - { name: 'from', type: 'address' }, - { name: 'amount', type: 'uint256' }, - ], - outputs: [], - }, -]; -const controller = new ethers.Contract(BORROW_CONTROLLER, controllerAbi, wallet); - -// Simple idempotency store -function loadState() { - try { return JSON.parse(fs.readFileSync(STATE_DB, 'utf8')); } catch { return { lastBlock: 0, processed: {} }; } -} -function saveState(s) { fs.writeFileSync(STATE_DB, JSON.stringify(s, null, 2)); } -let state = loadState(); - -// Fetch recent confirmed transactions for a contract and extract borrow-request prints -async function fetchBorrowEvents(sinceBlock) { - // Note: Stacks API offers multiple endpoints to fetch contract tx logs. - // Use your preferred event source (confirmed tx log stream or websocket). - // Below is a generic placeholder that should be adapted to your API client. - const url = `${STACKS_API_URL}/extended/v1/tx?limit=50&offset=0`; // Replace with contract-scoped events in your client - const res = await fetch(url); - if (!res.ok) throw new Error(`Stacks API error: ${res.status}`); - const data = await res.json(); - - // TODO: Filter to txs calling COLLATERAL_CONTRACT_ID::request-borrow - // TODO: Parse print events with event: "borrow-request" - // Return an array of { id, tokenId, amount, evmRecipient, user } - return []; -} - -function hexBuff20ToAddress(buff) { - // buff should be a 20-byte hex like 0xdead...; ensure it maps to a valid EVM address - if (!buff || buff === '0x0000000000000000000000000000000000000000') return null; - return ethers.getAddress(buff); -} - -async function processBorrow(ev) { - const key = `borrow:${ev.id}`; - if (state.processed[key]) return; - const token = tokenMap[ev.tokenId]; - if (!token) throw new Error(`Unknown token-id: ${ev.tokenId}`); - const to = hexBuff20ToAddress(ev.evmRecipient); - if (!to) throw new Error('Missing EVM recipient'); - const amount = ev.amount; // Ensure unit alignment with ERC20 decimals - const tx = await controller.borrow(token, to, amount); - const rcpt = await tx.wait(); - console.log('[EVM] borrow sent', { hash: tx.hash, gasUsed: rcpt.gasUsed?.toString?.() }); - state.processed[key] = { hash: tx.hash, t: Date.now() }; - saveState(state); -} - -async function loop() { - try { - const events = await fetchBorrowEvents(state.lastBlock); - for (const ev of events) await processBorrow(ev); - } catch (e) { - console.error('loop error', e); - } finally { - setTimeout(loop, Number(POLL_INTERVAL_MS)); - } -} - -console.log('Relayer starting...'); -loop(); -``` - -> IMPORTANT: Implement `fetchBorrowEvents` using your preferred Stacks API method to read contract events and filter for `borrow-request`. Keep a robust idempotency key (e.g., concat of txid + log index) to avoid double-processing. - ---- - -## 7) Event sourcing on Stacks (options) - -You can source events via: - -- Polling confirmed tx logs for your contract and parsing `print` events -- Websocket subscriptions (if available) to new confirmed events -- Indexer service (recommended for production) that stores events in a DB - -Tips -- Maintain a `lastBlock` or `lastHeight` checkpoint. -- Derive an idempotency key like `{txid}:{event_index}`. -- Only process after N confirmations (see `CONFIRMATIONS`). - ---- - -## 8) Repayment path (MVP) - -- User repays on EVM: `approve(BorrowController, amount)` then off-chain repayment via controller (optional). -- Frontend (or user) calls Stacks: `signal-repay(token-id, amount)` to reduce principal. -- Collateral withdraw enabled once `get-borrowed-total(user) == 0`. - -> To enforce EVM repayment proofs on Stacks, add a guardian committee or bridge/oracle later. - ---- - -## 9) Deployment options - -- PM2: `pm2 start src/index.js --name stacklend-relayer` -- systemd unit (example): - -```ini -[Unit] -Description=StackLend Relayer -After=network.target - -[Service] -Type=simple -WorkingDirectory=/opt/stacklend-relayer -Environment=NODE_ENV=production -ExecStart=/usr/bin/node src/index.js -Restart=always -RestartSec=5 - -[Install] -WantedBy=multi-user.target -``` - -- Docker (optional): - - Build a simple image with node:20-alpine - - Mount `.env` and `relayer-state.json` - ---- - -## 10) Security & reliability - -- Use a dedicated relayer key with limited funds; rotate regularly. -- Restrict `BorrowController` to a RELAYER role. -- Enforce an allowlist of tokens in the controller (`setAllowedToken`). -- Add alerts for failures and retries with exponential backoff. -- Use per-token decimals consistently; normalize amounts across chains. -- Log the Stacks txid and EVM tx hash for each processed request. - ---- - -## 11) Troubleshooting - -- No events seen: - - Verify `COLLATERAL_CONTRACT_ID` and the Stacks API endpoint. - - Ensure `request-borrow` transactions are confirmed (not just mempool). -- EVM tx fails: - - Check relayer wallet balance (Base Sepolia test ETH). - - Confirm `allowedToken[token] == true` in the controller. - - Ensure the `evm-recipient` is a valid 20-byte address. -- Amount mismatch: - - Align units (USTX vs ERC20 decimals). Consider a conversion layer in the relayer. - ---- - -## 12) Roadmap upgrades (optional) - -- Add request IDs and pending/finalized states on Stacks to improve UX without a relayer trust change. -- Guardian M-of-N finalization on Stacks for EVM outcomes. -- Bridge/oracle attestation to avoid trusting the relayer for outcome truth. -- On-chain interest accrual with rate indexes if needed later. diff --git a/hayyprotocol-stacks/docs/SETUP-STACKS.md b/hayyprotocol-stacks/docs/SETUP-STACKS.md deleted file mode 100644 index b6ba88c..0000000 --- a/hayyprotocol-stacks/docs/SETUP-STACKS.md +++ /dev/null @@ -1,168 +0,0 @@ -# Stacks Contracts Setup & Configuration (collateral-v1, lending-v1) - -This guide covers post-deployment setup for the Stacks contracts so you can start demoing deposits, borrowing, and withdrawals. It assumes both contracts are deployed to Stacks Testnet and you know their contract IDs. - -- Collateral: tracks STX collateral and borrow principals, dynamic token registry, and emits borrow/repay events -- Lending: simple STX pool for supply/withdraw with fixed APY exposure (no on-chain interest accrual in MVP) - -Refer to: -- Cross-chain relayer guides: `docs/RELAYER.md` and `docs/RELAYER-EXPRESS.md` -- Frontend + Solidity notes: `docs/INTEGRATION.md` - ---- - -## 1) Prerequisites - -- Stacks Testnet node API (Hiro): `https://stacks-node-api.testnet.stacks.co` -- Contract IDs: - - COLLATERAL_CONTRACT_ID: `SP...-TESTNET.collateral-v1` - - LENDING_CONTRACT_ID: `SP...-TESTNET.lending-v1` -- A testnet wallet funded with STX for admin and user ops -- (Optional) Your EVM addresses for token recipients - -Conventions used below -- ustx = microstacks (1 STX = 1_000_000 ustx) -- APY/threshold values are in basis points (bps). Example: 800 bps = 8.00% - ---- - -## 2) Initialize admin (collateral-v1) - -The collateral contract exposes an admin initializer. Run it once with your admin principal. - -Inputs -- admin: principal (Stacks address) - -Actions -1) Call `init-admin(admin)` from the admin wallet. -2) Verify with a read-only `is-admin(admin)` if available in your version. - -Notes -- Only call once; subsequent calls should be blocked by the contract. - ---- - -## 3) Configure the Borrow Market (dynamic token registry) - -Register each borrowable token via `add-token`. You can update metadata later with `set-token-meta`. - -Function shapes (conceptual) -- `add-token(token-id: string, chain: uint, apy-bps: uint, liquidity: uint, status: uint)` -- `set-token-meta(token-id: string, chain: uint, apy-bps: uint, liquidity: uint, status: uint)` - -Fields -- token-id: short string identifier, e.g., "USDC", "USDT", "ETH" -- chain: numeric enum for the chain (e.g., EVM). Use values defined in your contract -- apy-bps: displayed APY in basis points for this market (principal accrual is off-chain in MVP) -- liquidity: hint for UI depth/limits (uint) -- status: numeric enum, e.g., Active / Paused / ComingSoon (use your contract’s values) - -Example (conceptual values) -- USDC on EVM: `{ token-id: "USDC", chain: , apy-bps: u800, liquidity: u1000000000000000000, status: }` -- USDT on EVM: `{ token-id: "USDT", chain: , apy-bps: u900, liquidity: u500000000000000000, status: }` -- ETH (WETH) on EVM: `{ token-id: "ETH", chain: , apy-bps: u700, liquidity: u200000000000000000, status: }` - -Verification (read-onlys) -- `is-supported-token(token-id)` β†’ bool -- `get-borrow-token-meta(token-id)` β†’ returns { chain, apy-bps, liquidity, status } -- `token-code-of(token-id)` β†’ internal numeric code used as borrow key - -Caveats -- Admin functions accept unchecked data in MVP; ensure only a trusted admin can call them. - ---- - -## 4) Lending pool setup (lending-v1) - -This contract exposes a fixed lending APY and tracks user balances of STX supplied to the pool. - -Useful read-onlys -- `get-lend-apy-bps()` β†’ e.g., u500 (5.00%) -- `get-lend-balance(user: principal)` -- `get-total-lend()` - -User actions -- `deposit-lend-collateral(amount: uint)` β†’ transfer STX in -- `withdraw-lend-collateral(amount: uint)` β†’ transfer STX out (must not exceed balance) - -Note -- APY is fixed by constant in the MVP; no on-chain interest accrual. - ---- - -## 5) Collateral deposit/withdraw (collateral-v1) - -User actions -- `deposit-collateral(amount: uint)` β†’ transfer STX in and credit user collateral -- `withdraw-collateral(amount: uint)` β†’ transfer STX out; requires no outstanding borrow (principal must be zero) - -Useful read-onlys -- `get-collateral(user)` β†’ ustx -- `get-total-collateral()` -- `get-borrowed(user, token-id)` -- `get-borrowed-total(user)` -- `get-liquidation-threshold-bps()` - ---- - -## 6) Borrow and repay (principal-only, MVP) - -Borrow request -- `request-borrow(token-id: string, amount: uint, evm-recipient?: (buff 20) optional)` -- Emits a `borrow-request` event including `user`, `token-id`, `amount`, and `evm-recipient` (zero address if none) -- In relayer mode, your off-chain service mints the corresponding ERC20 on EVM to `evm-recipient` - -Repay signal -- `signal-repay(token-id: string, amount: uint)` -- Decreases principal by `amount`; emits a `repay-signal` event - -Frontend tips -- Convert EVM recipient to a 20-byte buffer (buff20) when calling `request-borrow` -- Keep token decimals aligned between Stacks display and EVM mint amounts (e.g., 6 vs 18 decimals) - ---- - -## 7) Health Factor (off-chain) - -Compute HF in the frontend using your chosen oracle/prices. The contract provides a liquidation threshold (bps). Keep the UI conservative and block risky borrows client-side. - -Read-only helpers -- `get-liquidation-threshold-bps()` -- Token meta via `get-borrow-token-meta` for APY display and status - ---- - -## 8) Suggested staging flow (for frontend) - -- Stage 1 (Stacks): - 1) `deposit-collateral(ustx)` β†’ wait 1–2 confirmations - 2) `request-borrow(token-id, amount, some(buff20 evmRecipient))` β†’ wait 1–2 confirmations -- Stage 2 (EVM): - - Relayer mints tokens to `evmRecipient`; UI observes EVM balance increase - - Repay path: user approves and repays on EVM; then call `signal-repay` on Stacks - ---- - -## 9) Quick sanity checks - -- After admin setup: `is-supported-token("USDC")` returns true -- After user deposit: `get-collateral(user)` increases by `amount` -- After borrow request: `get-borrowed(user, "USDC")` increases by `amount` -- After repay signal: `get-borrowed(user, "USDC")` decreases -- Withdraw only works when `get-borrowed-total(user) == 0` - ---- - -## 10) Troubleshooting - -- Function not found / wrong types: Confirm contract IDs and function parameter types (uint vs buff20 vs string) -- Withdraw blocked: Ensure `get-borrowed-total(user) == 0` -- EVM mint not seen: Check relayer logs and token allowlist; confirm `evm-recipient` encoded as buff20 -- Decimals mismatch: Normalize UI input to ERC20 base units and ustx separately - ---- - -## 11) Where next - -- Relayer setup: `docs/RELAYER-EXPRESS.md` (Express + viem) or `docs/RELAYER.md` (minimal Node) -- Frontend guidance + Solidity references: `docs/INTEGRATION.md` diff --git a/hayyprotocol-stacks/docs/TROUBLESHOOTING-FAQ.md b/hayyprotocol-stacks/docs/TROUBLESHOOTING-FAQ.md new file mode 100644 index 0000000..d6ead1d --- /dev/null +++ b/hayyprotocol-stacks/docs/TROUBLESHOOTING-FAQ.md @@ -0,0 +1,617 @@ +# Hayy Protocol Stacks - Troubleshooting & FAQ + +## Table of Contents + +1. [Common Issues](#common-issues) +2. [Development Issues](#development-issues) +3. [Deployment Issues](#deployment-issues) +4. [Frontend Integration Issues](#frontend-integration-issues) +5. [Backend Integration Issues](#backend-integration-issues) +6. [Cross-Chain Issues](#cross-chain-issues) +7. [Performance Issues](#performance-issues) +8. [Security Issues](#security-issues) +9. [Frequently Asked Questions](#frequently-asked-questions) + +--- + +## Common Issues + +### Transaction Failures + +#### Q: Why is my transaction failing with "err-insufficient-funds"? + +**A**: This error occurs when you don't have enough balance for the operation. + +**Solutions**: +1. Check your actual balance: + ```bash + stx balance ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + ``` +2. Ensure you have enough for gas fees (typically 0.05-0.1 STX) +3. For supply operations, verify you have the amount you're trying to supply +4. For borrow operations, check if you have sufficient collateral + +#### Q: Why am I getting "err-health-factor-too-low"? + +**A**: Your health factor is below the minimum threshold (1.0). + +**Solutions**: +1. Supply more collateral +2. Repay some of your debt +3. Wait for asset prices to improve (if applicable) +4. Check your current health factor: + ```clarity + (contract-call? .money-market-core get-user-health-factor 'YOUR_ADDRESS) + ``` + +#### Q: Transaction is stuck in mempool + +**A**: Transactions can get stuck due to network congestion or low gas fees. + +**Solutions**: +1. Wait longer (network may be congested) +2. Check transaction status on [Stacks Explorer](https://explorer.stacks.co/) +3. If stuck for >30 minutes, consider resubmitting with higher fees +4. Use `clarinet check-mempool` to see pending transactions + +--- + +## Development Issues + +### Clarinet Issues + +#### Q: Clarinet commands are not found + +**A**: Clarinet is not installed or not in your PATH. + +**Solutions**: +1. Install Clarinet: + ```bash + curl -L https://github.com/hirosystems/clarinet/releases/latest/download/clarinet-linux-x64.tar.gz | tar xz + sudo mv clarinet-linux-x64/clarinet /usr/local/bin/ + ``` +2. Verify installation: + ```bash + clarinet --version + ``` +3. Add to PATH if needed: + ```bash + export PATH=$PATH:/path/to/clarinet + ``` + +#### Q: Tests are failing with "contract not found" + +**A**: Contracts are not deployed in the test environment. + +**Solutions**: +1. Ensure contracts are defined in `Clarinet.toml` +2. Run `clarinet console` and type `::deploy-contracts` +3. Check contract names match between `.toml` and test files +4. Verify contract paths are correct + +#### Q: Getting "epoch mismatch" errors + +**A**: Your contracts are using a different epoch version than the network. + +**Solutions**: +1. Update `Clarinet.toml` to use epoch "3.0": + ```toml + [contracts.your-contract] + clarity_version = 3 + epoch = "3.0" + ``` +2. Redeploy contracts with correct epoch +3. Ensure all contracts use the same epoch version + +### Code Issues + +#### Q: Integer overflow/underflow errors + +**A**: Clarity automatically handles integer overflow, but logic errors can occur. + +**Solutions**: +1. Use proper arithmetic checks: + ```clarity + (asserts! (> amount u0) err-invalid-amount) + (asserts! (<= amount max-uint) err-amount-too-large) + ``` +2. Use `try!` for operations that might fail +3. Test edge cases with maximum values + +#### Q: Map lookups returning unexpected values + +**A**: Map might not have the key or returning default values. + +**Solutions**: +1. Use `map-get?` for optional lookups +2. Use `default-to` with appropriate defaults: + ```clarity + (default-to { amount: u0 } (map-get? user-balances { user: caller })) + ``` +3. Verify key types match map definition + +--- + +## Deployment Issues + +### Testnet Deployment + +#### Q: Deployment fails with "insufficient balance" + +**A**: Deployer wallet doesn't have enough STX for deployment costs. + +**Solutions**: +1. Get testnet STX from [faucet](https://explorer.stacks.co/txid/ST2F5BKZGK1ZYEQJMFH3WQSVEE7J5P3Z4JQJDQ44V.bridge-v1?chain=testnet) +2. Check deployment costs: + ```bash + clarinet check-spend --testnet + ``` +3. Ensure wallet has at least 2x the estimated cost for safety + +#### Q: Contract deployment times out + +**A**: Network congestion or anchor block issues. + +**Solutions**: +1. Use `anchor-block-only: true` in deployment plan +2. Wait for next anchor block (every 10 blocks on testnet) +3. Check network status on [Stacks Explorer](https://explorer.stacks.co/) +4. Try deploying during off-peak hours + +#### Q: Contract address doesn't match expected + +**A**: Deployer address is different than expected. + +**Solutions**: +1. Verify deployer address in deployment plan +2. Update `expected-sender` field to match your wallet +3. Use current wallet address: + ```bash + stx address + ``` + +### Mainnet Deployment + +#### Q: Multi-sig transaction not working + +**A**: Multi-sig setup requires proper coordination. + +**Solutions**: +1. Ensure all signers have the contract deployment transaction +2. Verify signature threshold is met +3. Use proper multi-sig wallet (e.g., Leather, Xverse) +4. Test multi-sig process on testnet first + +#### Q: Gas fees too high + +**A**: Mainnet gas fees can be significant during congestion. + +**Solutions**: +1. Monitor gas prices before deployment +2. Consider deploying during off-peak hours +3. Optimize contract code to reduce size +4. Batch multiple functions if possible + +--- + +## Frontend Integration Issues + +### Wallet Connection + +#### Q: Wallet connection fails + +**A**: Various issues with wallet provider or configuration. + +**Solutions**: +1. Ensure wallet is installed and unlocked +2. Check wallet network (testnet/mainnet matches) +3. Clear browser cache and cookies +4. Try different browser +5. Check wallet permissions for the site + +#### Q: Transaction signing not working + +**A**: Wallet not properly configured or transaction malformed. + +**Solutions**: +1. Verify transaction arguments are correct +2. Check contract addresses match network +3. Ensure function arguments have correct types +4. Test with small amounts first +5. Check wallet console for error messages + +### Data Display + +#### Q: User data not loading + +**A**: API calls failing or returning errors. + +**Solutions**: +1. Check network connectivity +2. Verify API endpoints are accessible +3. Check browser console for CORS errors +4. Verify contract addresses are correct +5. Test API calls directly with curl + +#### Q: Real-time updates not working + +**A**: WebSocket connection issues or event handling problems. + +**Solutions**: +1. Check WebSocket connection status +2. Verify event subscription filters +3. Check browser console for WebSocket errors +4. Ensure event parsing logic is correct +5. Test with manual API polling as fallback + +--- + +## Backend Integration Issues + +### API Issues + +#### Q: API returning 500 errors + +**A**: Server-side errors in backend code. + +**Solutions**: +1. Check server logs for detailed error messages +2. Verify environment variables are set correctly +3. Check database connectivity +4. Test contract calls directly +5. Ensure proper error handling in code + +#### Q: Rate limiting issues + +**A**: Too many requests to Stacks API or hitting rate limits. + +**Solutions**: +1. Implement caching with Redis +2. Add exponential backoff for retries +3. Use API keys for higher rate limits +4. Optimize queries to reduce calls +5. Consider using API provider with higher limits + +### Event Processing + +#### Q: Missing events in processing + +**A**: WebSocket connection issues or event filtering problems. + +**Solutions**: +1. Implement reconnection logic with exponential backoff +2. Add event sequence number tracking +3. Store processed events to avoid duplicates +4. Add health checks for WebSocket connection +5. Implement fallback polling mechanism + +#### Q: Event processing backlog + +**A**: High event volume causing processing delays. + +**Solutions**: +1. Use message queue (Redis, RabbitMQ) +2. Implement parallel processing +3. Add horizontal scaling +4. Optimize event processing logic +5. Consider event batching + +--- + +## Cross-Chain Issues + +### Relayer Problems + +#### Q: Relayer not processing events + +**A**: Relayer service not running or misconfigured. + +**Solutions**: +1. Check relayer service logs +2. Verify WebSocket connections to both chains +3. Check configuration files +4. Ensure private keys are correct +5. Test with manual event triggers + +#### Q: Cross-chain state mismatch + +**A**: State synchronization issues between chains. + +**Solutions**: +1. Implement state reconciliation checks +2. Add manual override capabilities +3. Use merkle proofs for verification +4. Implement retry logic with exponential backoff +5. Add monitoring and alerting for mismatches + +### Address Mapping + +#### Q: Address mapping incorrect + +**A**: User addresses not properly mapped between chains. + +**Solutions**: +1. Implement address registration flow +2. Use cryptographic proofs for address ownership +3. Add manual verification process +4. Store mapping in decentralized storage +5. Implement recovery mechanisms + +--- + +## Performance Issues + +### Slow Response Times + +#### Q: API responses are slow + +**A**: Various performance bottlenecks. + +**Solutions**: +1. Add Redis caching for frequently accessed data +2. Optimize database queries with proper indexes +3. Use connection pooling +4. Implement response compression +5. Add CDN for static assets + +#### Q: Frontend rendering is slow + +**A**: Inefficient React rendering or data fetching. + +**Solutions**: +1. Use React.memo for component optimization +2. Implement virtual scrolling for large lists +3. Add loading skeletons for better UX +4. Use React Query for data caching +5. Implement code splitting + +### High Memory Usage + +#### Q: Memory leaks in backend + +**A**: Unclosed connections or event listeners. + +**Solutions**: +1. Use memory profiling tools +2. Ensure proper cleanup in error handlers +3. Limit WebSocket connections +4. Implement object pooling +5. Add memory monitoring and alerts + +--- + +## Security Issues + +### Smart Contract Security + +#### Q: Reentrancy vulnerability + +**A**: Contract vulnerable to reentrancy attacks. + +**Solutions**: +1. Follow checks-effects-interactions pattern +2. Use reentrancy guards +3. Limit external call complexity +4. Audit all external calls +5. Use formal verification tools + +#### Q: Access control issues + +**A**: Unauthorized access to admin functions. + +**Solutions**: +1. Implement proper access controls +2. Use multi-sig for critical functions +3. Add role-based permissions +4. Audit all privileged functions +5. Implement time locks for admin actions + +### API Security + +#### Q: API endpoints not secured + +**A**: Missing authentication or authorization. + +**Solutions**: +1. Implement API key authentication +2. Add rate limiting +3. Use HTTPS everywhere +4. Implement proper CORS policies +5. Add request validation + +--- + +## Frequently Asked Questions + +### General Questions + +#### Q: What is Hayy Protocol? + +**A**: Hayy Protocol is a cross-chain lending protocol that enables users to supply collateral on Stacks and borrow assets on Sui. It leverages the security of both blockchains while providing optimal capital efficiency. + +#### Q: How does the cross-chain mechanism work? + +**A**: The protocol uses a relayer service that monitors events on both chains: +1. User supplies STX collateral on Stacks +2. Relayer detects the event and registers collateral on Sui +3. User can borrow against this collateral on Sui +4. For withdrawals, relayer verifies debt is zero before unlocking + +#### Q: What are the supported assets? + +**A**: Currently supported assets: +- Stacks: STX (collateral only) +- Sui: USDC (borrowing), sBTC (collateral & borrowing) +- Future: More assets planned based on governance + +#### Q: What are the fees? + +**A**: Fee structure: +- Supply fee: 0% +- Borrow fee: Variable based on utilization +- Withdrawal fee: 0% +- Liquidation fee: 5% bonus to liquidators +- Cross-chain relayer fees: Minimal gas costs + +### Technical Questions + +#### Q: What blockchain does Hayy Protocol use? + +**A**: Hayy Protocol is built on: +- Stacks Blockchain for STX collateral management +- Sui Blockchain for borrowing and lending operations +- Cross-chain relayer for state synchronization + +#### Q: How are prices determined? + +**A**: Prices are provided by: +- Stacks: Price oracle contract (Pyth or Band Protocol in production) +- Sui: On-chain price feeds +- Both chains use the same price sources to ensure consistency + +#### Q: Is the protocol audited? + +**A**: Yes, the protocol undergoes: +- Smart contract audits by reputable firms +- Formal verification where applicable +- Bug bounty programs +- Community security reviews + +#### Q: How is liquidation handled? + +**A**: Liquidation process: +1. Positions with health factor < 1.0 are liquidatable +2. Anyone can liquidate underwater positions +3. Liquidators receive a 5% bonus +4. Cross-chain liquidations are handled by the relayer + +### User Questions + +#### Q: How do I start using Hayy Protocol? + +**A**: Getting started guide: +1. Install a Stacks wallet (Hiro, Leather, or Xverse) +2. Get STX tokens from an exchange or faucet +3. Connect your wallet to the Hayy Protocol dApp +4. Supply STX as collateral +5. Switch to Sui chain to borrow assets + +#### Q: What are the risks? + +**A**: Main risks include: +- Smart contract risk (mitigated by audits) +- Oracle price risk (mitigated by diversified price sources) +- Liquidation risk (manage by maintaining healthy collateral ratios) +- Cross-chain relayer risk (mitigated by decentralization) + +#### Q: How do I maintain a healthy position? + +**A**: Best practices: +1. Keep health factor above 1.5 for safety +2. Monitor asset prices regularly +3. Don't borrow at maximum capacity +4. Consider market volatility +5. Set up price alerts + +#### Q: Can I lose my funds? + +**A**: While the protocol is designed to be secure, risks exist: +- Smart contract bugs (unlikely due to audits) +- Market crashes causing liquidations +- Oracle failures (mitigated by redundancy) +- Cross-chain failures (mitigated by relayer monitoring) + +### Developer Questions + +#### Q: How can I integrate Hayy Protocol into my dApp? + +**A**: Integration options: +1. Use our frontend SDK for React applications +2. Use our REST API for backend integration +3. Direct contract calls for custom implementations +4. Use our subgraph for historical data + +#### Q: What programming languages are supported? + +**A**: Supported languages: +- Frontend: TypeScript/JavaScript (React, Vue, Angular) +- Backend: Node.js, Python, Go, Rust +- Smart Contracts: Clarity (Stacks), Move (Sui) +- Mobile: React Native, Flutter + +#### Q: How can I contribute to the protocol? + +**A**: Contribution opportunities: +1. Code contributions on GitHub +2. Bug bounty participation +3. Community governance +4. Documentation improvements +5. Testing and feedback + +#### Q: Where can I get help? + +**A**: Support channels: +1. Documentation: docs.hayyprotocol.com +2. Discord: discord.gg/hayyprotocol +3. GitHub Issues: github.com/hayyprotocol/issues +4. Twitter: @hayyprotocol +5. Email: support@hayyprotocol.com + +### Troubleshooting Quick Reference + +| Issue | Quick Fix | +|-------|-----------| +| Transaction fails | Check balance, gas fees, and health factor | +| Wallet won't connect | Clear cache, check network, try different browser | +| Data not loading | Check API endpoints, verify contract addresses | +| Relayer not working | Check logs, verify WebSocket connections | +| High gas fees | Wait for off-peak hours, optimize transactions | +| Liquidation risk | Add more collateral or repay debt | +| Cross-chain delays | Check relayer status, monitor event processing | + +--- + +## Emergency Procedures + +### Smart Contract Emergency + +If a critical vulnerability is discovered: + +1. **Pause Protocol**: Use emergency pause functions +2. **Notify Community**: Announce through all channels +3. **Deploy Fix**: Deploy patched contracts +4. **Migrate Funds**: Safely migrate user funds +5. **Post-mortem**: Analyze and share findings + +### Relayer Emergency + +If relayer service fails: + +1. **Switch to Backup**: Activate backup relayer +2. **Manual Processing**: Process critical transactions manually +3. **Investigate**: Analyze root cause +4. **Restore**: Bring primary relayer back online +5. **Monitor**: Watch for any issues + +### Security Incident + +If a security incident occurs: + +1. **Contain**: Isolate affected systems +2. **Assess**: Determine impact scope +3. **Communicate**: Inform stakeholders +4. **Remediate**: Fix vulnerabilities +5. **Review**: Improve security measures + +--- + +## Contact Information + +- **Security Team**: security@hayyprotocol.com +- **Technical Support**: support@hayyprotocol.com +- **Business Inquiries**: business@hayyprotocol.com +- **Media**: press@hayyprotocol.com + +For urgent security issues, please use the security email with "URGENT" in the subject line. + +This troubleshooting guide should help resolve most common issues with Hayy Protocol. For additional support, please reach out through our official channels. \ No newline at end of file diff --git a/hayyprotocol-stacks/package.json b/hayyprotocol-stacks/package.json index a72440e..1c03fa3 100644 --- a/hayyprotocol-stacks/package.json +++ b/hayyprotocol-stacks/package.json @@ -1,5 +1,5 @@ { - "name": "stacklend-tests", + "name": "hayyprotocol-stacks-tests", "version": "1.0.0", "description": "Run unit tests on this project.", "type": "module", @@ -12,9 +12,15 @@ "author": "", "license": "ISC", "dependencies": { +<<<<<<< HEAD + "@hirosystems/clarinet-sdk": "^3.6.0", + "@types/node": "^24.4.0", + "@stacks/transactions": "^7.2.0", +======= "@hirosystems/clarinet-sdk": "^3.0.2", +>>>>>>> 5a9a85724588c9f8cf2e1b0c0a39972081b8d1cd "chokidar-cli": "^3.0.0", - "vitest": "^3.1.3", + "vitest": "^3.2.4", "vitest-environment-clarinet": "^2.3.0" } } diff --git a/hayyprotocol-stacks/settings/Devnet.toml b/hayyprotocol-stacks/settings/Devnet.toml index fa029eb..229b3e3 100644 --- a/hayyprotocol-stacks/settings/Devnet.toml +++ b/hayyprotocol-stacks/settings/Devnet.toml @@ -7,7 +7,7 @@ mnemonic = "twice kind fence tip hidden tilt action fragile skin nothing glory c balance = 100_000_000_000_000 sbtc_balance = 1_000_000_000 # secret_key: 753b7cc01a1a2e86221266a154af739463fce51219d97e4f856cd7200c3bd2a601 -# stx_address: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM +# stx_address: ST1W1D173KWXC8NQ3PAG06PY18NPBMXG4NCB28XS5 # btc_address: mqVnk6NPRdhntvfm4hh9vvjiRkFDUuSYsH [accounts.wallet_1] @@ -112,8 +112,8 @@ disable_stacks_api = false # postgres_password = "postgres" # postgres_database = "postgres" # bitcoin_node_image_url = "lncm/bitcoind:v27.2" -# stacks_node_image_url = "blockstack/stacks-blockchain:3.2.0.0.0-alpine" -# stacks_signer_image_url = "blockstack/stacks-signer:3.2.0.0.0.0-alpine" +# stacks_node_image_url = "blockstack/stacks-blockchain:3.2.0.0.2-alpine" +# stacks_signer_image_url = "blockstack/stacks-signer:3.2.0.0.2.0-alpine" # stacks_api_image_url = "hirosystems/stacks-blockchain-api:latest" # stacks_explorer_image_url = "hirosystems/explorer:latest" # bitcoin_explorer_image_url = "quay.io/hirosystems/bitcoin-explorer:devnet"