From 77d18f2f31e2fbcbc9d7e54c1fa01d2c5ab2229c Mon Sep 17 00:00:00 2001 From: Eric Liu Date: Sun, 23 Nov 2025 09:11:17 -0300 Subject: [PATCH 1/2] Add goddid.money, used Entropy in credit score calculation --- .../creditContract/CreditScore.sol | 290 +++++++++++++++ .../creditContract/cc.md | 344 ++++++++++++++++++ .../creditEngine/README.MD | 187 ++++++++++ .../creditEngine/checkCreditScore.ts | 92 +++++ .../creditEngine/checkScoreSimple.ts | 85 +++++ .../creditEngine/deployCreditScore.ts | 53 +++ .../creditEngine/polymarketCreditFlare.ts | 230 ++++++++++++ .../creditEngine/runCreditScore.ts | 303 +++++++++++++++ .../sampleData/averageTrader.json | 58 +++ .../sampleData/excellentTrader.json | 66 ++++ .../creditEngine/sampleData/poorTrader.json | 42 +++ .../creditEngine/sampleData/pringlesMax.json | 96 +++++ .../creditEngine/submitAndGetScore.ts | 222 +++++++++++ .../creditEngine/submitPolymarketData.ts | 195 ++++++++++ 14 files changed, 2263 insertions(+) create mode 100644 entropy/polymarket_credit_calculation/creditContract/CreditScore.sol create mode 100644 entropy/polymarket_credit_calculation/creditContract/cc.md create mode 100644 entropy/polymarket_credit_calculation/creditEngine/README.MD create mode 100644 entropy/polymarket_credit_calculation/creditEngine/checkCreditScore.ts create mode 100644 entropy/polymarket_credit_calculation/creditEngine/checkScoreSimple.ts create mode 100644 entropy/polymarket_credit_calculation/creditEngine/deployCreditScore.ts create mode 100644 entropy/polymarket_credit_calculation/creditEngine/polymarketCreditFlare.ts create mode 100644 entropy/polymarket_credit_calculation/creditEngine/runCreditScore.ts create mode 100644 entropy/polymarket_credit_calculation/creditEngine/sampleData/averageTrader.json create mode 100644 entropy/polymarket_credit_calculation/creditEngine/sampleData/excellentTrader.json create mode 100644 entropy/polymarket_credit_calculation/creditEngine/sampleData/poorTrader.json create mode 100644 entropy/polymarket_credit_calculation/creditEngine/sampleData/pringlesMax.json create mode 100644 entropy/polymarket_credit_calculation/creditEngine/submitAndGetScore.ts create mode 100644 entropy/polymarket_credit_calculation/creditEngine/submitPolymarketData.ts 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/creditContract/cc.md b/entropy/polymarket_credit_calculation/creditContract/cc.md new file mode 100644 index 00000000..19a50c58 --- /dev/null +++ b/entropy/polymarket_credit_calculation/creditContract/cc.md @@ -0,0 +1,344 @@ +USE BASE SEPOLIA : + +base-sepolia 0x41c9e39574f40ad34c79f1c99b66a45efb830d4c 1 block 500,000 gas + +how to do it and more info : +Best Practices +Limit gas usage on the callback +Keeping the callback function simple is crucial because the entropy providers limit gas usage. This ensures gas usage is predictable and consistent, avoiding potential issues with the callback. + +For example, if you want to use entropy to generate a random number for each player in a round of game, you need to make sure that the callback function works for the maximum number of players that can be in each round. Otherwise, the callbacks will work for some rounds with fewer players, but will fail for rounds with more players. + +Multiple solutions are possible to address this problem. You can store the random number received from the callback and either ask users to submit more transactions after the callback to continue the flow or run a background crank service to submit the necessary transactions. + +The gas limit for each chain is listed on the contract addresses page. + +Handling callback failures +While the default entropy provider is highly reliable, in rare cases a callback might not be received. This typically happens when there's an issue with your contract's callback implementation rather than with the provider itself. The most common causes are: + +The callback function is using more gas than the allowed limit +The callback function contains logic that throws an error +If you're not receiving a callback, you can manually invoke it to identify the specific issue. This allows you to: + +See if the transaction fails and why +Check the gas usage against the chain's callback gas limit +Debug your callback implementation +For detailed instructions on how to manually invoke and debug callbacks, refer to the Debug Callback Failures guide. + +Generating random values within a specific range +You can map the random number provided by Entropy into a smaller range using the solidity modulo operator. Here is a simple example of how to map a random number provided by Entropy into a range between minRange and maxRange (inclusive). + +// Maps a random number into a range between minRange and maxRange (inclusive) +function mapRandomNumber( + bytes32 randomNumber, + int256 minRange, + int256 maxRange +) internal returns (int256) { + uint256 range = uint256(maxRange - minRange + 1); + return minRange + int256(uint256(randomNumber) % range); +} + +Notice that using the modulo operator can distort the distribution of random numbers if it's not a power of 2. This is negligible for small and medium ranges, but it can be noticeable for large ranges. For example, if you want to generate a random number between 1 and 52, the probability of having value 5 is approximately 10^-77 higher than the probability of having value 50 which is infinitesimal. + +Generating multiple random values in a single transaction +If you need to generate multiple random values in a single transaction, you can hash the random input provided by Entropy with a unique identifier for each random number. + +In the following example, mapRandomNumber is used to generate 6 random attributes for a character. + +function generateAttributes(bytes32 randomNumber) internal { + int256 strength = mapRandomNumber( + keccak256(abi.encodePacked(randomNumber, "strength")), + 15, + 20 + ); + int256 stamina = mapRandomNumber( + keccak256(abi.encodePacked(randomNumber, "stamina")), + 10, + 15 + ); + int256 agility = mapRandomNumber( + keccak256(abi.encodePacked(randomNumber, "agility")), + 5, + 15 + ); + int256 stealth = mapRandomNumber( + keccak256(abi.encodePacked(randomNumber, "stealth")), + 0, + 5 + ); + int256 positionX = mapRandomNumber( + keccak256(abi.encodePacked(randomNumber, "positionX")), + -100, + 100 + ); + int256 positionY = mapRandomNumber( + keccak256(abi.encodePacked(randomNumber, "positionY")), + -100, + 100 + ); +} + + + +code example of a coin flip : + + +ts file : +import crypto from "crypto"; +import { arbitrumSepolia, optimismSepolia } from "viem/chains"; +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; +import { ICoinFlipAbi } from "./coin_flip_abi"; + +import { + createWalletClient, + getContract, + http, + publicActions, + Hex, + isHex, + isAddress, + parseEventLogs, +} from "viem"; +import { privateKeyToAccount } from "viem/accounts"; + +const parser = yargs(hideBin(process.argv)) + .option("private-key", { + description: "Private key (as a hexadecimal string) of the sender", + type: "string", + required: true, + }) + .option("address", { + description: "The address of the CoinFlip contract", + type: "string", + required: true, + }) + .option("chain-name", { + description: + "The chain on which you want to test the CoinFlip contract (e.g., 'optimism-sepolia', 'arbitrum-sepolia')", + type: "string", + required: true, + }) + .option("rpc-url", { + description: "The RPC URL to use for the CoinFlip contract", + type: "string", + required: true, + }) + .help() + .alias("help", "h") + .parserConfiguration({ + "parse-numbers": false, + }); + +async function main() { + const argv = await parser.argv; + const chainName = argv.chainName; + + if (chainName !== "optimism-sepolia" && chainName !== "arbitrum-sepolia") { + throw new Error("Invalid chain name"); + } + if (!isHex(argv.privateKey, { strict: true })) { + throw new Error("Private key must be a hexadecimal string"); + } + if (!isAddress(argv.address, { strict: true })) { + throw new Error("Invalid address"); + } + if (!argv.rpcUrl.startsWith("http")) { + throw new Error("RPC URL must start with http"); + } + + const client = createWalletClient({ + chain: chainName === "optimism-sepolia" ? optimismSepolia : arbitrumSepolia, + account: privateKeyToAccount(argv.privateKey), + transport: http(argv.rpcUrl), + }).extend(publicActions); + + const coinFlipContract = getContract({ + address: argv.address, + abi: ICoinFlipAbi, + client, + }); + + console.log("1. Generating user's random number..."); + + const randomNumber: `0x${string}` = `0x${crypto + .randomBytes(32) + .toString("hex")}`; + console.log(`User Generated Random number: ${randomNumber}`); + + console.log("\n2. Requesting coin flip..."); + + const flipFee = await coinFlipContract.read.getFlipFee(); + console.log(`Flip Fee: ${flipFee} wei`); + + console.log("\n3. Sending request to flip coin..."); + +const flipTxHash = await coinFlipContract.write.requestFlip({ + value: flipFee, + }); + console.log(`Transaction Hash: ${flipTxHash}`); + + const receipt = await client.waitForTransactionReceipt({ + hash: flipTxHash, + }); + + const logs = parseEventLogs({ + abi: ICoinFlipAbi, + eventName: "FlipRequest", + logs: receipt.logs, + }); + + const sequenceNumber = logs[0].args.sequenceNumber; + + console.log(`\nSequence Number: ${sequenceNumber}`); + + console.log("\n4. Waiting for flip result..."); + const result = await new Promise((resolve, reject) => { + const unwatch = coinFlipContract.watchEvent.FlipResult({ + fromBlock: receipt.blockNumber - 1n, + onLogs: (logs) => { + for (const log of logs) { + if (log.args.sequenceNumber === sequenceNumber) { + unwatch(); + resolve(log.args.isHeads ? "Heads" : "Tails"); + } + } + }, + }); + }); + + console.log(`\nFlip Result: ${result}`); +} + +main(); + + +CONTRACT -: +// SPDX-License-Identifier: Apache 2 +pragma solidity ^0.8.0; + +// Import the entropy SDK in order to interact with the entropy contracts +import "@pythnetwork/entropy-sdk-solidity/IEntropyV2.sol"; +import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol"; +// Import the EntropyStructsV2 contract to get the ProviderInfo struct +import "@pythnetwork/entropy-sdk-solidity/EntropyStructsV2.sol"; + +library CoinFlipErrors { + error IncorrectSender(); + + error InsufficientFee(); +} + +/// Example contract using Pyth Entropy to allow a user to flip a secure fair coin. +/// Users interact with the contract by requesting a random number from the entropy provider. +/// The entropy provider will then fulfill the request by revealing their random number. +/// Once the provider has fulfilled their request the entropy contract will call back +/// the requesting contract with the generated random number. +/// +/// The CoinFlip contract implements the IEntropyConsumer interface imported from the Solidity SDK. +/// The interface helps in integrating with Entropy correctly. +contract CoinFlip is IEntropyConsumer { + // Event emitted when a coin flip is requested. The sequence number can be used to identify a request + event FlipRequest(uint64 sequenceNumber); + + // Event emitted when the result of the coin flip is known. + event FlipResult(uint64 sequenceNumber, bool isHeads); + + // Contracts using Pyth Entropy should import the solidity SDK and then store both the Entropy contract + // and a specific entropy provider to use for requests. Each provider commits to a sequence of random numbers. + // Providers are then responsible for fulfilling a request on chain by revealing their random number. + // Users should choose a reliable provider who they trust to uphold these commitments. + IEntropyV2 private entropy; + address private entropyProvider; + + constructor(address _entropy, address _entropyProvider) { + entropy = IEntropyV2(_entropy); + entropyProvider = _entropyProvider; + } + + // Request to flip a coin. + function requestFlip() external payable { + // The entropy protocol requires the caller to pay a fee (in native gas tokens) per requested random number. + // This fee can either be paid by the contract itself or passed on to the end user. + // This implementation of the requestFlip method passes on the fee to the end user. + uint256 fee = entropy.getFeeV2(); + if (msg.value < fee) { + revert CoinFlipErrors.InsufficientFee(); + } + + // Request the random number from the Entropy protocol. The call returns a sequence number that uniquely + // identifies the generated random number. Callers can use this sequence number to match which request + // is being revealed in the next stage of the protocol. + // This requestV2 function will trust the provider to draw a random number. + uint64 sequenceNumber = entropy.requestV2{value: fee}(); + + emit FlipRequest(sequenceNumber); + } + + // Request to flip a coin with a custom gas limit. + function requestFlipWithCustomGasLimit(uint32 gasLimit) external payable { + uint256 fee = entropy.getFeeV2(gasLimit); + if (msg.value < fee) { + revert CoinFlipErrors.InsufficientFee(); + } + + uint64 sequenceNumber = entropy.requestV2{value: fee}(gasLimit); + + emit FlipRequest(sequenceNumber); + } + + // Request to flip a coin with a custom provider and custom gas limit. + function requestFlipWithCustomProviderAndGasLimit(address provider, uint32 gasLimit) external payable { + uint256 fee = entropy.getFeeV2(provider, gasLimit); + if (msg.value < fee) { + revert CoinFlipErrors.InsufficientFee(); + } + + uint64 sequenceNumber = entropy.requestV2{value: fee}(provider, gasLimit); + + emit FlipRequest(sequenceNumber); + } + + // Request to flip a coin with a custom provider and custom gas limit and userContribution / Random Number. + function requestFlipWithCustomProviderAndGasLimitAndUserContribution(address provider, uint32 gasLimit, bytes32 userContribution) external payable { + uint256 fee = entropy.getFeeV2(provider, gasLimit); + if (msg.value < fee) { + revert CoinFlipErrors.InsufficientFee(); + } + + uint64 sequenceNumber = entropy.requestV2{value: fee}(provider, userContribution, gasLimit); + + emit FlipRequest(sequenceNumber); + } + + // Get the default gas limit for the default provider. + function getDefaultProviderGasLimit() public view returns (uint32) { + EntropyStructsV2.ProviderInfo memory providerInfo = entropy.getProviderInfoV2(entropy.getDefaultProvider()); + return providerInfo.defaultGasLimit; + } + + // This method is required by the IEntropyConsumer interface. + // It is called by the entropy contract when a random number is generated. + function entropyCallback( + uint64 sequenceNumber, + // If your app uses multiple providers, you can use this argument + // to distinguish which one is calling the app back. This app only + // uses one provider so this argument is not used. + address, + bytes32 randomNumber + ) internal override { + emit FlipResult(sequenceNumber, uint256(randomNumber) % 2 == 0); + } + + // This method is required by the IEntropyConsumer interface. + // It returns the address of the entropy contract which will call the callback. + function getEntropy() internal view override returns (address) { + return address(entropy); + } + + function getFlipFee() public view returns (uint256) { + return entropy.getFeeV2(); + } + + receive() external payable {} +} \ No newline at end of file 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/sampleData/pringlesMax.json b/entropy/polymarket_credit_calculation/creditEngine/sampleData/pringlesMax.json new file mode 100644 index 00000000..e71e96f1 --- /dev/null +++ b/entropy/polymarket_credit_calculation/creditEngine/sampleData/pringlesMax.json @@ -0,0 +1,96 @@ +{ + "user": { + "name": "PringlesMax", + "value": "175337" + }, + "closedPositions": [ + { + "realizedPnl": "416736", + "totalBought": "1190675", + "asset": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + { + "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" + } + ] +} 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); + }); From 57b8a69eeb95478bbbf20cf9db281211f421d460 Mon Sep 17 00:00:00 2001 From: Eric Liu Date: Sun, 23 Nov 2025 10:07:18 -0300 Subject: [PATCH 2/2] remove unnecessary files --- .../creditContract/cc.md | 344 ------------------ .../creditEngine/sampleData/pringlesMax.json | 96 ----- 2 files changed, 440 deletions(-) delete mode 100644 entropy/polymarket_credit_calculation/creditContract/cc.md delete mode 100644 entropy/polymarket_credit_calculation/creditEngine/sampleData/pringlesMax.json diff --git a/entropy/polymarket_credit_calculation/creditContract/cc.md b/entropy/polymarket_credit_calculation/creditContract/cc.md deleted file mode 100644 index 19a50c58..00000000 --- a/entropy/polymarket_credit_calculation/creditContract/cc.md +++ /dev/null @@ -1,344 +0,0 @@ -USE BASE SEPOLIA : - -base-sepolia 0x41c9e39574f40ad34c79f1c99b66a45efb830d4c 1 block 500,000 gas - -how to do it and more info : -Best Practices -Limit gas usage on the callback -Keeping the callback function simple is crucial because the entropy providers limit gas usage. This ensures gas usage is predictable and consistent, avoiding potential issues with the callback. - -For example, if you want to use entropy to generate a random number for each player in a round of game, you need to make sure that the callback function works for the maximum number of players that can be in each round. Otherwise, the callbacks will work for some rounds with fewer players, but will fail for rounds with more players. - -Multiple solutions are possible to address this problem. You can store the random number received from the callback and either ask users to submit more transactions after the callback to continue the flow or run a background crank service to submit the necessary transactions. - -The gas limit for each chain is listed on the contract addresses page. - -Handling callback failures -While the default entropy provider is highly reliable, in rare cases a callback might not be received. This typically happens when there's an issue with your contract's callback implementation rather than with the provider itself. The most common causes are: - -The callback function is using more gas than the allowed limit -The callback function contains logic that throws an error -If you're not receiving a callback, you can manually invoke it to identify the specific issue. This allows you to: - -See if the transaction fails and why -Check the gas usage against the chain's callback gas limit -Debug your callback implementation -For detailed instructions on how to manually invoke and debug callbacks, refer to the Debug Callback Failures guide. - -Generating random values within a specific range -You can map the random number provided by Entropy into a smaller range using the solidity modulo operator. Here is a simple example of how to map a random number provided by Entropy into a range between minRange and maxRange (inclusive). - -// Maps a random number into a range between minRange and maxRange (inclusive) -function mapRandomNumber( - bytes32 randomNumber, - int256 minRange, - int256 maxRange -) internal returns (int256) { - uint256 range = uint256(maxRange - minRange + 1); - return minRange + int256(uint256(randomNumber) % range); -} - -Notice that using the modulo operator can distort the distribution of random numbers if it's not a power of 2. This is negligible for small and medium ranges, but it can be noticeable for large ranges. For example, if you want to generate a random number between 1 and 52, the probability of having value 5 is approximately 10^-77 higher than the probability of having value 50 which is infinitesimal. - -Generating multiple random values in a single transaction -If you need to generate multiple random values in a single transaction, you can hash the random input provided by Entropy with a unique identifier for each random number. - -In the following example, mapRandomNumber is used to generate 6 random attributes for a character. - -function generateAttributes(bytes32 randomNumber) internal { - int256 strength = mapRandomNumber( - keccak256(abi.encodePacked(randomNumber, "strength")), - 15, - 20 - ); - int256 stamina = mapRandomNumber( - keccak256(abi.encodePacked(randomNumber, "stamina")), - 10, - 15 - ); - int256 agility = mapRandomNumber( - keccak256(abi.encodePacked(randomNumber, "agility")), - 5, - 15 - ); - int256 stealth = mapRandomNumber( - keccak256(abi.encodePacked(randomNumber, "stealth")), - 0, - 5 - ); - int256 positionX = mapRandomNumber( - keccak256(abi.encodePacked(randomNumber, "positionX")), - -100, - 100 - ); - int256 positionY = mapRandomNumber( - keccak256(abi.encodePacked(randomNumber, "positionY")), - -100, - 100 - ); -} - - - -code example of a coin flip : - - -ts file : -import crypto from "crypto"; -import { arbitrumSepolia, optimismSepolia } from "viem/chains"; -import yargs from "yargs"; -import { hideBin } from "yargs/helpers"; -import { ICoinFlipAbi } from "./coin_flip_abi"; - -import { - createWalletClient, - getContract, - http, - publicActions, - Hex, - isHex, - isAddress, - parseEventLogs, -} from "viem"; -import { privateKeyToAccount } from "viem/accounts"; - -const parser = yargs(hideBin(process.argv)) - .option("private-key", { - description: "Private key (as a hexadecimal string) of the sender", - type: "string", - required: true, - }) - .option("address", { - description: "The address of the CoinFlip contract", - type: "string", - required: true, - }) - .option("chain-name", { - description: - "The chain on which you want to test the CoinFlip contract (e.g., 'optimism-sepolia', 'arbitrum-sepolia')", - type: "string", - required: true, - }) - .option("rpc-url", { - description: "The RPC URL to use for the CoinFlip contract", - type: "string", - required: true, - }) - .help() - .alias("help", "h") - .parserConfiguration({ - "parse-numbers": false, - }); - -async function main() { - const argv = await parser.argv; - const chainName = argv.chainName; - - if (chainName !== "optimism-sepolia" && chainName !== "arbitrum-sepolia") { - throw new Error("Invalid chain name"); - } - if (!isHex(argv.privateKey, { strict: true })) { - throw new Error("Private key must be a hexadecimal string"); - } - if (!isAddress(argv.address, { strict: true })) { - throw new Error("Invalid address"); - } - if (!argv.rpcUrl.startsWith("http")) { - throw new Error("RPC URL must start with http"); - } - - const client = createWalletClient({ - chain: chainName === "optimism-sepolia" ? optimismSepolia : arbitrumSepolia, - account: privateKeyToAccount(argv.privateKey), - transport: http(argv.rpcUrl), - }).extend(publicActions); - - const coinFlipContract = getContract({ - address: argv.address, - abi: ICoinFlipAbi, - client, - }); - - console.log("1. Generating user's random number..."); - - const randomNumber: `0x${string}` = `0x${crypto - .randomBytes(32) - .toString("hex")}`; - console.log(`User Generated Random number: ${randomNumber}`); - - console.log("\n2. Requesting coin flip..."); - - const flipFee = await coinFlipContract.read.getFlipFee(); - console.log(`Flip Fee: ${flipFee} wei`); - - console.log("\n3. Sending request to flip coin..."); - -const flipTxHash = await coinFlipContract.write.requestFlip({ - value: flipFee, - }); - console.log(`Transaction Hash: ${flipTxHash}`); - - const receipt = await client.waitForTransactionReceipt({ - hash: flipTxHash, - }); - - const logs = parseEventLogs({ - abi: ICoinFlipAbi, - eventName: "FlipRequest", - logs: receipt.logs, - }); - - const sequenceNumber = logs[0].args.sequenceNumber; - - console.log(`\nSequence Number: ${sequenceNumber}`); - - console.log("\n4. Waiting for flip result..."); - const result = await new Promise((resolve, reject) => { - const unwatch = coinFlipContract.watchEvent.FlipResult({ - fromBlock: receipt.blockNumber - 1n, - onLogs: (logs) => { - for (const log of logs) { - if (log.args.sequenceNumber === sequenceNumber) { - unwatch(); - resolve(log.args.isHeads ? "Heads" : "Tails"); - } - } - }, - }); - }); - - console.log(`\nFlip Result: ${result}`); -} - -main(); - - -CONTRACT -: -// SPDX-License-Identifier: Apache 2 -pragma solidity ^0.8.0; - -// Import the entropy SDK in order to interact with the entropy contracts -import "@pythnetwork/entropy-sdk-solidity/IEntropyV2.sol"; -import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol"; -// Import the EntropyStructsV2 contract to get the ProviderInfo struct -import "@pythnetwork/entropy-sdk-solidity/EntropyStructsV2.sol"; - -library CoinFlipErrors { - error IncorrectSender(); - - error InsufficientFee(); -} - -/// Example contract using Pyth Entropy to allow a user to flip a secure fair coin. -/// Users interact with the contract by requesting a random number from the entropy provider. -/// The entropy provider will then fulfill the request by revealing their random number. -/// Once the provider has fulfilled their request the entropy contract will call back -/// the requesting contract with the generated random number. -/// -/// The CoinFlip contract implements the IEntropyConsumer interface imported from the Solidity SDK. -/// The interface helps in integrating with Entropy correctly. -contract CoinFlip is IEntropyConsumer { - // Event emitted when a coin flip is requested. The sequence number can be used to identify a request - event FlipRequest(uint64 sequenceNumber); - - // Event emitted when the result of the coin flip is known. - event FlipResult(uint64 sequenceNumber, bool isHeads); - - // Contracts using Pyth Entropy should import the solidity SDK and then store both the Entropy contract - // and a specific entropy provider to use for requests. Each provider commits to a sequence of random numbers. - // Providers are then responsible for fulfilling a request on chain by revealing their random number. - // Users should choose a reliable provider who they trust to uphold these commitments. - IEntropyV2 private entropy; - address private entropyProvider; - - constructor(address _entropy, address _entropyProvider) { - entropy = IEntropyV2(_entropy); - entropyProvider = _entropyProvider; - } - - // Request to flip a coin. - function requestFlip() external payable { - // The entropy protocol requires the caller to pay a fee (in native gas tokens) per requested random number. - // This fee can either be paid by the contract itself or passed on to the end user. - // This implementation of the requestFlip method passes on the fee to the end user. - uint256 fee = entropy.getFeeV2(); - if (msg.value < fee) { - revert CoinFlipErrors.InsufficientFee(); - } - - // Request the random number from the Entropy protocol. The call returns a sequence number that uniquely - // identifies the generated random number. Callers can use this sequence number to match which request - // is being revealed in the next stage of the protocol. - // This requestV2 function will trust the provider to draw a random number. - uint64 sequenceNumber = entropy.requestV2{value: fee}(); - - emit FlipRequest(sequenceNumber); - } - - // Request to flip a coin with a custom gas limit. - function requestFlipWithCustomGasLimit(uint32 gasLimit) external payable { - uint256 fee = entropy.getFeeV2(gasLimit); - if (msg.value < fee) { - revert CoinFlipErrors.InsufficientFee(); - } - - uint64 sequenceNumber = entropy.requestV2{value: fee}(gasLimit); - - emit FlipRequest(sequenceNumber); - } - - // Request to flip a coin with a custom provider and custom gas limit. - function requestFlipWithCustomProviderAndGasLimit(address provider, uint32 gasLimit) external payable { - uint256 fee = entropy.getFeeV2(provider, gasLimit); - if (msg.value < fee) { - revert CoinFlipErrors.InsufficientFee(); - } - - uint64 sequenceNumber = entropy.requestV2{value: fee}(provider, gasLimit); - - emit FlipRequest(sequenceNumber); - } - - // Request to flip a coin with a custom provider and custom gas limit and userContribution / Random Number. - function requestFlipWithCustomProviderAndGasLimitAndUserContribution(address provider, uint32 gasLimit, bytes32 userContribution) external payable { - uint256 fee = entropy.getFeeV2(provider, gasLimit); - if (msg.value < fee) { - revert CoinFlipErrors.InsufficientFee(); - } - - uint64 sequenceNumber = entropy.requestV2{value: fee}(provider, userContribution, gasLimit); - - emit FlipRequest(sequenceNumber); - } - - // Get the default gas limit for the default provider. - function getDefaultProviderGasLimit() public view returns (uint32) { - EntropyStructsV2.ProviderInfo memory providerInfo = entropy.getProviderInfoV2(entropy.getDefaultProvider()); - return providerInfo.defaultGasLimit; - } - - // This method is required by the IEntropyConsumer interface. - // It is called by the entropy contract when a random number is generated. - function entropyCallback( - uint64 sequenceNumber, - // If your app uses multiple providers, you can use this argument - // to distinguish which one is calling the app back. This app only - // uses one provider so this argument is not used. - address, - bytes32 randomNumber - ) internal override { - emit FlipResult(sequenceNumber, uint256(randomNumber) % 2 == 0); - } - - // This method is required by the IEntropyConsumer interface. - // It returns the address of the entropy contract which will call the callback. - function getEntropy() internal view override returns (address) { - return address(entropy); - } - - function getFlipFee() public view returns (uint256) { - return entropy.getFeeV2(); - } - - receive() external payable {} -} \ No newline at end of file diff --git a/entropy/polymarket_credit_calculation/creditEngine/sampleData/pringlesMax.json b/entropy/polymarket_credit_calculation/creditEngine/sampleData/pringlesMax.json deleted file mode 100644 index e71e96f1..00000000 --- a/entropy/polymarket_credit_calculation/creditEngine/sampleData/pringlesMax.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "user": { - "name": "PringlesMax", - "value": "175337" - }, - "closedPositions": [ - { - "realizedPnl": "416736", - "totalBought": "1190675", - "asset": "0x0000000000000000000000000000000000000000000000000000000000000001" - }, - { - "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" - } - ] -}