diff --git a/entropy/polymarket_credit_calculation/creditContract/CreditScore.sol b/entropy/polymarket_credit_calculation/creditContract/CreditScore.sol new file mode 100644 index 00000000..ed1caacf --- /dev/null +++ b/entropy/polymarket_credit_calculation/creditContract/CreditScore.sol @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@pythnetwork/entropy-sdk-solidity/IEntropyV2.sol"; +import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol"; +import "@pythnetwork/entropy-sdk-solidity/EntropyStructsV2.sol"; + +contract CreditScore is IEntropyConsumer { + // Structs to hold Polymarket data + struct ClosedPosition { + int256 realizedPnl; + uint256 totalBought; + bytes32 asset; + } + + struct CurrentPosition { + uint256 size; + uint256 avgPrice; + int256 initialValue; + int256 currentValue; + int256 cashPnl; + int256 percentPnl; + uint256 totalBought; + int256 realizedPnl; + int256 percentRealizedPnl; + uint256 curPrice; + } + + struct UserData { + string name; + uint256 value; + ClosedPosition[] closedPositions; + CurrentPosition[] currentPositions; + } + + struct CreditScoreData { + uint256 score; + uint256 timestamp; + bytes32 entropyUsed; + bool isCalculated; + } + + // Events + event CreditScoreRequested(address indexed user, uint64 sequenceNumber); + event CreditScoreCalculated(address indexed user, uint256 score, bytes32 entropyUsed); + + // State variables + IEntropyV2 private entropy; + address private entropyProvider; + + mapping(address => UserData) public userData; + mapping(address => CreditScoreData) public creditScores; + mapping(uint64 => address) private sequenceToUser; + mapping(address => uint64) private userToSequence; + + // Constants for score calculation + uint256 private constant MAX_SCORE = 999; + uint256 private constant MIN_SCORE = 0; + uint256 private constant ENTROPY_VARIANCE_WEIGHT = 50; // Max 50 points of variance from entropy + + constructor(address _entropy, address _entropyProvider) { + entropy = IEntropyV2(_entropy); + entropyProvider = _entropyProvider; + } + + // Submit user data and request credit score calculation + function submitUserDataAndRequestScore( + string memory name, + uint256 value, + ClosedPosition[] memory closedPositions, + CurrentPosition[] memory currentPositions + ) external payable { + // Clear previous data + delete userData[msg.sender].closedPositions; + delete userData[msg.sender].currentPositions; + + // Store user data + userData[msg.sender].name = name; + userData[msg.sender].value = value; + + // Store closed positions + for (uint i = 0; i < closedPositions.length; i++) { + userData[msg.sender].closedPositions.push(closedPositions[i]); + } + + // Store current positions + for (uint i = 0; i < currentPositions.length; i++) { + userData[msg.sender].currentPositions.push(currentPositions[i]); + } + + // Request entropy for variance + uint256 fee = entropy.getFeeV2(); + require(msg.value >= fee, "Insufficient fee for entropy"); + + uint64 sequenceNumber = entropy.requestV2{ value: fee }(); + sequenceToUser[sequenceNumber] = msg.sender; + userToSequence[msg.sender] = sequenceNumber; + + emit CreditScoreRequested(msg.sender, sequenceNumber); + } + + // Calculate base credit score without entropy (for preview purposes) + function calculateBaseScore(address user) public view returns (uint256) { + UserData storage data = userData[user]; + + if (data.closedPositions.length == 0 && data.currentPositions.length == 0) { + return 500; // Default middle score for new users + } + + uint256 score = 0; + uint256 weightSum = 0; + + // 1. Calculate profit/loss metrics from closed positions (40% weight) + if (data.closedPositions.length > 0) { + int256 totalPnl = 0; + uint256 totalInvested = 0; + uint256 winCount = 0; + + for (uint i = 0; i < data.closedPositions.length; i++) { + totalPnl += data.closedPositions[i].realizedPnl; + totalInvested += data.closedPositions[i].totalBought; + if (data.closedPositions[i].realizedPnl > 0) { + winCount++; + } + } + + // Win rate (0-200 points) + uint256 winRate = (winCount * 200) / data.closedPositions.length; + score += winRate; + + // Profit ratio (0-200 points) + if (totalInvested > 0) { + if (totalPnl > 0) { + uint256 profitRatio = (uint256(totalPnl) * 200) / totalInvested; + if (profitRatio > 200) profitRatio = 200; // Cap at 200 + score += profitRatio; + } else { + // Negative PnL reduces score + uint256 lossRatio = (uint256(-totalPnl) * 100) / totalInvested; + if (lossRatio > 200) lossRatio = 200; + score += 0; // No additional score for losses + } + } + weightSum += 400; + } + + // 2. Current positions performance (30% weight) + if (data.currentPositions.length > 0) { + int256 currentTotalPnl = 0; + uint256 currentTotalInvested = 0; + + for (uint i = 0; i < data.currentPositions.length; i++) { + currentTotalPnl += data.currentPositions[i].cashPnl; + currentTotalInvested += data.currentPositions[i].totalBought; + } + + // Current position health (0-300 points) + if (currentTotalInvested > 0) { + if (currentTotalPnl >= 0) { + uint256 currentRatio = (uint256(currentTotalPnl) * 150) / currentTotalInvested; + if (currentRatio > 150) currentRatio = 150; + score += 150 + currentRatio; // Base 150 + up to 150 bonus + } else { + // Losses reduce from base + uint256 lossRatio = (uint256(-currentTotalPnl) * 150) / currentTotalInvested; + if (lossRatio > 150) lossRatio = 150; + score += (150 - lossRatio); + } + } else { + score += 150; // Neutral if no current positions + } + weightSum += 300; + } + + // 3. Portfolio value consideration (20% weight) + if (data.value > 0) { + // Scale based on value (logarithmic scale) + uint256 valueScore = 0; + if (data.value >= 1000000) { + // 1M+ + valueScore = 200; + } else if (data.value >= 500000) { + // 500k+ + valueScore = 180; + } else if (data.value >= 100000) { + // 100k+ + valueScore = 150; + } else if (data.value >= 50000) { + // 50k+ + valueScore = 120; + } else if (data.value >= 10000) { + // 10k+ + valueScore = 100; + } else { + valueScore = (data.value * 100) / 10000; // Linear scale below 10k + } + score += valueScore; + weightSum += 200; + } + + // 4. Activity bonus (10% weight) + uint256 totalTrades = data.closedPositions.length + data.currentPositions.length; + uint256 activityScore = 0; + if (totalTrades >= 20) { + activityScore = 100; + } else if (totalTrades >= 10) { + activityScore = 80; + } else if (totalTrades >= 5) { + activityScore = 60; + } else if (totalTrades > 0) { + activityScore = (totalTrades * 60) / 5; + } + score += activityScore; + weightSum += 100; + + // Normalize to 0-949 range (leaving room for entropy variance) + if (weightSum > 0) { + score = (score * 949) / weightSum; + } + + return score; + } + + // Entropy callback implementation + function entropyCallback(uint64 sequenceNumber, address, bytes32 randomNumber) internal override { + address user = sequenceToUser[sequenceNumber]; + require(user != address(0), "Invalid sequence number"); + + // Calculate base score + uint256 baseScore = calculateBaseScore(user); + + // Add entropy-based variance (±ENTROPY_VARIANCE_WEIGHT points) + uint256 entropyFactor = uint256(randomNumber) % (ENTROPY_VARIANCE_WEIGHT * 2 + 1); + int256 variance = int256(entropyFactor) - int256(ENTROPY_VARIANCE_WEIGHT); + + // Calculate final score with bounds checking + int256 finalScoreInt = int256(baseScore) + variance; + uint256 finalScore; + + if (finalScoreInt < 0) { + finalScore = 0; + } else if (finalScoreInt > int256(MAX_SCORE)) { + finalScore = MAX_SCORE; + } else { + finalScore = uint256(finalScoreInt); + } + + // Store the calculated score + creditScores[user] = CreditScoreData({ + score: finalScore, + timestamp: block.timestamp, + entropyUsed: randomNumber, + isCalculated: true + }); + + emit CreditScoreCalculated(user, finalScore, randomNumber); + + // Clean up mappings + delete sequenceToUser[sequenceNumber]; + delete userToSequence[user]; + } + + // Required by IEntropyConsumer + function getEntropy() internal view override returns (address) { + return address(entropy); + } + + // Get the fee required for entropy + function getEntropyFee() public view returns (uint256) { + return entropy.getFeeV2(); + } + + // Get user's credit score + function getCreditScore(address user) external view returns (uint256 score, uint256 timestamp, bool isCalculated) { + CreditScoreData memory data = creditScores[user]; + return (data.score, data.timestamp, data.isCalculated); + } + + // Get user's pending sequence number + function getPendingSequence(address user) external view returns (uint64) { + return userToSequence[user]; + } + + // Check if user has pending score calculation + function hasPendingCalculation(address user) external view returns (bool) { + return userToSequence[user] != 0; + } + + receive() external payable {} +} diff --git a/entropy/polymarket_credit_calculation/creditEngine/README.MD b/entropy/polymarket_credit_calculation/creditEngine/README.MD new file mode 100644 index 00000000..4c5c6131 --- /dev/null +++ b/entropy/polymarket_credit_calculation/creditEngine/README.MD @@ -0,0 +1,187 @@ +# Credit Score Engine with Pyth Entropy + +This module calculates credit scores (0-999) for traders based on their Polymarket trading data, using Pyth Entropy for randomized variance to ensure fairness. + +## 📍 Deployed Contract + +**Base Sepolia:** `0x2e951d54caD20ff3CeA95bFc79CF11FfC62E0134` + +## 🚀 Quick Start + +### Calculate Credit Score from JSON + +```bash +# Using excellent trader sample data +JSON_FILE=scripts/creditEngine/sampleData/excellentTrader.json npx hardhat run scripts/creditEngine/runCreditScore.ts --network baseSepolia + +# Using poor trader sample data +JSON_FILE=scripts/creditEngine/sampleData/poorTrader.json npx hardhat run scripts/creditEngine/runCreditScore.ts --network baseSepolia + +# Using average trader sample data +JSON_FILE=scripts/creditEngine/sampleData/averageTrader.json npx hardhat run scripts/creditEngine/runCreditScore.ts --network baseSepolia +``` + +### Check Existing Score + +```bash +# Check the current user's score +npx hardhat run scripts/creditEngine/checkScoreSimple.ts --network baseSepolia +``` + +## 📊 How Credit Scores are Calculated + +The credit score is calculated using multiple factors: + +1. **Closed Positions Performance (40% weight)** + - Win rate + - Profit/loss ratio + - Historical trading success + +2. **Current Positions Health (30% weight)** + - Current P&L + - Position management + - Risk exposure + +3. **Portfolio Value (20% weight)** + - Total value under management + - Logarithmic scale for fairness + +4. **Trading Activity (10% weight)** + - Number of trades + - Market participation + +5. **Entropy Variance (±50 points)** + - Random adjustment using Pyth Entropy + - Prevents gaming the system + - Ensures fairness + +## 🏆 Score Ratings + +| Score Range | Rating | Description | +|-------------|--------|-------------| +| 850-999 | 🌟 EXCEPTIONAL | Top tier trader with excellent track record | +| 750-849 | ✨ EXCELLENT | Very strong trading performance | +| 650-749 | 👍 GOOD | Solid trader with positive results | +| 550-649 | 📊 FAIR | Average trading performance | +| 450-549 | ⚠️ BELOW AVERAGE | Needs improvement | +| 300-449 | ⚡ POOR | Significant losses or low activity | +| 0-299 | 🔴 VERY POOR | High risk profile | + +## 📁 JSON Data Format + +Create a JSON file with the following structure: + +```json +{ + "user": { + "name": "TraderName", + "value": "100000" // Portfolio value in USD + }, + "closedPositions": [ + { + "realizedPnl": "50000", // Profit/loss from closed position + "totalBought": "100000" // Amount invested + } + ], + "currentPositions": [ + { + "size": "50000", + "avgPrice": "1", + "initialValue": "50000", + "currentValue": "55000", + "cashPnl": "5000", + "percentPnl": "10", + "totalBought": "50000", + "realizedPnl": "0", + "percentRealizedPnl": "0", + "curPrice": "1100000" + } + ] +} +``` + +## ⏱️ Performance Metrics + +Typical execution times: +- **Data Preparation:** ~0.5-1s +- **Transaction Submission:** ~2-3s +- **Entropy Callback:** ~0.2-4s (varies) +- **Total Time:** ~3-8s + +## 🔧 Technical Details + +### Smart Contract Features +- Uses Pyth Entropy for verifiable randomness +- Stores scores on-chain with timestamps +- Calculates base score deterministically +- Adds ±50 points variance via entropy +- Supports re-calculation with new data + +### Scripts Available + +1. **`deployCreditScore.ts`** - Deploy the contract +2. **`runCreditScore.ts`** - Calculate score from JSON data +3. **`checkScoreSimple.ts`** - Check existing scores +4. **`submitPolymarketData.ts`** - Submit hardcoded example data + +### Sample Data Files + +- **`excellentTrader.json`** - High performing trader (score ~750-850) +- **`averageTrader.json`** - Moderate performance (score ~500-650) +- **`poorTrader.json`** - Poor performance (score ~200-400) + +## 🔑 Configuration + +The scripts use a hardcoded private key for testing: +``` +0x0ab2a1d8d5a410c75b6365c1b544117a960aa4cc3459cf2adfea8cd6fc9e14ce +``` + +**⚠️ WARNING:** This is for testing only. Never use this key for production or with real funds. + +## 📈 Example Output + +``` +════════════════════════════════════════════════════════════ + ✅ CREDIT SCORE CALCULATION COMPLETE! +════════════════════════════════════════════════════════════ + +┌──────────────────────────────────────┐ +│ 📊 FINAL CREDIT SCORE: 791 / 999 │ +└──────────────────────────────────────┘ + +📋 Score Breakdown: + Base Score (Performance): 763 + Entropy Variance: +28 + ───────────────────────── + Final Score: 791 + +✨ Credit Rating: EXCELLENT + Very strong trading performance + +📈 Trading Performance: + Win Rate: 100.0% + Total P&L: $770,000 + ROI: 81.5% +``` + +## 🌐 View on Explorer + +View the deployed contract on BaseScan: +https://sepolia.basescan.org/address/0x2e951d54caD20ff3CeA95bFc79CF11FfC62E0134 + +## 🔮 Pyth Entropy Integration + +The contract uses Pyth Entropy for randomness: +- **Entropy Contract:** `0x41c9e39574f40ad34c79f1c99b66a45efb830d4c` +- **Default Provider:** `0x6CC14824Ea2918f5De5C2f75A9Da968ad4BD6344` +- **Gas Limit:** 500,000 +- **Fee:** ~0.000015 ETH per calculation + +## 💡 Use Cases + +1. **DeFi Lending** - Assess trader creditworthiness +2. **Copy Trading** - Identify successful traders to follow +3. **Risk Assessment** - Evaluate portfolio risk profiles +4. **Gamification** - Create trader leaderboards +5. **Reputation Systems** - Build on-chain reputation \ No newline at end of file diff --git a/entropy/polymarket_credit_calculation/creditEngine/checkCreditScore.ts b/entropy/polymarket_credit_calculation/creditEngine/checkCreditScore.ts new file mode 100644 index 00000000..87ea99f6 --- /dev/null +++ b/entropy/polymarket_credit_calculation/creditEngine/checkCreditScore.ts @@ -0,0 +1,92 @@ +import { ethers } from "hardhat"; + +async function main() { + const contractAddress = process.env.CREDIT_SCORE_CONTRACT || process.argv[2]; + const userAddress = process.argv[3]; + + if (!contractAddress || !userAddress) { + console.error( + "Usage: npx hardhat run scripts/creditEngine/checkCreditScore.ts " + ); + process.exit(1); + } + + console.log("Checking credit score for user:", userAddress); + console.log("Contract address:", contractAddress); + + // Connect to provider (read-only, no signer needed) + const provider = new ethers.JsonRpcProvider("https://sepolia.base.org"); + + // Create a dummy wallet just for getting the contract factory with proper typing + const dummyWallet = ethers.Wallet.createRandom(); + const signer = dummyWallet.connect(provider); + + // Get contract instance with proper typing + const CreditScore = await ethers.getContractFactory("CreditScore", signer); + const creditScore = CreditScore.attach(contractAddress); + + // Check if there's a pending calculation + const hasPending = await creditScore.hasPendingCalculation(userAddress); + if (hasPending) { + const sequenceNumber = await creditScore.getPendingSequence(userAddress); + console.log("\n⏳ User has a pending credit score calculation"); + console.log("Sequence number:", sequenceNumber.toString()); + console.log("Please wait for the entropy callback to complete..."); + } + + // Get the credit score + const [score, timestamp, isCalculated] = await creditScore.getCreditScore(userAddress); + + if (isCalculated) { + console.log("\n✅ Credit Score Calculated!"); + console.log("====================================="); + console.log("Credit Score:", score.toString(), "/ 999"); + console.log("Calculated at:", new Date(Number(timestamp) * 1000).toISOString()); + console.log("====================================="); + + // Provide interpretation + let rating = ""; + const scoreNum = Number(score); + if (scoreNum >= 850) { + rating = "🌟 Exceptional - Top tier trader with excellent track record"; + } else if (scoreNum >= 750) { + rating = "✨ Excellent - Very strong trading performance"; + } else if (scoreNum >= 650) { + rating = "👍 Good - Solid trader with positive results"; + } else if (scoreNum >= 550) { + rating = "📊 Fair - Average trading performance"; + } else if (scoreNum >= 450) { + rating = "⚠️ Below Average - Needs improvement"; + } else if (scoreNum >= 300) { + rating = "⚡ Poor - Significant losses or low activity"; + } else { + rating = "🔴 Very Poor - High risk profile"; + } + + console.log("\nRating:", rating); + } else { + console.log("\n❌ No credit score calculated yet for this user"); + console.log("User needs to submit their Polymarket data first"); + } + + // Show base score for comparison + try { + const baseScore = await creditScore.calculateBaseScore(userAddress); + if (Number(baseScore) > 0 || isCalculated) { + console.log("\nBase score (without entropy variance):", baseScore.toString()); + if (isCalculated) { + const variance = Number(score) - Number(baseScore); + console.log("Entropy variance applied:", variance > 0 ? `+${variance}` : variance.toString()); + } + } + } catch (error) { + // User might not have data submitted + } +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/entropy/polymarket_credit_calculation/creditEngine/checkScoreSimple.ts b/entropy/polymarket_credit_calculation/creditEngine/checkScoreSimple.ts new file mode 100644 index 00000000..42a062d3 --- /dev/null +++ b/entropy/polymarket_credit_calculation/creditEngine/checkScoreSimple.ts @@ -0,0 +1,85 @@ +import { ethers } from "hardhat"; + +async function main() { + const contractAddress = "0x2e951d54caD20ff3CeA95bFc79CF11FfC62E0134"; + const userAddress = "0x2B2A778e2e61c8436D5161cC63b973d6c64B00D3"; + + console.log("Checking credit score for user:", userAddress); + console.log("Contract address:", contractAddress); + + // Connect to provider (read-only, no signer needed) + const provider = new ethers.JsonRpcProvider("https://sepolia.base.org"); + + // Create a dummy wallet just for getting the contract factory with proper typing + const dummyWallet = ethers.Wallet.createRandom(); + const signer = dummyWallet.connect(provider); + + // Get contract instance with proper typing + const CreditScore = await ethers.getContractFactory("CreditScore", signer); + const creditScore = CreditScore.attach(contractAddress); + + // Check if there's a pending calculation + const hasPending = await creditScore.hasPendingCalculation(userAddress); + if (hasPending) { + const sequenceNumber = await creditScore.getPendingSequence(userAddress); + console.log("\n⏳ User has a pending credit score calculation"); + console.log("Sequence number:", sequenceNumber.toString()); + console.log("Please wait for the entropy callback to complete..."); + } + + // Get the credit score + const [score, timestamp, isCalculated] = await creditScore.getCreditScore(userAddress); + + if (isCalculated) { + console.log("\n✅ Credit Score Calculated!"); + console.log("====================================="); + console.log("Credit Score:", score.toString(), "/ 999"); + console.log("Calculated at:", new Date(Number(timestamp) * 1000).toISOString()); + console.log("====================================="); + + // Provide interpretation + let rating = ""; + const scoreNum = Number(score); + if (scoreNum >= 850) { + rating = "🌟 Exceptional - Top tier trader with excellent track record"; + } else if (scoreNum >= 750) { + rating = "✨ Excellent - Very strong trading performance"; + } else if (scoreNum >= 650) { + rating = "👍 Good - Solid trader with positive results"; + } else if (scoreNum >= 550) { + rating = "📊 Fair - Average trading performance"; + } else if (scoreNum >= 450) { + rating = "⚠️ Below Average - Needs improvement"; + } else if (scoreNum >= 300) { + rating = "⚡ Poor - Significant losses or low activity"; + } else { + rating = "🔴 Very Poor - High risk profile"; + } + + console.log("\nRating:", rating); + } else { + console.log("\n❌ No credit score calculated yet for this user"); + console.log("User needs to submit their Polymarket data first"); + } + + // Show base score for comparison + try { + const baseScore = await creditScore.calculateBaseScore(userAddress); + if (Number(baseScore) > 0 || isCalculated) { + console.log("\nBase score (without entropy variance):", baseScore.toString()); + if (isCalculated) { + const variance = Number(score) - Number(baseScore); + console.log("Entropy variance applied:", variance > 0 ? `+${variance}` : variance.toString()); + } + } + } catch (error) { + // User might not have data submitted + } +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/entropy/polymarket_credit_calculation/creditEngine/deployCreditScore.ts b/entropy/polymarket_credit_calculation/creditEngine/deployCreditScore.ts new file mode 100644 index 00000000..23d71225 --- /dev/null +++ b/entropy/polymarket_credit_calculation/creditEngine/deployCreditScore.ts @@ -0,0 +1,53 @@ +import { ethers } from "hardhat"; + +async function main() { + console.log("Deploying CreditScore contract to Base Sepolia..."); + + // Base Sepolia Entropy contract details from cc.md + const ENTROPY_CONTRACT = "0x41c9e39574f40ad34c79f1c99b66a45efb830d4c"; + const ENTROPY_PROVIDER = "0x6CC14824Ea2918f5De5C2f75A9Da968ad4BD6344"; // Default provider for Base Sepolia + + const PRIVATE_KEY = "0x0ab2a1d8d5a410c75b6365c1b544117a960aa4cc3459cf2adfea8cd6fc9e14ce"; + + // Create wallet from private key + const wallet = new ethers.Wallet(PRIVATE_KEY); + + // Connect wallet to provider + const provider = new ethers.JsonRpcProvider("https://sepolia.base.org"); + const signer = wallet.connect(provider); + + console.log("Deploying from address:", signer.address); + + // Get contract factory + const CreditScore = await ethers.getContractFactory("CreditScore", signer); + + // Deploy contract + console.log("Deploying CreditScore contract..."); + const creditScore = await CreditScore.deploy(ENTROPY_CONTRACT, ENTROPY_PROVIDER); + + await creditScore.waitForDeployment(); + const contractAddress = await creditScore.getAddress(); + + console.log("CreditScore deployed to:", contractAddress); + console.log("Entropy Contract:", ENTROPY_CONTRACT); + console.log("Entropy Provider:", ENTROPY_PROVIDER); + console.log("Deployer:", signer.address); + + // Verify deployment by reading the entropy fee + try { + const deployedContract = await ethers.getContractAt("CreditScore", contractAddress, signer); + const entropyFee = await deployedContract.getEntropyFee(); + console.log("Entropy Fee:", ethers.formatEther(entropyFee), "ETH"); + } catch (error) { + console.log("Note: Could not read entropy fee, but contract is deployed successfully"); + } + + return contractAddress; +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/entropy/polymarket_credit_calculation/creditEngine/polymarketCreditFlare.ts b/entropy/polymarket_credit_calculation/creditEngine/polymarketCreditFlare.ts new file mode 100644 index 00000000..ecf72ce6 --- /dev/null +++ b/entropy/polymarket_credit_calculation/creditEngine/polymarketCreditFlare.ts @@ -0,0 +1,230 @@ +// polymarketCreditFlare.ts + +export interface FlareClosedPosition { + realizedPnl: string; + totalBought: string; + asset: string; +} + +export interface FlareCurrentPosition { + size: string; + avgPrice: string; + initialValue: string; + currentValue: string; + cashPnl: string; + percentPnl: string; + totalBought: string; + realizedPnl: string; + percentRealizedPnl: string; + curPrice: string; +} + +export interface FlareUser { + name: string; + value: string; +} + +export interface FlareScoreInput { + user: FlareUser; + closedPositions: FlareClosedPosition[]; + currentPositions: FlareCurrentPosition[]; +} + +export interface CreditResult { + userName: string; + userId: string; + pd: number; // probability of bad event + creditScore: number; // 300–850 style + ltv: number; // 0–1 + maxLoan: number; // in “USDC” units (same units as pnl/values) + features: { + ROI_real: number; + WinRate: number; + Sharpe: number; + ProfitFactorFeature: number; + DD_open: number; + HHI_open: number; + DeadShare: number; + V_open: number; + V_realized_plus: number; + V_eff: number; + }; + subscores: { + performance: number; + risk: number; + }; +} + +// ---- Core function ---- + +export function computeCreditFromFlare(score: FlareScoreInput): CreditResult { + const eps = 1e-8; + + // ---------- 1. Parse and normalize closed positions ---------- + + const closed = (score.closedPositions || []).map((cp) => { + const realizedPnl = Number(cp.realizedPnl) || 0; + const totalBought = Number(cp.totalBought) || 0; + return { realizedPnl, totalBought }; + }); + + const N_closed = closed.length; + + let totalRealizedPnl = 0; + let totalBoughtAll = 0; + const rj: number[] = []; + + for (const cp of closed) { + totalRealizedPnl += cp.realizedPnl; + totalBoughtAll += cp.totalBought; + const cost = Math.max(1, cp.totalBought); + const r = cp.realizedPnl / cost; + rj.push(r); + } + + const ROI_real = totalBoughtAll <= 0 ? 0 : totalRealizedPnl / Math.max(1, totalBoughtAll); + + const WinRate = N_closed === 0 ? 0.5 : closed.filter((cp) => cp.realizedPnl > 0).length / Math.max(1, N_closed); + + let rMean = 0; + let rVar = 0; + if (N_closed > 0) { + rMean = rj.reduce((a, b) => a + b, 0) / N_closed; + if (N_closed > 1) { + rVar = rj.reduce((sum, r) => sum + (r - rMean) * (r - rMean), 0) / N_closed; + } + } + const rStd = Math.sqrt(rVar + eps); + const Sharpe = rMean / (rStd + eps); + + // Profit factor: sum positive PnL / sum negative |PnL|, then clamp & transform + let Gplus = 0; + let Gminus = 0; + for (const cp of closed) { + if (cp.realizedPnl > 0) Gplus += cp.realizedPnl; + else if (cp.realizedPnl < 0) Gminus += -cp.realizedPnl; + } + const PF_raw = Gplus / (Gminus + eps); + const PF_clamped = Math.min(PF_raw, 10); // cap insane PF + const ProfitFactorFeature = PF_clamped - 1; // center around ~0 + + // ---------- 2. Parse and normalize current positions ---------- + + const current = (score.currentPositions || []).map((cp) => { + const initialValue = Number(cp.initialValue) || 0; + const currentValue = Number(cp.currentValue) || 0; + const percentPnl = Number(cp.percentPnl) || 0; + return { initialValue, currentValue, percentPnl }; + }); + + let I_open = 0; + let C_open = 0; + for (const pos of current) { + I_open += pos.initialValue; + C_open += pos.currentValue; + } + + const DD_open = I_open <= 0 ? 0 : (C_open - I_open) / Math.max(1, I_open); + + const V_curr = current.reduce((sum, pos) => sum + pos.currentValue, 0); + let HHI_open = 0; + if (V_curr > 0) { + HHI_open = current.reduce((sum, pos) => { + const share = pos.currentValue / V_curr; + return sum + share * share; + }, 0); + } + + // DeadShare = fraction of initial exposure at -100% PnL + let deadInitSum = 0; + for (const pos of current) { + if (pos.percentPnl <= -100) { + deadInitSum += pos.initialValue; + } + } + const DeadShare = I_open <= 0 ? 0 : deadInitSum / Math.max(1, I_open); + + const V_open = C_open; + const V_realized_plus = closed.reduce((sum, cp) => sum + Math.max(0, cp.realizedPnl), 0); + + // Weight for realized PnL contributing to effective collateral + const lambdaRealized = 0.25; + const V_eff = V_open + lambdaRealized * V_realized_plus; + + // ---------- 3. Subscores (no standardization, but centered transforms) ---------- + + // Performance score: + // Perf = 1.0*ROI_real + 0.8*(WinRate-0.5) + 0.4*Sharpe + 0.15*ProfitFactorFeature + const PerfScore = 1.0 * ROI_real + 0.8 * (WinRate - 0.5) + 0.4 * Sharpe + 0.15 * ProfitFactorFeature; + + // Risk score: + // Risk = 1.5*(-DD_open) + 1.0*HHI_open + 1.0*DeadShare + const RiskScore = 1.5 * -DD_open + 1.0 * HHI_open + 1.0 * DeadShare; + + // ---------- 4. Logistic PD model ---------- + + // Calibrated coefficients (heuristic but numerically tuned): + const beta0 = -0.9; + const betaPerf = -1.0; // higher performance -> lower PD + const betaRisk = 0.7; // higher risk -> higher PD + + const z = beta0 + betaPerf * PerfScore + betaRisk * RiskScore; + const PD = 1 / (1 + Math.exp(-z)); + + // ---------- 5. Score mapping (FICO-style with PDO = 50, ScoreRef = 650 at PD=0.2) ---------- + + const PDO = 50; + const Factor = PDO / Math.log(2); // ~72.13475 + const ScoreRef = 650; + const OddsRef = 4; // 4:1 odds (PD=0.2) + const Offset = ScoreRef - Factor * Math.log(OddsRef); // ~550 + + const oddsGood = (1 - PD) / Math.max(PD, eps); + const rawScore = Offset + Factor * Math.log(Math.max(oddsGood, eps)); + + const ScoreMin = 300; + const ScoreMax = 850; + const creditScore = Math.min(ScoreMax, Math.max(ScoreMin, rawScore)); + + // ---------- 6. LTV & Max Loan ---------- + + const G = Math.max(0, 1 - PD); // goodness + + const LTV_min = 0.25; + const LTV_max = 0.8; + const gamma = 1.5; + + const ltv = LTV_min + (LTV_max - LTV_min) * Math.pow(G, gamma); + + const kappa = 1.5; + const delta = 1.2; + + const collateralLoan = ltv * V_eff; + const cappedLoan = kappa * V_eff * Math.pow(G, delta); + const maxLoan = Math.max(0, Math.min(collateralLoan, cappedLoan)); + + return { + userName: score.user?.name ?? "", + userId: score.user?.value ?? "", + pd: PD, + creditScore, + ltv, + maxLoan, + features: { + ROI_real, + WinRate, + Sharpe, + ProfitFactorFeature, + DD_open, + HHI_open, + DeadShare, + V_open, + V_realized_plus, + V_eff, + }, + subscores: { + performance: PerfScore, + risk: RiskScore, + }, + }; +} diff --git a/entropy/polymarket_credit_calculation/creditEngine/runCreditScore.ts b/entropy/polymarket_credit_calculation/creditEngine/runCreditScore.ts new file mode 100644 index 00000000..34b07585 --- /dev/null +++ b/entropy/polymarket_credit_calculation/creditEngine/runCreditScore.ts @@ -0,0 +1,303 @@ +import { ethers } from "hardhat"; +import * as fs from "fs"; +import * as path from "path"; + +// Contract address on Base Sepolia +const CONTRACT_ADDRESS = "0x2e951d54caD20ff3CeA95bFc79CF11FfC62E0134"; + +// Get JSON file path from environment variable +const JSON_FILE_PATH = process.env.JSON_FILE || "scripts/creditEngine/sampleData/excellentTrader.json"; + +// Interface for the JSON data structure +interface PolymarketData { + user: { + name: string; + value: string; + }; + closedPositions: Array<{ + realizedPnl: string; + totalBought: string; + asset?: string; + }>; + currentPositions: Array<{ + size: string; + avgPrice: string; + initialValue: string; + currentValue: string; + cashPnl: string; + percentPnl: string; + totalBought: string; + realizedPnl: string; + percentRealizedPnl: string; + curPrice: string; + }>; +} + +async function waitForCreditScore( + creditScore: any, + userAddress: string, + maxWaitTime: number = 60000 +): Promise { + const startTime = Date.now(); + const checkInterval = 2000; // Check every 2 seconds + + while (Date.now() - startTime < maxWaitTime) { + const [score, , isCalculated] = await creditScore.getCreditScore(userAddress); + + if (isCalculated) { + return true; + } + + // Check if still pending + const hasPending = await creditScore.hasPendingCalculation(userAddress); + if (!hasPending) { + // No pending calculation and no score - something went wrong + return false; + } + + // Wait before checking again + await new Promise((resolve) => setTimeout(resolve, checkInterval)); + } + + return false; +} + +async function main() { + const startTime = performance.now(); + + console.log("⏱️ Starting credit score calculation process...\n"); + console.log(`📁 Using JSON file: ${JSON_FILE_PATH}\n`); + + // Read and parse JSON file + let data: PolymarketData; + try { + const jsonContent = fs.readFileSync(path.resolve(JSON_FILE_PATH), "utf-8"); + data = JSON.parse(jsonContent); + console.log("✅ JSON data loaded successfully"); + console.log(`📊 User: ${data.user.name}`); + console.log(`💰 Portfolio Value: $${Number(data.user.value).toLocaleString()}`); + console.log(`📈 Closed Positions: ${data.closedPositions.length}`); + console.log(`📉 Current Positions: ${data.currentPositions.length}`); + } catch (error) { + console.error("❌ Error reading or parsing JSON file:", error); + console.error( + "\nUsage: JSON_FILE= npx hardhat run scripts/creditEngine/runCreditScore.ts --network baseSepolia" + ); + console.error( + "Example: JSON_FILE=scripts/creditEngine/sampleData/poorTrader.json npx hardhat run scripts/creditEngine/runCreditScore.ts --network baseSepolia" + ); + process.exit(1); + } + + // Hardcoded private key + const PRIVATE_KEY = "0x0ab2a1d8d5a410c75b6365c1b544117a960aa4cc3459cf2adfea8cd6fc9e14ce"; + + // Create wallet and connect to provider + const wallet = new ethers.Wallet(PRIVATE_KEY); + const provider = new ethers.JsonRpcProvider("https://sepolia.base.org"); + const signer = wallet.connect(provider); + + console.log(`\n🔑 Using address: ${signer.address}`); + console.log(`📍 Contract: ${CONTRACT_ADDRESS}`); + + // Get contract instance + const CreditScore = await ethers.getContractFactory("CreditScore", signer); + const creditScore = CreditScore.attach(CONTRACT_ADDRESS); + + // Check if user already has a calculated score + const [existingScore, existingTimestamp, hasExistingScore] = await creditScore.getCreditScore(signer.address); + if (hasExistingScore) { + console.log(`\n⚠️ User already has a credit score: ${existingScore.toString()}`); + console.log(` Calculated at: ${new Date(Number(existingTimestamp) * 1000).toISOString()}`); + console.log(" Submitting new data will recalculate the score...\n"); + } + + // Prepare the data for contract submission + const closedPositions = data.closedPositions.map((pos, index) => ({ + realizedPnl: BigInt(pos.realizedPnl), + totalBought: BigInt(pos.totalBought), + asset: pos.asset || `0x${(index + 1).toString(16).padStart(64, "0")}`, + })); + + const currentPositions = data.currentPositions.map((pos) => ({ + size: BigInt(pos.size), + avgPrice: BigInt(pos.avgPrice), + initialValue: BigInt(pos.initialValue), + currentValue: BigInt(pos.currentValue), + cashPnl: BigInt(pos.cashPnl), + percentPnl: BigInt(pos.percentPnl), + totalBought: BigInt(pos.totalBought), + realizedPnl: BigInt(pos.realizedPnl), + percentRealizedPnl: BigInt(pos.percentRealizedPnl), + curPrice: BigInt(pos.curPrice), + })); + + // Get entropy fee + const entropyFee = await creditScore.getEntropyFee(); + console.log(`💎 Entropy fee required: ${ethers.formatEther(entropyFee)} ETH`); + + // Start timing the transaction + console.log("\n📤 Submitting user data and requesting credit score calculation..."); + const submissionStartTime = performance.now(); + + const tx = await creditScore.submitUserDataAndRequestScore( + data.user.name, + BigInt(data.user.value), + closedPositions, + currentPositions, + { value: entropyFee } + ); + + console.log(`📝 Transaction hash: ${tx.hash}`); + const receipt = await tx.wait(); + const submissionTime = performance.now() - submissionStartTime; + console.log(`✅ Transaction confirmed in block: ${receipt.blockNumber}`); + console.log(`⏱️ Transaction time: ${(submissionTime / 1000).toFixed(2)} seconds`); + + // Get the base score + const baseScore = await creditScore.calculateBaseScore(signer.address); + console.log(`\n🎯 Base score (deterministic): ${baseScore.toString()}`); + + // Parse events to get sequence number + const requestEvent = receipt.logs.find((log) => { + try { + const parsed = creditScore.interface.parseLog(log); + return parsed?.name === "CreditScoreRequested"; + } catch { + return false; + } + }); + + let sequenceNumber = null; + if (requestEvent) { + const parsed = creditScore.interface.parseLog(requestEvent); + sequenceNumber = parsed.args.sequenceNumber.toString(); + console.log(`🎲 Entropy sequence number: ${sequenceNumber}`); + } + + // Wait for entropy callback + console.log("\n⏳ Waiting for Pyth Entropy callback to add variance..."); + const callbackStartTime = performance.now(); + + const success = await waitForCreditScore(creditScore, signer.address); + + if (success) { + const callbackTime = performance.now() - callbackStartTime; + + // Get the final score + const [finalScore, timestamp, isCalculated] = await creditScore.getCreditScore(signer.address); + + console.log("\n" + "═".repeat(60)); + console.log(" ✅ CREDIT SCORE CALCULATION COMPLETE!"); + console.log("═".repeat(60)); + + // Display score breakdown + const scoreNum = Number(finalScore); + const baseNum = Number(baseScore); + const variance = scoreNum - baseNum; + + console.log(`\n┌──────────────────────────────────────┐`); + console.log(`│ 📊 FINAL CREDIT SCORE: ${finalScore.toString().padEnd(4)} / 999 │`); + console.log(`└──────────────────────────────────────┘`); + + console.log(`\n📋 Score Breakdown:`); + console.log(` Base Score (Performance): ${baseScore.toString()}`); + console.log(` Entropy Variance: ${variance > 0 ? "+" : ""}${variance}`); + console.log(` ─────────────────────────`); + console.log(` Final Score: ${finalScore.toString()}`); + + // Provide detailed rating + let rating = ""; + let description = ""; + let emoji = ""; + + if (scoreNum >= 850) { + rating = "EXCEPTIONAL"; + emoji = "🌟"; + description = "Top tier trader with excellent track record"; + } else if (scoreNum >= 750) { + rating = "EXCELLENT"; + emoji = "✨"; + description = "Very strong trading performance"; + } else if (scoreNum >= 650) { + rating = "GOOD"; + emoji = "👍"; + description = "Solid trader with positive results"; + } else if (scoreNum >= 550) { + rating = "FAIR"; + emoji = "📊"; + description = "Average trading performance"; + } else if (scoreNum >= 450) { + rating = "BELOW AVERAGE"; + emoji = "⚠️"; + description = "Needs improvement"; + } else if (scoreNum >= 300) { + rating = "POOR"; + emoji = "⚡"; + description = "Significant losses or low activity"; + } else { + rating = "VERY POOR"; + emoji = "🔴"; + description = "High risk profile"; + } + + console.log(`\n${emoji} Credit Rating: ${rating}`); + console.log(` ${description}`); + + // Display performance metrics + if (data.closedPositions.length > 0) { + let totalPnl = 0; + let totalInvested = 0; + let winCount = 0; + + for (const pos of data.closedPositions) { + const pnl = Number(pos.realizedPnl); + totalPnl += pnl; + totalInvested += Number(pos.totalBought); + if (pnl > 0) winCount++; + } + + const winRate = ((winCount / data.closedPositions.length) * 100).toFixed(1); + const roi = totalInvested > 0 ? ((totalPnl / totalInvested) * 100).toFixed(1) : "0.0"; + + console.log(`\n📈 Trading Performance:`); + console.log(` Win Rate: ${winRate}%`); + console.log(` Total P&L: $${totalPnl.toLocaleString()}`); + console.log(` ROI: ${roi}%`); + } + + // Timing information + const totalTime = performance.now() - startTime; + + console.log("\n" + "═".repeat(60)); + console.log(" ⏱️ TIMING BREAKDOWN"); + console.log("═".repeat(60)); + console.log(` Data Preparation: ${((submissionStartTime - startTime) / 1000).toFixed(2)}s`); + console.log(` Transaction Submission: ${(submissionTime / 1000).toFixed(2)}s`); + console.log(` Entropy Callback Wait: ${(callbackTime / 1000).toFixed(2)}s`); + console.log(` ─────────────────────────`); + console.log(` Total Time: ${(totalTime / 1000).toFixed(2)}s`); + + console.log("\n📅 Timestamp:", new Date(Number(timestamp) * 1000).toISOString()); + console.log("🔗 View on BaseScan: https://sepolia.basescan.org/address/" + CONTRACT_ADDRESS); + } else { + console.error("\n❌ Failed to receive credit score after waiting 60 seconds"); + console.error("The entropy callback may have failed or is taking longer than expected"); + + const hasPending = await creditScore.hasPendingCalculation(signer.address); + if (hasPending) { + console.log("\n⏳ Calculation is still pending. You can check later using:"); + console.log(` npx hardhat run scripts/creditEngine/checkScoreSimple.ts --network baseSepolia`); + } + + const totalTime = performance.now() - startTime; + console.log(`\n⏱️ Total execution time: ${(totalTime / 1000).toFixed(2)} seconds`); + } +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error("\n❌ Error:", error); + process.exit(1); + }); diff --git a/entropy/polymarket_credit_calculation/creditEngine/sampleData/averageTrader.json b/entropy/polymarket_credit_calculation/creditEngine/sampleData/averageTrader.json new file mode 100644 index 00000000..77155457 --- /dev/null +++ b/entropy/polymarket_credit_calculation/creditEngine/sampleData/averageTrader.json @@ -0,0 +1,58 @@ +{ + "user": { + "name": "AverageTrader", + "value": "50000" + }, + "closedPositions": [ + { + "realizedPnl": "15000", + "totalBought": "30000" + }, + { + "realizedPnl": "-5000", + "totalBought": "20000" + }, + { + "realizedPnl": "8000", + "totalBought": "25000" + }, + { + "realizedPnl": "-3000", + "totalBought": "15000" + }, + { + "realizedPnl": "12000", + "totalBought": "35000" + }, + { + "realizedPnl": "6000", + "totalBought": "18000" + } + ], + "currentPositions": [ + { + "size": "25000", + "avgPrice": "1", + "initialValue": "25000", + "currentValue": "26500", + "cashPnl": "1500", + "percentPnl": "6", + "totalBought": "25000", + "realizedPnl": "0", + "percentRealizedPnl": "0", + "curPrice": "1060000" + }, + { + "size": "20000", + "avgPrice": "1", + "initialValue": "20000", + "currentValue": "19000", + "cashPnl": "-1000", + "percentPnl": "-5", + "totalBought": "20000", + "realizedPnl": "0", + "percentRealizedPnl": "0", + "curPrice": "950000" + } + ] +} diff --git a/entropy/polymarket_credit_calculation/creditEngine/sampleData/excellentTrader.json b/entropy/polymarket_credit_calculation/creditEngine/sampleData/excellentTrader.json new file mode 100644 index 00000000..c0e29ccc --- /dev/null +++ b/entropy/polymarket_credit_calculation/creditEngine/sampleData/excellentTrader.json @@ -0,0 +1,66 @@ +{ + "user": { + "name": "ExcellentTrader", + "value": "500000" + }, + "closedPositions": [ + { + "realizedPnl": "150000", + "totalBought": "200000" + }, + { + "realizedPnl": "80000", + "totalBought": "100000" + }, + { + "realizedPnl": "120000", + "totalBought": "150000" + }, + { + "realizedPnl": "95000", + "totalBought": "110000" + }, + { + "realizedPnl": "70000", + "totalBought": "80000" + }, + { + "realizedPnl": "110000", + "totalBought": "130000" + }, + { + "realizedPnl": "60000", + "totalBought": "75000" + }, + { + "realizedPnl": "85000", + "totalBought": "100000" + } + ], + "currentPositions": [ + { + "size": "100000", + "avgPrice": "1", + "initialValue": "100000", + "currentValue": "125000", + "cashPnl": "25000", + "percentPnl": "25", + "totalBought": "100000", + "realizedPnl": "0", + "percentRealizedPnl": "0", + "curPrice": "1250000" + }, + { + "size": "80000", + "avgPrice": "1", + "initialValue": "80000", + "currentValue": "95000", + "cashPnl": "15000", + "percentPnl": "19", + "totalBought": "80000", + "realizedPnl": "0", + "percentRealizedPnl": "0", + "curPrice": "1187500" + } + ] +} diff --git a/entropy/polymarket_credit_calculation/creditEngine/sampleData/poorTrader.json b/entropy/polymarket_credit_calculation/creditEngine/sampleData/poorTrader.json new file mode 100644 index 00000000..2a8fa46e --- /dev/null +++ b/entropy/polymarket_credit_calculation/creditEngine/sampleData/poorTrader.json @@ -0,0 +1,42 @@ +{ + "user": { + "name": "PoorTrader", + "value": "5000" + }, + "closedPositions": [ + { + "realizedPnl": "-50000", + "totalBought": "100000" + }, + { + "realizedPnl": "-30000", + "totalBought": "80000" + }, + { + "realizedPnl": "5000", + "totalBought": "50000" + }, + { + "realizedPnl": "-45000", + "totalBought": "90000" + }, + { + "realizedPnl": "-20000", + "totalBought": "40000" + } + ], + "currentPositions": [ + { + "size": "10000", + "avgPrice": "1", + "initialValue": "10000", + "currentValue": "3000", + "cashPnl": "-7000", + "percentPnl": "-70", + "totalBought": "10000", + "realizedPnl": "0", + "percentRealizedPnl": "0", + "curPrice": "300000" + } + ] +} diff --git a/entropy/polymarket_credit_calculation/creditEngine/submitAndGetScore.ts b/entropy/polymarket_credit_calculation/creditEngine/submitAndGetScore.ts new file mode 100644 index 00000000..ec3227a8 --- /dev/null +++ b/entropy/polymarket_credit_calculation/creditEngine/submitAndGetScore.ts @@ -0,0 +1,222 @@ +import { ethers } from "hardhat"; + +interface PolymarketData { + user?: { + name: string; + value: string; + }; + closedPositions?: Array<{ + realizedPnl: string; + totalBought: string; + }>; + currentPositions?: Array<{ + size: string; + avgPrice: string; + initialValue: string; + currentValue: string; + cashPnl: string; + percentPnl: string; + totalBought: string; + realizedPnl: string; + percentRealizedPnl: string; + curPrice: string; + }>; +} + +async function main() { + // Get parameters + const contractAddress = process.env.CREDIT_SCORE_CONTRACT || "0x18D4EE2813d4eb63cC89DC82A8bFe30B482944ed"; + const polymarketDataStr = process.env.POLYMARKET_DATA || process.argv[2]; + + if (!polymarketDataStr) { + console.error("Please provide Polymarket data as JSON string (via POLYMARKET_DATA env or as argument)"); + process.exit(1); + } + + let polymarketData: PolymarketData; + try { + polymarketData = JSON.parse(polymarketDataStr); + } catch (error) { + console.error("Invalid JSON data provided"); + process.exit(1); + } + + console.log("📊 Submitting Polymarket data to Credit Score contract..."); + console.log("Contract address:", contractAddress); + console.log("User:", polymarketData.user?.name || "Unknown"); + console.log("Portfolio Value:", polymarketData.user?.value || "0"); + + // Use the same private key for consistency + const PRIVATE_KEY = "0x0ab2a1d8d5a410c75b6365c1b544117a960aa4cc3459cf2adfea8cd6fc9e14ce"; + + // Create wallet from private key + const wallet = new ethers.Wallet(PRIVATE_KEY); + + // Connect wallet to provider + const provider = new ethers.JsonRpcProvider("https://sepolia.base.org"); + const signer = wallet.connect(provider); + + console.log("Wallet address:", signer.address); + + // Get contract instance + const CreditScore = await ethers.getContractFactory("CreditScore", signer); + const creditScore = CreditScore.attach(contractAddress); + + // Prepare the data for submission + const closedPositions = (polymarketData.closedPositions || []).map((pos) => ({ + realizedPnl: BigInt(pos.realizedPnl), + totalBought: BigInt(pos.totalBought), + asset: ethers.zeroPadValue("0x00", 32), // Placeholder asset + })); + + const currentPositions = (polymarketData.currentPositions || []).map((pos) => ({ + size: BigInt(pos.size), + avgPrice: BigInt(pos.avgPrice), + initialValue: BigInt(pos.initialValue), + currentValue: BigInt(pos.currentValue), + cashPnl: BigInt(pos.cashPnl), + percentPnl: BigInt(pos.percentPnl), + totalBought: BigInt(pos.totalBought), + realizedPnl: BigInt(pos.realizedPnl), + percentRealizedPnl: BigInt(pos.percentRealizedPnl), + curPrice: BigInt(pos.curPrice), + })); + + // Get entropy fee + const entropyFee = await creditScore.getEntropyFee(); + console.log("💰 Entropy fee required:", ethers.formatEther(entropyFee), "ETH"); + + // Submit data and request score calculation + console.log("📤 Submitting user data and requesting credit score calculation..."); + const tx = await creditScore.submitUserDataAndRequestScore( + polymarketData.user?.name || "User", + BigInt(polymarketData.user?.value || "0"), + closedPositions, + currentPositions, + { value: entropyFee } + ); + + console.log("Transaction hash:", tx.hash); + const receipt = await tx.wait(); + console.log("✅ Transaction confirmed in block:", receipt.blockNumber); + + // Parse events to get sequence number + const requestEvent = receipt.logs.find((log: any) => { + try { + const parsed = creditScore.interface.parseLog(log); + return parsed?.name === "CreditScoreRequested"; + } catch { + return false; + } + }); + + let sequenceNumber: bigint | null = null; + if (requestEvent) { + const parsed = creditScore.interface.parseLog(requestEvent); + sequenceNumber = parsed?.args.sequenceNumber; + console.log("🔢 Sequence number:", sequenceNumber?.toString()); + } + + // Get base score immediately + const baseScore = await creditScore.calculateBaseScore(signer.address); + console.log("📊 Base score (without entropy):", baseScore.toString()); + + // Wait for entropy callback + console.log("⏳ Waiting for entropy callback to calculate final score..."); + console.log("This typically takes 5-10 seconds..."); + + let finalScore: bigint | null = null; + let attempts = 0; + const maxAttempts = 20; + + while (attempts < maxAttempts) { + await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second + attempts++; + + try { + const [score, timestamp, isCalculated] = await creditScore.getCreditScore(signer.address); + + if (isCalculated) { + finalScore = score; + console.log("\n✨ CREDIT SCORE CALCULATED!"); + console.log("====================================="); + console.log("🎯 Final Credit Score:", score.toString()); + console.log("📈 Base Score:", baseScore.toString()); + console.log("🎲 Entropy Variance:", (Number(score) - Number(baseScore)).toString()); + console.log("⏰ Timestamp:", new Date(Number(timestamp) * 1000).toISOString()); + console.log("====================================="); + + // Provide interpretation + let rating = ""; + const scoreNum = Number(score); + if (scoreNum >= 850) { + rating = "🌟 Exceptional"; + } else if (scoreNum >= 750) { + rating = "✨ Excellent"; + } else if (scoreNum >= 650) { + rating = "👍 Good"; + } else if (scoreNum >= 550) { + rating = "📊 Fair"; + } else if (scoreNum >= 450) { + rating = "⚠️ Below Average"; + } else if (scoreNum >= 300) { + rating = "⚡ Poor"; + } else { + rating = "🔴 Very Poor"; + } + + console.log("Rating:", rating); + + // Output JSON result for the frontend to parse + console.log("\n### JSON_RESULT ###"); + console.log( + JSON.stringify({ + success: true, + creditScore: score.toString(), + baseScore: baseScore.toString(), + timestamp: timestamp.toString(), + rating: rating, + walletAddress: signer.address, + }) + ); + + break; + } + } catch (error) { + // Continue waiting + } + + if (attempts % 3 === 0) { + console.log(`⏳ Still waiting... (${attempts}s elapsed)`); + } + } + + if (!finalScore) { + console.log("⚠️ Timeout waiting for entropy callback"); + console.log("The score calculation may still complete. Check again later."); + + console.log("\n### JSON_RESULT ###"); + console.log( + JSON.stringify({ + success: false, + baseScore: baseScore.toString(), + message: "Timeout waiting for entropy callback", + walletAddress: signer.address, + }) + ); + } +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error("Error:", error); + console.log("\n### JSON_RESULT ###"); + console.log( + JSON.stringify({ + success: false, + error: error.message || "Unknown error occurred", + }) + ); + process.exit(1); + }); diff --git a/entropy/polymarket_credit_calculation/creditEngine/submitPolymarketData.ts b/entropy/polymarket_credit_calculation/creditEngine/submitPolymarketData.ts new file mode 100644 index 00000000..0c85557c --- /dev/null +++ b/entropy/polymarket_credit_calculation/creditEngine/submitPolymarketData.ts @@ -0,0 +1,195 @@ +import { ethers } from "hardhat"; + +// Example Polymarket data from terminal +const EXAMPLE_POLYMARKET_DATA = { + user: { + name: "PringlesMax", + value: "175337", + }, + closedPositions: [ + { + realizedPnl: "416736", + totalBought: "1190675", + asset: "0x0000000000000000000000000000000000000000000000000000000000000001", // Simplified asset ID + }, + { + realizedPnl: "392857", + totalBought: "892858", + asset: "0x0000000000000000000000000000000000000000000000000000000000000002", + }, + { + realizedPnl: "381360", + totalBought: "681000", + asset: "0x0000000000000000000000000000000000000000000000000000000000000003", + }, + { + realizedPnl: "367856", + totalBought: "943220", + asset: "0x0000000000000000000000000000000000000000000000000000000000000004", + }, + { + realizedPnl: "362543", + totalBought: "1021552", + asset: "0x0000000000000000000000000000000000000000000000000000000000000005", + }, + { + realizedPnl: "340632", + totalBought: "540686", + asset: "0x0000000000000000000000000000000000000000000000000000000000000006", + }, + { + realizedPnl: "331533", + totalBought: "534732", + asset: "0x0000000000000000000000000000000000000000000000000000000000000007", + }, + { + realizedPnl: "312820", + totalBought: "512821", + asset: "0x0000000000000000000000000000000000000000000000000000000000000008", + }, + { + realizedPnl: "301882", + totalBought: "702002", + asset: "0x0000000000000000000000000000000000000000000000000000000000000009", + }, + { + realizedPnl: "294081", + totalBought: "599218", + asset: "0x000000000000000000000000000000000000000000000000000000000000000a", + }, + ], + currentPositions: [ + { + size: "1263999", + avgPrice: "0", + initialValue: "303359", + currentValue: "0", + cashPnl: "-303360", + percentPnl: "-100", + totalBought: "1263999", + realizedPnl: "0", + percentRealizedPnl: "-100", + curPrice: "0", + }, + { + size: "1145983", + avgPrice: "0", + initialValue: "481313", + currentValue: "0", + cashPnl: "-481314", + percentPnl: "-100", + totalBought: "1145983", + realizedPnl: "0", + percentRealizedPnl: "-101", + curPrice: "0", + }, + { + size: "873938", + avgPrice: "0", + initialValue: "559699", + currentValue: "0", + cashPnl: "-559700", + percentPnl: "-100", + totalBought: "873938", + realizedPnl: "0", + percentRealizedPnl: "-100", + curPrice: "0", + }, + ], +}; + +async function main() { + const contractAddress = process.env.CREDIT_SCORE_CONTRACT || process.argv[2]; + + if (!contractAddress) { + console.error( + "Please provide the CreditScore contract address as an argument or set CREDIT_SCORE_CONTRACT env variable" + ); + process.exit(1); + } + + console.log("Submitting Polymarket data to CreditScore contract at:", contractAddress); + + // Hardcoded private key as requested + const PRIVATE_KEY = "0x0ab2a1d8d5a410c75b6365c1b544117a960aa4cc3459cf2adfea8cd6fc9e14ce"; + + // Create wallet from private key + const wallet = new ethers.Wallet(PRIVATE_KEY); + + // Connect wallet to provider + const provider = new ethers.JsonRpcProvider("https://sepolia.base.org"); + const signer = wallet.connect(provider); + + console.log("Submitting from address:", signer.address); + + // Get contract instance + const CreditScore = await ethers.getContractFactory("CreditScore", signer); + const creditScore = CreditScore.attach(contractAddress); + + // Prepare the data + const closedPositions = EXAMPLE_POLYMARKET_DATA.closedPositions.map((pos) => ({ + realizedPnl: BigInt(pos.realizedPnl), + totalBought: BigInt(pos.totalBought), + asset: pos.asset, + })); + + const currentPositions = EXAMPLE_POLYMARKET_DATA.currentPositions.map((pos) => ({ + size: BigInt(pos.size), + avgPrice: BigInt(pos.avgPrice), + initialValue: BigInt(pos.initialValue), + currentValue: BigInt(pos.currentValue), + cashPnl: BigInt(pos.cashPnl), + percentPnl: BigInt(pos.percentPnl), + totalBought: BigInt(pos.totalBought), + realizedPnl: BigInt(pos.realizedPnl), + percentRealizedPnl: BigInt(pos.percentRealizedPnl), + curPrice: BigInt(pos.curPrice), + })); + + // Get entropy fee + const entropyFee = await creditScore.getEntropyFee(); + console.log("Entropy fee required:", ethers.formatEther(entropyFee), "ETH"); + + // Submit data and request score calculation + console.log("Submitting user data and requesting credit score calculation..."); + const tx = await creditScore.submitUserDataAndRequestScore( + EXAMPLE_POLYMARKET_DATA.user.name, + BigInt(EXAMPLE_POLYMARKET_DATA.user.value), + closedPositions, + currentPositions, + { value: entropyFee } + ); + + console.log("Transaction hash:", tx.hash); + const receipt = await tx.wait(); + console.log("Transaction confirmed in block:", receipt.blockNumber); + + // Parse events to get sequence number + const requestEvent = receipt.logs.find((log) => { + try { + const parsed = creditScore.interface.parseLog(log); + return parsed?.name === "CreditScoreRequested"; + } catch { + return false; + } + }); + + if (requestEvent) { + const parsed = creditScore.interface.parseLog(requestEvent); + console.log("Credit score requested with sequence number:", parsed.args.sequenceNumber.toString()); + console.log("\nWaiting for entropy callback to calculate final score..."); + console.log("This may take a few blocks. You can check the score later using getCreditScore()"); + } + + // Check base score (without entropy) + const baseScore = await creditScore.calculateBaseScore(signer.address); + console.log("\nBase score (without entropy variance):", baseScore.toString()); + console.log("Final score will be ±50 points from this base score"); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + });