diff --git a/entropy/sentinel_protocol/README.md b/entropy/sentinel_protocol/README.md new file mode 100644 index 0000000..9dafffd --- /dev/null +++ b/entropy/sentinel_protocol/README.md @@ -0,0 +1,406 @@ +# Token Reputation System with Pyth Entropy & Price Feeds + +The [Sentinel Protocol](https://github.com/csking101/Sentinel-Protocol) is a decentralized reputation protocol that monitors cryptocurrency portfolios, evaluates token credibility using real-time price data from Pyth Network, and enables autonomous portfolio protection through agent-to-agent (A2A) communication. + +We have used Pyth to obtain real-time pricing feed on-chain, as well as entropy that is used to compute the reputation score, using a stochastic model. Please check out our [project](https://github.com/csking101/Sentinel-Protocol) to see how Pyth is integrated with AI. + +## ๐ŸŒŸ Overview + +This project demonstrates how to build a comprehensive token reputation system that: +- **Fetches real-time price data** from Pyth Network's oracle +- **Calculates reputation scores** based on market stability, fundamental strength, and risk metrics +- **Integrates Pyth Entropy** for verifiable randomness in score computations, to aid in the stochastic computation model +- **Enables autonomous agents** to make portfolio rebalancing decisions +- **Supports multiple blockchain networks** (EVM-compatible chains) + +## ๐Ÿ—๏ธ Architecture + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Agent Orchestrator โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ Price Feed โ”‚ โ”‚ News Source โ”‚ โ”‚ Reputation โ”‚ โ”‚ +โ”‚ โ”‚ Agent โ”‚ โ”‚ Agent โ”‚ โ”‚ Agent โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ Decision Agent โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Smart Contract โ”‚ + โ”‚ (On-Chain) โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ Pyth Oracle โ”‚ โ”‚ Entropy โ”‚ + โ”‚ Price Feeds โ”‚ โ”‚ (Random) โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +## โœจ Key Features + +### 1. **Real-Time Price Integration** +- Fetches live cryptocurrency prices from Pyth Network +- Supports multiple tokens (BTC, ETH, SOL, MATIC, AAVE, DOGE, USDC, etc.) +- Price staleness checks to ensure data freshness +- Confidence intervals for risk assessment + +### 2. **Multi-Dimensional Reputation Scoring** +- **Market Stability**: Tracks price volatility and market conditions +- **Fundamental Strength**: Evaluates token fundamentals and adoption +- **Risk Score**: Assesses potential threats and vulnerabilities +- **Overall Reputation**: Composite score for decision-making + +### 3. **Entropy-Powered Randomness** +- Uses Pyth Entropy for verifiable random number generation +- Dynamic score adjustments based on entropy and price movements +- Prevents predictable manipulation of reputation scores +- Helps in creating a stochastic reputation model + +### 4. **Agent-to-Agent Communication** +- Modular agent architecture for specialized tasks +- Decision agent coordinates multiple information sources +- Authorization agent validates proposed actions +- Autonomous portfolio rebalancing based on market conditions + +## ๐Ÿ“‹ Prerequisites + +- Node.js v18+ and npm/yarn +- Solidity ^0.8.20 +- An Ethereum wallet with testnet funds +- Basic understanding of smart contracts and oracles + +## ๐Ÿš€ Quick Start + +### 1. Clone the Repository + +```bash +git clone https://github.com/pyth-network/pyth-examples.git +cd pyth-examples/token-reputation-system +``` + +### 2. Install Dependencies + +```bash +npm install +# or +yarn install +``` + +### 3. Configure Environment + +Create a `.env` file: + +```env +# Network Configuration +RPC_URL=https://rpc.ankr.com/eth_sepolia +PRIVATE_KEY=your_private_key_here + +# Contract Addresses (Sepolia Testnet) +PYTH_CONTRACT=0xDd24F84d36BF92C65F92307595335bdFab5Bbd21 +ENTROPY_CONTRACT=0x41c9e39574F40Ad34c79f1C99B66A45eFB830d4c +ENTROPY_PROVIDER=0x6CC14824Ea2918f5De5C2f75A9Da968ad4BD6344 + +# Deployed Contract +CONTRACT_ADDRESS=your_deployed_contract_address +ABI_PATH=./artifacts/TokenReputationContract.json +``` + +### 4. Deploy the Contract + +```bash +npx hardhat run scripts/deploy.ts --network sepolia +``` + +Or using Remix IDE: +1. Open [Remix](https://remix.ethereum.org) +2. Copy `contracts/TokenReputationContract.sol` +3. Compile with Solidity 0.8.20+ +4. Deploy with Pyth and Entropy addresses + +### 5. Run Tests + +```bash +npm test +``` + +## ๐Ÿ“ Project Structure + +``` +token-reputation-system/ +โ”œโ”€โ”€ contracts/ +โ”‚ โ””โ”€โ”€ TokenReputationContract.sol # Main smart contract +โ”œโ”€โ”€ scripts/ +โ”‚ โ”œโ”€โ”€ deploy.ts # Deployment script +โ”‚ โ””โ”€โ”€ test.ts # Test suite +โ”œโ”€โ”€ src/ +โ”‚ โ””โ”€โ”€ agents/ +โ”‚ โ”œโ”€โ”€ PriceFeedAgent.ts # Fetches Pyth prices +โ”‚ โ”œโ”€โ”€ ReputationAgent.ts # Reads on-chain reputation +โ”‚ โ”œโ”€โ”€ NewsSourceAgent.ts # Analyzes news sentiment +โ”‚ โ”œโ”€โ”€ DecisionAgent.ts # Makes rebalancing decisions +โ”‚ โ””โ”€โ”€ AuthorizationAgent.ts # Validates actions +โ”œโ”€โ”€ README.md +โ”œโ”€โ”€ USAGE.md # Detailed usage guide +โ”œโ”€โ”€ package.json +โ””โ”€โ”€ .env.example +``` + +## ๐Ÿ”ง Usage + +### Setting Up Price Feeds + +```typescript +import { ethers } from "ethers"; + +// Connect to contract +const contract = new ethers.Contract(contractAddress, abi, signer); + +// Set price feed for a token +await contract.setPriceFeedId( + "BTC", + "0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43" +); + +// Batch set multiple tokens +await contract.batchSetPriceFeedIds( + ["ETH", "SOL", "MATIC"], + [ethFeedId, solFeedId, maticFeedId] +); +``` + +### Setting Reputation Scores + +```typescript +// Set scores for a token +await contract.setScores( + "BTC", + 85, // market stability + 90, // fundamental strength + 30, // risk + 95 // reputation score +); +``` + +### Reading Token Data + +```typescript +// Get reputation scores +const [market, fundamental, risk, reputation] = + await contract.getScores("BTC"); + +console.log(`BTC Reputation: ${reputation}`); + +// Get complete data (scores + price) +const tokenData = await contract.getTokenData("BTC"); +console.log(`Price: $${formatPrice(tokenData.price, tokenData.expo)}`); +``` + +### Updating Prices from Pyth + +```typescript +import { PriceServiceConnection } from "@pythnetwork/price-service-client"; + +const connection = new PriceServiceConnection("https://hermes.pyth.network"); + +// Get price update data +const priceIds = [BTC_FEED_ID, ETH_FEED_ID]; +const priceUpdateData = await connection.getPriceFeedsUpdateData(priceIds); + +// Update on-chain +const updateFee = await contract.pyth.getUpdateFee(priceUpdateData); +await contract.updatePriceFeeds(priceUpdateData, { value: updateFee }); +``` + +### Requesting Entropy + +```typescript +// Request random entropy for dynamic score updates +const entropyFee = ethers.parseEther("0.001"); +await contract.requestRandomEntropy("BTC", { value: entropyFee }); + +// Entropy callback automatically updates scores +``` + +## ๐ŸŽฏ Key Concepts + +### Pyth Price Feeds + +Pyth uses a pull-based oracle model: +1. Fetch signed price updates off-chain from Hermes API +2. Submit updates on-chain with your transaction +3. Pay a small fee to update the price +4. Read validated price data + +**Price Data Structure:** +```typescript +{ + price: int64 // Raw price value + conf: uint64 // Confidence interval + expo: int32 // Exponent (price = price ร— 10^expo) + publishTime: uint // Unix timestamp +} +``` + +**Example:** +``` +price: 393238314383 +expo: -8 +Actual Price = 393238314383 ร— 10^(-8) = $3,932.38 +``` + +### Reputation Scoring + +Scores are calculated based on: +- **Market Stability (0-100)**: Lower volatility = higher score +- **Fundamental Strength (0-100)**: Strong fundamentals = higher score +- **Risk (0-100)**: Lower risk = better (inverted in reputation calculation) +- **Reputation Score (0-100)**: Composite of all factors + +Formula: +``` +Reputation = (MarketStability + FundamentalStrength - Risk) / 3 +``` + +### Entropy Integration + +Pyth Entropy provides verifiable randomness: +1. Request entropy with `requestRandomEntropy(token)` +2. Entropy callback receives random number +3. Scores are dynamically adjusted using: + - Random factor from entropy + - Current price movement + - Historical patterns + +This prevents predictable score manipulation. + +## ๐Ÿ” Security Considerations + +1. **Price Staleness**: Always check `publishTime` - reject stale prices +2. **Confidence Intervals**: High confidence = low `conf` value +3. **Access Control**: Only owner can set scores and price feeds +4. **Entropy Callbacks**: Only Entropy contract can call `_entropyCallback` +5. **Fee Handling**: Always send sufficient value for Pyth/Entropy fees + +## ๐ŸŒ Supported Networks + +### Testnets +- **Ethereum Sepolia**: Pyth `0xDd24F84d36BF92C65F92307595335bdFab5Bbd21` +- **Arbitrum Sepolia**: Pyth `0x4374e5a8b9C22271E9EB878A2AA31DE97DF15DAF` +- **Base Sepolia**: Pyth `0xA2aa501b19aff244D90cc15a4Cf739D2725B5729` + +### Mainnets +- **Ethereum**: Pyth `0x4305FB66699C3B2702D4d05CF36551390A4c69C6` +- **Arbitrum**: Pyth `0xff1a0f4744e8582DF1aE09D5611b887B6a12925C` +- **Optimism**: Pyth `0xff1a0f4744e8582DF1aE09D5611b887B6a12925C` + +See [Pyth Docs](https://docs.pyth.network/price-feeds/contract-addresses) for complete list. + +## ๐Ÿ“Š Pyth Price Feed IDs + +Common cryptocurrencies: + +| Token | Feed ID | +|-------|---------| +| BTC/USD | `0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43` | +| ETH/USD | `0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace` | +| SOL/USD | `0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d` | +| MATIC/USD | `0x5de33a9112c2b700b8d30b8a3402c103578ccfa2765696471cc672bd5cf6ac52` | +| AAVE/USD | `0x2b9ab1e972a281585084148ba1389800799bd4be63b957507db1349314e47445` | +| DOGE/USD | `0xdcef50dd0a4cd2dcc17e45df1676dcb336a11a61c69df7a0299b0150c672d25c` | +| USDC/USD | `0xeaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a` | + +Find more at: [pyth.network/developers/price-feed-ids](https://pyth.network/developers/price-feed-ids) + +## ๐Ÿงช Testing + +Run the complete test suite: + +```bash +npm test +``` + +Test individual components: + +```bash +# Test contract deployment +npm run test:deploy + +# Test price feeds +npm run test:prices + +# Test reputation scoring +npm run test:reputation + +# Test entropy integration +npm run test:entropy +``` + +## ๐Ÿ› Troubleshooting + +### "Insufficient fee" Error +Increase the value sent with `updatePriceFeeds`: +```typescript +const fee = await pyth.getUpdateFee(priceUpdateData); +await contract.updatePriceFeeds(priceUpdateData, { value: fee * 2n }); +``` + +### "Price feed not set for token" +Ensure you called `setPriceFeedId` before reading prices: +```typescript +await contract.setPriceFeedId("BTC", BTC_FEED_ID); +``` + +### "Price too stale" +Update prices more frequently or increase staleness threshold. + +### BigInt Conversion Issues +```typescript +// Convert BigInt to Number safely +const price = Number(priceData.price) * Math.pow(10, priceData.expo); +``` + +## ๐Ÿค Contributing + +We welcome contributions! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines. + +1. Fork the repository +2. Create a feature branch (`git checkout -b feature/amazing-feature`) +3. Commit your changes (`git commit -m 'Add amazing feature'`) +4. Push to the branch (`git push origin feature/amazing-feature`) +5. Open a Pull Request + +## ๐Ÿ“š Additional Resources + +- [Pyth Network Documentation](https://docs.pyth.network) +- [Pyth Price Feeds Tutorial](https://docs.pyth.network/price-feeds/create-your-first-pyth-app) +- [Pyth Entropy Documentation](https://docs.pyth.network/entropy) +- [Web3.js Documentation](https://web3js.readthedocs.io) +- [Ethers.js Documentation](https://docs.ethers.org) + +## ๐Ÿ“„ License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## ๐Ÿ™ Acknowledgments + +- [Pyth Network](https://pyth.network) for oracle infrastructure +- [Hedera](https://hedera.com) for agent-to-agent communication platform +- Community contributors and testers + +## ๐Ÿ“ž Support + +- **Issues**: [GitHub Issues](https://github.com/pyth-network/pyth-examples/issues) +- **Discord**: [Pyth Network Discord](https://discord.gg/PythNetwork) +- **Docs**: [Pyth Documentation](https://docs.pyth.network) + +--- + +**Built with โค๏ธ using Pyth Network** \ No newline at end of file diff --git a/entropy/sentinel_protocol/contracts-foundry/script/interaction.ts b/entropy/sentinel_protocol/contracts-foundry/script/interaction.ts new file mode 100644 index 0000000..64d5a0a --- /dev/null +++ b/entropy/sentinel_protocol/contracts-foundry/script/interaction.ts @@ -0,0 +1,472 @@ +import { ethers } from "ethers"; +import * as fs from "fs"; + +/** + * Test Script for TokenReputationContract with Pyth + Entropy Integration + * + * This contract requires 3 addresses on deployment: + * 1. Pyth Contract Address + * 2. Entropy Contract Address + * 3. Entropy Provider Address + */ + +// ============================================================================ +// Configuration +// ============================================================================ + +const CONFIG = { + // Contract addresses (update these for your network) + // For this example, the contract is deployed on Hedera + PYTH_CONTRACT: "0xa2aa501b19aff244d90cc15a4cf739d2725b5729", + ENTROPY_CONTRACT: "0x41c9e39574F40Ad34c79f1C99B66A45eFB830d4c", // Example - update with yours + ENTROPY_PROVIDER: "0x6CC14824Ea2918f5De5C2f75A9Da968ad4BD6344", // Example - update with yours + + // Your deployed contract address (fill after deployment) + TOKEN_REPUTATION_CONTRACT: "0x479d9f5ea676ad97e129f15724e31f770f952a3a", + + // RPC and Wallet + RPC_URL: "https://rpc.ankr.com/eth_sepolia", // Or your preferred RPC + PRIVATE_KEY: "YOUR_PRIVATE_KEY", // NEVER commit this! +}; + +// Pyth Price Feed IDs +const PRICE_FEED_IDS = { + BTC: "0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43", + ETH: "0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", + SOL: "0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d", + MATIC: "0x5de33a9112c2b700b8d30b8a3402c103578ccfa2765696471cc672bd5cf6ac52", + AAVE: "0x2b9ab1e972a281585084148ba1389800799bd4be63b957507db1349314e47445", + DOGE: "0xdcef50dd0a4cd2dcc17e45df1676dcb336a11a61c69df7a0299b0150c672d25c", + USDC: "0xeaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a", +}; + +// Contract ABI (from your compiled contract) +const CONTRACT_ABI = [ + "constructor(address _pythContract, address _entropyContract, address _entropyProvider)", + "function owner() external view returns (address)", + "function pyth() external view returns (address)", + "function PRICE_STALENESS_THRESHOLD() external view returns (uint256)", + + // Price Feed Management + "function setPriceFeedId(string calldata token, bytes32 priceFeedId) external", + "function batchSetPriceFeedIds(string[] calldata tokens, bytes32[] calldata priceFeedIds) external", + "function getLatestPrice(string calldata token) external view returns (int64 price, uint64 confidence, int32 expo, uint256 publishTime)", + "function getPriceNoOlderThan(string calldata token, uint256 age) external view returns (int64, uint64, int32, uint256)", + "function updatePriceFeeds(bytes[] calldata priceUpdateData) external payable", + + // Score Management + "function setScores(string calldata token, uint256 marketStability, uint256 fundamentalStrength, uint256 risk, uint256 reputationScore) external", + "function getScores(string calldata token) external view returns (uint256, uint256, uint256, uint256)", + "function getTokenData(string calldata token) external view returns (uint256, uint256, uint256, uint256, int64, uint64, int32, uint256)", + "function getAllTokens() external view returns (string[] memory)", + + // Entropy Functions + "function requestRandomEntropy(string calldata token) external payable", + + // Utility + "function withdraw() external", + + // Events + "event ScoresUpdated(string token, uint256 market, uint256 fundamental, uint256 risk, uint256 reputation)", + "event PriceFeedIdSet(string token, bytes32 priceFeedId)", + "event PriceUpdated(string token, int64 price, uint64 confidence, uint256 publishTime)", + "event EntropyRequested(uint64 sequenceNumber, string token)", + "event EntropyReceived(uint64 sequenceNumber, bytes32 randomNumber, string token)", +]; + +// ============================================================================ +// Helper Functions +// ============================================================================ + +function formatPrice(price: bigint, expo: number): string { + const actualPrice = Number(price) * Math.pow(10, expo); + return actualPrice.toFixed(2); +} + +function formatTimestamp(timestamp: bigint): string { + return new Date(Number(timestamp) * 1000).toLocaleString(); +} + +async function waitForTransaction(tx: any, description: string) { + console.log(`โณ ${description}...`); + console.log(` Transaction hash: ${tx.hash}`); + const receipt = await tx.wait(); + console.log(`โœ… Confirmed in block ${receipt.blockNumber}`); + console.log(` Gas used: ${receipt.gasUsed.toString()}\n`); + return receipt; +} + +// ============================================================================ +// Main Test Suite +// ============================================================================ + +async function main() { + console.log("๐Ÿš€ TokenReputationContract Test Suite"); + console.log("=" .repeat(60)); + console.log(); + + // Setup provider and signer + const provider = new ethers.JsonRpcProvider(CONFIG.RPC_URL); + const wallet = new ethers.Wallet(CONFIG.PRIVATE_KEY, provider); + + console.log("๐Ÿ“‹ Configuration:"); + console.log(` Network: ${(await provider.getNetwork()).name}`); + console.log(` Wallet Address: ${wallet.address}`); + console.log(` Balance: ${ethers.formatEther(await provider.getBalance(wallet.address))} ETH`); + console.log(` Pyth Contract: ${CONFIG.PYTH_CONTRACT}`); + console.log(` Entropy Contract: ${CONFIG.ENTROPY_CONTRACT}`); + console.log(` Contract Address: ${CONFIG.TOKEN_REPUTATION_CONTRACT}`); + console.log(); + + // Connect to deployed contract + const contract = new ethers.Contract( + CONFIG.TOKEN_REPUTATION_CONTRACT, + CONTRACT_ABI, + wallet + ); + + // ============================================================================ + // Test 1: Verify Deployment + // ============================================================================ + console.log("๐Ÿ“Š Test 1: Verify Deployment Configuration"); + console.log("-".repeat(60)); + + try { + const owner = await contract.owner(); + const pythAddress = await contract.pyth(); + const stalenessThreshold = await contract.PRICE_STALENESS_THRESHOLD(); + + console.log(`โœ… Owner: ${owner}`); + console.log(`โœ… Pyth Contract: ${pythAddress}`); + console.log(`โœ… Staleness Threshold: ${stalenessThreshold} seconds`); + console.log(`โœ… Deployment verified!\n`); + } catch (error: any) { + console.error(`โŒ Error verifying deployment: ${error.message}\n`); + } + + // ============================================================================ + // Test 2: Set Price Feed IDs + // ============================================================================ + console.log("๐Ÿ“Š Test 2: Set Price Feed IDs"); + console.log("-".repeat(60)); + + const tokensToSetup = [ + { symbol: "BTC", feedId: PRICE_FEED_IDS.BTC }, + { symbol: "ETH", feedId: PRICE_FEED_IDS.ETH }, + { symbol: "MATIC", feedId: PRICE_FEED_IDS.MATIC }, + ]; + + for (const token of tokensToSetup) { + try { + const tx = await contract.setPriceFeedId(token.symbol, token.feedId); + await waitForTransaction(tx, `Setting price feed for ${token.symbol}`); + } catch (error: any) { + console.error(`โŒ Failed to set ${token.symbol} price feed: ${error.message}\n`); + } + } + + // ============================================================================ + // Test 3: Batch Set Price Feeds + // ============================================================================ + console.log("๐Ÿ“Š Test 3: Batch Set Price Feeds"); + console.log("-".repeat(60)); + + try { + const batchTokens = ["AAVE", "DOGE", "USDC"]; + const batchFeedIds = [ + PRICE_FEED_IDS.AAVE, + PRICE_FEED_IDS.DOGE, + PRICE_FEED_IDS.USDC, + ]; + + const tx = await contract.batchSetPriceFeedIds(batchTokens, batchFeedIds); + await waitForTransaction(tx, "Batch setting price feeds"); + } catch (error: any) { + console.error(`โŒ Batch set failed: ${error.message}\n`); + } + + // ============================================================================ + // Test 4: Set Reputation Scores + // ============================================================================ + console.log("๐Ÿ“Š Test 4: Set Reputation Scores"); + console.log("-".repeat(60)); + + const scoreData = [ + { token: "BTC", market: 85, fundamental: 90, risk: 30, reputation: 95 }, + { token: "ETH", market: 80, fundamental: 88, risk: 35, reputation: 90 }, + { token: "MATIC", market: 70, fundamental: 75, risk: 50, reputation: 80 }, + ]; + + for (const data of scoreData) { + try { + const tx = await contract.setScores( + data.token, + data.market, + data.fundamental, + data.risk, + data.reputation + ); + await waitForTransaction(tx, `Setting scores for ${data.token}`); + } catch (error: any) { + console.error(`โŒ Failed to set ${data.token} scores: ${error.message}\n`); + } + } + + // ============================================================================ + // Test 5: Read Scores + // ============================================================================ + console.log("๐Ÿ“Š Test 5: Read Reputation Scores"); + console.log("-".repeat(60)); + + for (const token of ["BTC", "ETH", "MATIC"]) { + try { + const [market, fundamental, risk, reputation] = await contract.getScores(token); + console.log(`\n${token} Scores:`); + console.log(` Market Stability: ${market}`); + console.log(` Fundamental Strength: ${fundamental}`); + console.log(` Risk: ${risk}`); + console.log(` Reputation Score: ${reputation}`); + } catch (error: any) { + console.error(`โŒ Failed to read ${token} scores: ${error.message}`); + } + } + console.log(); + + // ============================================================================ + // Test 6: Get All Tokens + // ============================================================================ + console.log("๐Ÿ“Š Test 6: Get All Registered Tokens"); + console.log("-".repeat(60)); + + try { + const allTokens = await contract.getAllTokens(); + console.log(`โœ… Registered Tokens (${allTokens.length}):`); + allTokens.forEach((token: string, index: number) => { + console.log(` ${index + 1}. ${token}`); + }); + console.log(); + } catch (error: any) { + console.error(`โŒ Failed to get tokens: ${error.message}\n`); + } + + // ============================================================================ + // Test 7: Get Latest Prices (will likely fail without price updates) + // ============================================================================ + console.log("๐Ÿ“Š Test 7: Get Latest Prices"); + console.log("-".repeat(60)); + console.log("โš ๏ธ Note: This will fail without calling updatePriceFeeds first\n"); + + for (const token of ["BTC", "ETH", "MATIC"]) { + try { + const [price, conf, expo, publishTime] = await contract.getLatestPrice(token); + const actualPrice = formatPrice(price, expo); + const actualConf = formatPrice(conf, expo); + + console.log(`${token} Price Data:`); + console.log(` Price: $${actualPrice}`); + console.log(` Confidence: ยฑ$${actualConf}`); + console.log(` Publish Time: ${formatTimestamp(publishTime)}`); + console.log(); + } catch (error: any) { + console.log(`โ„น๏ธ ${token}: ${error.message.substring(0, 100)}...\n`); + } + } + + // ============================================================================ + // Test 8: Get Token Data (Combined Scores + Price) + // ============================================================================ + console.log("๐Ÿ“Š Test 8: Get Complete Token Data"); + console.log("-".repeat(60)); + + try { + const [market, fundamental, risk, reputation, price, conf, expo, publishTime] = + await contract.getTokenData("BTC"); + + console.log("BTC Complete Data:"); + console.log("\nReputation Scores:"); + console.log(` Market Stability: ${market}`); + console.log(` Fundamental Strength: ${fundamental}`); + console.log(` Risk: ${risk}`); + console.log(` Reputation Score: ${reputation}`); + console.log("\nPrice Data:"); + console.log(` Price: $${formatPrice(price, expo)}`); + console.log(` Confidence: ยฑ$${formatPrice(conf, expo)}`); + console.log(` Last Updated: ${formatTimestamp(publishTime)}`); + console.log(); + } catch (error: any) { + console.log(`โ„น๏ธ Complete data unavailable: ${error.message}\n`); + } + + // ============================================================================ + // Test 9: Request Random Entropy (Requires ETH for fee) + // ============================================================================ + console.log("๐Ÿ“Š Test 9: Request Random Entropy"); + console.log("-".repeat(60)); + console.log("โš ๏ธ This requires ETH for entropy fee and may take time\n"); + + try { + // Get entropy fee (you'd need to query the entropy contract) + const entropyFee = ethers.parseEther("0.001"); // Example fee + + const tx = await contract.requestRandomEntropy("BTC", { + value: entropyFee + }); + + console.log(`โณ Requesting entropy for BTC...`); + console.log(` Transaction hash: ${tx.hash}`); + const receipt = await tx.wait(); + + // Parse events + const events = receipt.logs + .map((log: any) => { + try { + return contract.interface.parseLog(log); + } catch { + return null; + } + }) + .filter((event: any) => event !== null); + + events.forEach((event: any) => { + if (event.name === "EntropyRequested") { + console.log(`โœ… Entropy requested!`); + console.log(` Sequence Number: ${event.args.sequenceNumber}`); + console.log(` Token: ${event.args.token}`); + } + }); + console.log(); + } catch (error: any) { + console.error(`โŒ Entropy request failed: ${error.message}\n`); + } + + // ============================================================================ + // Test 10: Listen for Events + // ============================================================================ + console.log("๐Ÿ“Š Test 10: Listen for Recent Events"); + console.log("-".repeat(60)); + + try { + const currentBlock = await provider.getBlockNumber(); + const fromBlock = currentBlock - 100; // Last 100 blocks + + // Get ScoresUpdated events + const scoresFilter = contract.filters.ScoresUpdated(); + const scoresEvents = await contract.queryFilter(scoresFilter, fromBlock); + + console.log(`\nScoresUpdated Events (${scoresEvents.length}):`); + scoresEvents.forEach((event: any, index: number) => { + console.log(`\n Event ${index + 1}:`); + console.log(` Token: ${event.args.token}`); + console.log(` Market: ${event.args.market}`); + console.log(` Fundamental: ${event.args.fundamental}`); + console.log(` Risk: ${event.args.risk}`); + console.log(` Reputation: ${event.args.reputation}`); + }); + + // Get PriceFeedIdSet events + const priceFilter = contract.filters.PriceFeedIdSet(); + const priceEvents = await contract.queryFilter(priceFilter, fromBlock); + + console.log(`\n\nPriceFeedIdSet Events (${priceEvents.length}):`); + priceEvents.forEach((event: any, index: number) => { + console.log(` ${index + 1}. ${event.args.token}: ${event.args.priceFeedId}`); + }); + + // Get Entropy events + const entropyReqFilter = contract.filters.EntropyRequested(); + const entropyReqEvents = await contract.queryFilter(entropyReqFilter, fromBlock); + + console.log(`\n\nEntropyRequested Events (${entropyReqEvents.length}):`); + entropyReqEvents.forEach((event: any, index: number) => { + console.log(` ${index + 1}. Seq: ${event.args.sequenceNumber}, Token: ${event.args.token}`); + }); + + const entropyRecFilter = contract.filters.EntropyReceived(); + const entropyRecEvents = await contract.queryFilter(entropyRecFilter, fromBlock); + + console.log(`\n\nEntropyReceived Events (${entropyRecEvents.length}):`); + entropyRecEvents.forEach((event: any, index: number) => { + console.log(` ${index + 1}. Seq: ${event.args.sequenceNumber}`); + console.log(` Random: ${event.args.randomNumber}`); + console.log(` Token: ${event.args.token}`); + }); + + console.log(); + } catch (error: any) { + console.error(`โŒ Event listening failed: ${error.message}\n`); + } + + // ============================================================================ + // Summary + // ============================================================================ + console.log("=".repeat(60)); + console.log("โœ… Test Suite Completed!"); + console.log("=".repeat(60)); + console.log("\n๐Ÿ“ Summary:"); + console.log("โœ“ Contract deployment verified"); + console.log("โœ“ Price feeds configured"); + console.log("โœ“ Reputation scores set and verified"); + console.log("โœ“ Token list populated"); + console.log("โœ“ Events emitted and captured"); + console.log("\n๐Ÿš€ Next Steps:"); + console.log("1. Call updatePriceFeeds() with Pyth price data"); + console.log("2. Monitor entropy callbacks for score updates"); + console.log("3. Integrate with your A2A agent system"); + console.log("4. Set up automated monitoring and rebalancing"); + console.log(); +} + +// ============================================================================ +// Deployment Helper +// ============================================================================ + +async function deployContract() { + console.log("๐Ÿš€ Deploying TokenReputationContract...\n"); + + const provider = new ethers.JsonRpcProvider(CONFIG.RPC_URL); + const wallet = new ethers.Wallet(CONFIG.PRIVATE_KEY, provider); + + // Load contract bytecode and ABI + const contractJson = JSON.parse(fs.readFileSync("./TokenReputationContract.json", "utf8")); + + const factory = new ethers.ContractFactory( + contractJson.abi, + contractJson.bytecode, + wallet + ); + + console.log("๐Ÿ“‹ Deployment Parameters:"); + console.log(` Pyth: ${CONFIG.PYTH_CONTRACT}`); + console.log(` Entropy: ${CONFIG.ENTROPY_CONTRACT}`); + console.log(` Provider: ${CONFIG.ENTROPY_PROVIDER}\n`); + + const contract = await factory.deploy( + CONFIG.PYTH_CONTRACT, + CONFIG.ENTROPY_CONTRACT, + CONFIG.ENTROPY_PROVIDER + ); + + console.log(`โณ Waiting for deployment...`); + await contract.waitForDeployment(); + + const address = await contract.getAddress(); + console.log(`โœ… Contract deployed at: ${address}\n`); + console.log(`Update CONFIG.TOKEN_REPUTATION_CONTRACT with this address!`); + + return address; +} + +// ============================================================================ +// Run +// ============================================================================ + +// Uncomment to deploy: +// deployContract().catch(console.error); + +// Run tests: +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); \ No newline at end of file diff --git a/entropy/sentinel_protocol/contracts-foundry/src/TokenReputationABI.json b/entropy/sentinel_protocol/contracts-foundry/src/TokenReputationABI.json new file mode 100644 index 0000000..965a482 --- /dev/null +++ b/entropy/sentinel_protocol/contracts-foundry/src/TokenReputationABI.json @@ -0,0 +1,639 @@ +{ + "compiler": { + "version": "0.8.30+commit.73712a01" + }, + "language": "Solidity", + "output": { + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_pythContract", + "type": "address" + }, + { + "internalType": "address", + "name": "_entropyContract", + "type": "address" + }, + { + "internalType": "address", + "name": "_entropyProvider", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "sequenceNumber", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "randomNumber", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "string", + "name": "token", + "type": "string" + } + ], + "name": "EntropyReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "sequenceNumber", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "string", + "name": "token", + "type": "string" + } + ], + "name": "EntropyRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "token", + "type": "string" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "priceFeedId", + "type": "bytes32" + } + ], + "name": "PriceFeedIdSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "token", + "type": "string" + }, + { + "indexed": false, + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "confidence", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "name": "PriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "token", + "type": "string" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "market", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fundamental", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "risk", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reputation", + "type": "uint256" + } + ], + "name": "ScoresUpdated", + "type": "event" + }, + { + "inputs": [], + "name": "PRICE_STALENESS_THRESHOLD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint64", + "name": "sequence", + "type": "uint64" + }, + { + "internalType": "address", + "name": "provider", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "randomNumber", + "type": "bytes32" + } + ], + "name": "_entropyCallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string[]", + "name": "tokens", + "type": "string[]" + }, + { + "internalType": "bytes32[]", + "name": "priceFeedIds", + "type": "bytes32[]" + } + ], + "name": "batchSetPriceFeedIds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getAllTokens", + "outputs": [ + { + "internalType": "string[]", + "name": "", + "type": "string[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "token", + "type": "string" + } + ], + "name": "getLatestPrice", + "outputs": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "confidence", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "token", + "type": "string" + }, + { + "internalType": "uint256", + "name": "age", + "type": "uint256" + } + ], + "name": "getPriceNoOlderThan", + "outputs": [ + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "confidence", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "token", + "type": "string" + } + ], + "name": "getScores", + "outputs": [ + { + "internalType": "uint256", + "name": "marketStability", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fundamentalStrength", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "risk", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reputationScore", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "token", + "type": "string" + } + ], + "name": "getTokenData", + "outputs": [ + { + "internalType": "uint256", + "name": "marketStability", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fundamentalStrength", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "risk", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reputationScore", + "type": "uint256" + }, + { + "internalType": "int64", + "name": "price", + "type": "int64" + }, + { + "internalType": "uint64", + "name": "confidence", + "type": "uint64" + }, + { + "internalType": "int32", + "name": "expo", + "type": "int32" + }, + { + "internalType": "uint256", + "name": "publishTime", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pyth", + "outputs": [ + { + "internalType": "contract IPyth", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "token", + "type": "string" + } + ], + "name": "requestRandomEntropy", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "token", + "type": "string" + }, + { + "internalType": "bytes32", + "name": "priceFeedId", + "type": "bytes32" + } + ], + "name": "setPriceFeedId", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "token", + "type": "string" + }, + { + "internalType": "uint256", + "name": "marketStability", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "fundamentalStrength", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "risk", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reputationScore", + "type": "uint256" + } + ], + "name": "setScores", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "name": "tokenPriceFeedIds", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "priceUpdateData", + "type": "bytes[]" + } + ], + "name": "updatePriceFeeds", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ], + "devdoc": { + "kind": "dev", + "methods": {}, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": {}, + "version": 1 + } + }, + "settings": { + "compilationTarget": { + "contracts/test.sol": "TokenReputationContract" + }, + "evmVersion": "prague", + "libraries": {}, + "metadata": { + "bytecodeHash": "ipfs" + }, + "optimizer": { + "enabled": false, + "runs": 200 + }, + "remappings": [] + }, + "sources": { + "@pythnetwork/entropy-sdk-solidity/EntropyEvents.sol": { + "keccak256": "0x385eb7fb335b3c7037e5d2ecf119f42baa4f69fbc535daf1effbc26e774a6a4a", + "license": "Apache-2.0", + "urls": [ + "bzz-raw://b62bfbf9e5969390d22c4ad0a6c5d64a1091d0cdef3e19e72482333c88c0e39b", + "dweb:/ipfs/QmaN1oB9u82CaxYcGyKDxZKDhjYiM8J324AE6j5yCjFReK" + ] + }, + "@pythnetwork/entropy-sdk-solidity/EntropyEventsV2.sol": { + "keccak256": "0xc8c2438857680a605d6b441f0b5fa21054dce0ae1db0a7ef8b8ab14a97249e4e", + "license": "Apache-2.0", + "urls": [ + "bzz-raw://81739805ac90c9bc91e87e4e42d57c5420cfb179a3f9088fb8416e4ba2eeade3", + "dweb:/ipfs/QmSvrb38Bn8tDCcaC9vZpV4J8BnXy8y9fSNMaUdNaKMA28" + ] + }, + "@pythnetwork/entropy-sdk-solidity/EntropyStructs.sol": { + "keccak256": "0xc23ba702644b68f402b5cd1ef98da7c194ae4a3d05249a88b75160c503704809", + "license": "Apache 2", + "urls": [ + "bzz-raw://b2ac288f4e8cd2484cf8abb7bb709e4709e4ffa10697945498ba365312cfe191", + "dweb:/ipfs/Qme9QS4P94gb9B81qLYX3EE2pQrb7MJSAaZygHuydpZo6n" + ] + }, + "@pythnetwork/entropy-sdk-solidity/EntropyStructsV2.sol": { + "keccak256": "0xca3e9a064e5e557f767475d4a543399c315babce9681f98454950e7fe52ed44c", + "license": "Apache 2", + "urls": [ + "bzz-raw://149efc8c37b0d325da7ee2dae5bfffcbd6f420acdb8445f0744e351b4a392688", + "dweb:/ipfs/QmUW5Z72iFDwxdeWh76kYNyT1agDao2AVmpc4zSC5q8Laz" + ] + }, + "@pythnetwork/entropy-sdk-solidity/IEntropy.sol": { + "keccak256": "0xa75f679e5dfec8b3d5f8dcfe09fc10a2c8e5f04a2612cd47c07e47294cc7c332", + "license": "Apache 2", + "urls": [ + "bzz-raw://d5b5edc3fbbb98a5a4ea4f60335b60c5c395b7fdb58596d6e25bfb01c8179a60", + "dweb:/ipfs/QmWqbFNTZrAwmZkHbDq9qR9QojsRCjDv1rExPU5Bh4icRZ" + ] + }, + "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol": { + "keccak256": "0xf3d3dee1e9cbdef70b6c1f4d79aa8b438413e4636c00e79e615da9dc4df9c379", + "license": "Apache 2", + "urls": [ + "bzz-raw://0e473522447c8f92a43f4fa3e54d83a789e12cc44b2a86847bd238a7f8827952", + "dweb:/ipfs/Qmdihx73a89EZYy2GpitTxK92SWDLyPWeWnJTZ4Acva958" + ] + }, + "@pythnetwork/entropy-sdk-solidity/IEntropyV2.sol": { + "keccak256": "0xbfa09defc676b17bc4cb9d8e17f703c885de676d16396e45963dbe104afaba6a", + "license": "Apache 2", + "urls": [ + "bzz-raw://cdf892b3def9c3e506e197ffe1ed1c040d6e4e668b9249439eacac965d98c3d5", + "dweb:/ipfs/QmRzQev7pKackFa33pVqwjf3iYt6GdDzCCvwUY5c8Z4tXU" + ] + }, + "@pythnetwork/pyth-sdk-solidity/IPyth.sol": { + "keccak256": "0xada6a1f4898b056d2804409ecad466b786dc6a12bc6f8f247e7f1471c1b86fcd", + "license": "Apache-2.0", + "urls": [ + "bzz-raw://ecee3c205896236fc11bfb1367f033353448fa462b08e5212a32e767f72022c9", + "dweb:/ipfs/QmeEQpRRza2Swm2gCCC61TDxQ2GNLjqPhdtbSznADMuMni" + ] + }, + "@pythnetwork/pyth-sdk-solidity/IPythEvents.sol": { + "keccak256": "0xd67239becd2c39bd9d065830be24e70606f5747ab31b8818bea849d09ac17ddc", + "license": "Apache-2.0", + "urls": [ + "bzz-raw://e719bd2d8c1d8d46713aeb700dd6b5ddd766a77830ea9ae3cc9327f67ed775d1", + "dweb:/ipfs/QmNQYyRVFLEvLsCSTkHkAemTvvZm4CzLN7hUdMidMcBS7u" + ] + }, + "@pythnetwork/pyth-sdk-solidity/PythStructs.sol": { + "keccak256": "0x474436bf0d558cc9b2c00a9d0ce318147acdf7963f34ef4acadb9248e65bbc7b", + "license": "Apache-2.0", + "urls": [ + "bzz-raw://2b8f9bd9de35e67c7bdbf04d8c2e771718fcc8666456ca5f92dbd914e4a5f2b3", + "dweb:/ipfs/QmNP3ShBYRp4RiVbAudMB7rNHAqgtwn8pPBzb8JTUaViRh" + ] + }, + "contracts/test.sol": { + "keccak256": "0xb330142769a12ffc87fabc6c24fc291550e2ba0b888179a5f1affdca82473569", + "license": "MIT", + "urls": [ + "bzz-raw://366dc366fd089b1477c6a4b9bec2d4308071b62456f04994de69ced8984449d9", + "dweb:/ipfs/QmcFGGB2uJjuVSgQzK8fYZQL8wSUdMXmpHpiASH1jeDaFZ" + ] + } + }, + "version": 1 +} \ No newline at end of file diff --git a/entropy/sentinel_protocol/contracts-foundry/src/TokenReputationContract.sol b/entropy/sentinel_protocol/contracts-foundry/src/TokenReputationContract.sol new file mode 100644 index 0000000..ca01228 --- /dev/null +++ b/entropy/sentinel_protocol/contracts-foundry/src/TokenReputationContract.sol @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; +import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; +import "@pythnetwork/entropy-sdk-solidity/IEntropy.sol"; +import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol"; + +contract TokenReputationContract is IEntropyConsumer { + // === Core Contracts === + IPyth public pyth; + IEntropy private entropy; + address private entropyProvider; + + // === Owner === + address public owner; + + // === Structs === + struct Scores { + uint256 marketStability; + uint256 fundamentalStrength; + uint256 risk; + uint256 reputationScore; + } + + struct PriceData { + int64 price; + uint64 conf; + int32 expo; + uint256 publishTime; + } + + // === State Mappings === + mapping(string => Scores) private tokenScores; + mapping(string => bytes32) public tokenPriceFeedIds; + mapping(string => bool) private tokenExists; + mapping(uint64 => string) private entropyRequests; + + // === Token List === + string[] private tokenList; + + // === Constants === + uint256 public constant PRICE_STALENESS_THRESHOLD = 60; + + // === Events === + event ScoresUpdated(string token, uint256 market, uint256 fundamental, uint256 risk, uint256 reputation); + event PriceFeedIdSet(string token, bytes32 priceFeedId); + event PriceUpdated(string token, int64 price, uint64 confidence, uint256 publishTime); + event EntropyRequested(uint64 sequenceNumber, string token); + event EntropyReceived(uint64 sequenceNumber, bytes32 randomNumber, string token); + + // === Constructor === + constructor( + address _pythContract, + address _entropyContract, + address _entropyProvider + ) { + owner = msg.sender; + pyth = IPyth(_pythContract); + entropy = IEntropy(_entropyContract); + entropyProvider = _entropyProvider; + } + + // === Modifiers === + modifier onlyOwner() { + require(msg.sender == owner, "Only owner can call this"); + _; + } + + // === Required by IEntropyConsumer === + function getEntropy() internal view override returns (address) { + return address(entropy); + } + + // === Price Feed Management === + function setPriceFeedId(string calldata token, bytes32 priceFeedId) external onlyOwner { + tokenPriceFeedIds[token] = priceFeedId; + emit PriceFeedIdSet(token, priceFeedId); + } + + function batchSetPriceFeedIds( + string[] calldata tokens, + bytes32[] calldata priceFeedIds + ) external onlyOwner { + require(tokens.length == priceFeedIds.length, "Array length mismatch"); + for (uint256 i = 0; i < tokens.length; i++) { + tokenPriceFeedIds[tokens[i]] = priceFeedIds[i]; + emit PriceFeedIdSet(tokens[i], priceFeedIds[i]); + } + } + + // === External Wrapper === + function getLatestPrice( + string calldata token + ) public view returns (int64 price, uint64 confidence, int32 expo, uint256 publishTime) { + return _getLatestPrice(token); + } + + // === Internal version (memory-based) === + function _getLatestPrice( + string memory token + ) internal view returns (int64 price, uint64 confidence, int32 expo, uint256 publishTime) { + bytes32 priceFeedId = tokenPriceFeedIds[token]; + require(priceFeedId != bytes32(0), "Price feed not set for token"); + + PythStructs.Price memory priceData = pyth.getPriceUnsafe(priceFeedId); + return (priceData.price, priceData.conf, priceData.expo, priceData.publishTime); + } + + function getPriceNoOlderThan( + string calldata token, + uint256 age + ) public view returns (int64 price, uint64 confidence, int32 expo, uint256 publishTime) { + bytes32 priceFeedId = tokenPriceFeedIds[token]; + require(priceFeedId != bytes32(0), "Price feed not set for token"); + + PythStructs.Price memory priceData = pyth.getPriceNoOlderThan(priceFeedId, age); + return (priceData.price, priceData.conf, priceData.expo, priceData.publishTime); + } + + function updatePriceFeeds(bytes[] calldata priceUpdateData) public payable { + uint256 fee = pyth.getUpdateFee(priceUpdateData); + require(msg.value >= fee, "Insufficient fee"); + + pyth.updatePriceFeeds{value: fee}(priceUpdateData); + + if (msg.value > fee) { + payable(msg.sender).transfer(msg.value - fee); + } + } + + // === Entropy Integration === + function requestRandomEntropy(string calldata token) public payable onlyOwner { + uint256 fee = entropy.getFee(entropyProvider); + uint64 seqNum = entropy.requestWithCallback{value: fee}(entropyProvider, keccak256(abi.encodePacked(token, block.timestamp))); + entropyRequests[seqNum] = token; + emit EntropyRequested(seqNum, token); + } + + function entropyCallback( + uint64 sequenceNumber, + address, + bytes32 entropyRandomNumber + ) internal override { + string memory token = entropyRequests[sequenceNumber]; + require(bytes(token).length > 0, "Unknown sequence number"); + + // Use entropy + price to dynamically recompute scores + (int64 price, , , ) = _getLatestPrice(token); + uint256 randomFactor = uint256(entropyRandomNumber) % 100; + + Scores memory s = tokenScores[token]; + + // Update scores based on random + price movement + s.marketStability = uint256(int256(s.marketStability) + (price % 3)) % 100; + s.fundamentalStrength = (s.fundamentalStrength + randomFactor / 2) % 100; + s.risk = (s.risk + randomFactor) % 100; + s.reputationScore = (100 + s.marketStability + s.fundamentalStrength - s.risk) / 3; + + tokenScores[token] = s; + + emit EntropyReceived(sequenceNumber, entropyRandomNumber, token); + emit ScoresUpdated(token, s.marketStability, s.fundamentalStrength, s.risk, s.reputationScore); + } + + // === Score Management === + function setScores( + string calldata token, + uint256 marketStability, + uint256 fundamentalStrength, + uint256 risk, + uint256 reputationScore + ) public onlyOwner { + if (!tokenExists[token]) { + tokenList.push(token); + tokenExists[token] = true; + } + + tokenScores[token] = Scores({ + marketStability: marketStability, + fundamentalStrength: fundamentalStrength, + risk: risk, + reputationScore: reputationScore + }); + + emit ScoresUpdated(token, marketStability, fundamentalStrength, risk, reputationScore); + } + + function getScores( + string calldata token + ) external view returns (uint256 marketStability, uint256 fundamentalStrength, uint256 risk, uint256 reputationScore) { + Scores memory s = tokenScores[token]; + return (s.marketStability, s.fundamentalStrength, s.risk, s.reputationScore); + } + + function getTokenData( + string calldata token + ) external view returns ( + uint256 marketStability, + uint256 fundamentalStrength, + uint256 risk, + uint256 reputationScore, + int64 price, + uint64 confidence, + int32 expo, + uint256 publishTime + ) { + Scores memory s = tokenScores[token]; + (int64 p, uint64 conf, int32 exp, uint256 pubTime) = _getLatestPrice(token); + return (s.marketStability, s.fundamentalStrength, s.risk, s.reputationScore, p, conf, exp, pubTime); + } + + function getAllTokens() external view returns (string[] memory) { + return tokenList; + } + + // === Owner Utilities === + function withdraw() external onlyOwner { + payable(owner).transfer(address(this).balance); + } + + receive() external payable {} +} diff --git a/entropy/sentinel_protocol/package-lock.json b/entropy/sentinel_protocol/package-lock.json new file mode 100644 index 0000000..ee0fefa --- /dev/null +++ b/entropy/sentinel_protocol/package-lock.json @@ -0,0 +1,115 @@ +{ + "name": "sentinel_protocol", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "ethers": "^6.15.0", + "fs": "^0.0.1-security" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", + "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==" + }, + "node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@types/node": { + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/aes-js": { + "version": "4.0.0-beta.5", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==" + }, + "node_modules/ethers": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.15.0.tgz", + "integrity": "sha512-Kf/3ZW54L4UT0pZtsY/rf+EkBU7Qi5nnhonjUb8yTXcxH3cdcWrV2cRyk0Xk/4jK6OoHhxxZHriyhje20If2hQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/ethers-io/" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "dependencies": { + "@adraffy/ens-normalize": "1.10.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.2", + "@types/node": "22.7.5", + "aes-js": "4.0.0-beta.5", + "tslib": "2.7.0", + "ws": "8.17.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/fs": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", + "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==" + }, + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/entropy/sentinel_protocol/package.json b/entropy/sentinel_protocol/package.json new file mode 100644 index 0000000..5ed28cb --- /dev/null +++ b/entropy/sentinel_protocol/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "ethers": "^6.15.0", + "fs": "^0.0.1-security" + } +}