Skip to content

Commit 77d18f2

Browse files
committed
Add goddid.money, used Entropy in credit score calculation
1 parent b48bc74 commit 77d18f2

File tree

14 files changed

+2263
-0
lines changed

14 files changed

+2263
-0
lines changed
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import "@pythnetwork/entropy-sdk-solidity/IEntropyV2.sol";
5+
import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol";
6+
import "@pythnetwork/entropy-sdk-solidity/EntropyStructsV2.sol";
7+
8+
contract CreditScore is IEntropyConsumer {
9+
// Structs to hold Polymarket data
10+
struct ClosedPosition {
11+
int256 realizedPnl;
12+
uint256 totalBought;
13+
bytes32 asset;
14+
}
15+
16+
struct CurrentPosition {
17+
uint256 size;
18+
uint256 avgPrice;
19+
int256 initialValue;
20+
int256 currentValue;
21+
int256 cashPnl;
22+
int256 percentPnl;
23+
uint256 totalBought;
24+
int256 realizedPnl;
25+
int256 percentRealizedPnl;
26+
uint256 curPrice;
27+
}
28+
29+
struct UserData {
30+
string name;
31+
uint256 value;
32+
ClosedPosition[] closedPositions;
33+
CurrentPosition[] currentPositions;
34+
}
35+
36+
struct CreditScoreData {
37+
uint256 score;
38+
uint256 timestamp;
39+
bytes32 entropyUsed;
40+
bool isCalculated;
41+
}
42+
43+
// Events
44+
event CreditScoreRequested(address indexed user, uint64 sequenceNumber);
45+
event CreditScoreCalculated(address indexed user, uint256 score, bytes32 entropyUsed);
46+
47+
// State variables
48+
IEntropyV2 private entropy;
49+
address private entropyProvider;
50+
51+
mapping(address => UserData) public userData;
52+
mapping(address => CreditScoreData) public creditScores;
53+
mapping(uint64 => address) private sequenceToUser;
54+
mapping(address => uint64) private userToSequence;
55+
56+
// Constants for score calculation
57+
uint256 private constant MAX_SCORE = 999;
58+
uint256 private constant MIN_SCORE = 0;
59+
uint256 private constant ENTROPY_VARIANCE_WEIGHT = 50; // Max 50 points of variance from entropy
60+
61+
constructor(address _entropy, address _entropyProvider) {
62+
entropy = IEntropyV2(_entropy);
63+
entropyProvider = _entropyProvider;
64+
}
65+
66+
// Submit user data and request credit score calculation
67+
function submitUserDataAndRequestScore(
68+
string memory name,
69+
uint256 value,
70+
ClosedPosition[] memory closedPositions,
71+
CurrentPosition[] memory currentPositions
72+
) external payable {
73+
// Clear previous data
74+
delete userData[msg.sender].closedPositions;
75+
delete userData[msg.sender].currentPositions;
76+
77+
// Store user data
78+
userData[msg.sender].name = name;
79+
userData[msg.sender].value = value;
80+
81+
// Store closed positions
82+
for (uint i = 0; i < closedPositions.length; i++) {
83+
userData[msg.sender].closedPositions.push(closedPositions[i]);
84+
}
85+
86+
// Store current positions
87+
for (uint i = 0; i < currentPositions.length; i++) {
88+
userData[msg.sender].currentPositions.push(currentPositions[i]);
89+
}
90+
91+
// Request entropy for variance
92+
uint256 fee = entropy.getFeeV2();
93+
require(msg.value >= fee, "Insufficient fee for entropy");
94+
95+
uint64 sequenceNumber = entropy.requestV2{ value: fee }();
96+
sequenceToUser[sequenceNumber] = msg.sender;
97+
userToSequence[msg.sender] = sequenceNumber;
98+
99+
emit CreditScoreRequested(msg.sender, sequenceNumber);
100+
}
101+
102+
// Calculate base credit score without entropy (for preview purposes)
103+
function calculateBaseScore(address user) public view returns (uint256) {
104+
UserData storage data = userData[user];
105+
106+
if (data.closedPositions.length == 0 && data.currentPositions.length == 0) {
107+
return 500; // Default middle score for new users
108+
}
109+
110+
uint256 score = 0;
111+
uint256 weightSum = 0;
112+
113+
// 1. Calculate profit/loss metrics from closed positions (40% weight)
114+
if (data.closedPositions.length > 0) {
115+
int256 totalPnl = 0;
116+
uint256 totalInvested = 0;
117+
uint256 winCount = 0;
118+
119+
for (uint i = 0; i < data.closedPositions.length; i++) {
120+
totalPnl += data.closedPositions[i].realizedPnl;
121+
totalInvested += data.closedPositions[i].totalBought;
122+
if (data.closedPositions[i].realizedPnl > 0) {
123+
winCount++;
124+
}
125+
}
126+
127+
// Win rate (0-200 points)
128+
uint256 winRate = (winCount * 200) / data.closedPositions.length;
129+
score += winRate;
130+
131+
// Profit ratio (0-200 points)
132+
if (totalInvested > 0) {
133+
if (totalPnl > 0) {
134+
uint256 profitRatio = (uint256(totalPnl) * 200) / totalInvested;
135+
if (profitRatio > 200) profitRatio = 200; // Cap at 200
136+
score += profitRatio;
137+
} else {
138+
// Negative PnL reduces score
139+
uint256 lossRatio = (uint256(-totalPnl) * 100) / totalInvested;
140+
if (lossRatio > 200) lossRatio = 200;
141+
score += 0; // No additional score for losses
142+
}
143+
}
144+
weightSum += 400;
145+
}
146+
147+
// 2. Current positions performance (30% weight)
148+
if (data.currentPositions.length > 0) {
149+
int256 currentTotalPnl = 0;
150+
uint256 currentTotalInvested = 0;
151+
152+
for (uint i = 0; i < data.currentPositions.length; i++) {
153+
currentTotalPnl += data.currentPositions[i].cashPnl;
154+
currentTotalInvested += data.currentPositions[i].totalBought;
155+
}
156+
157+
// Current position health (0-300 points)
158+
if (currentTotalInvested > 0) {
159+
if (currentTotalPnl >= 0) {
160+
uint256 currentRatio = (uint256(currentTotalPnl) * 150) / currentTotalInvested;
161+
if (currentRatio > 150) currentRatio = 150;
162+
score += 150 + currentRatio; // Base 150 + up to 150 bonus
163+
} else {
164+
// Losses reduce from base
165+
uint256 lossRatio = (uint256(-currentTotalPnl) * 150) / currentTotalInvested;
166+
if (lossRatio > 150) lossRatio = 150;
167+
score += (150 - lossRatio);
168+
}
169+
} else {
170+
score += 150; // Neutral if no current positions
171+
}
172+
weightSum += 300;
173+
}
174+
175+
// 3. Portfolio value consideration (20% weight)
176+
if (data.value > 0) {
177+
// Scale based on value (logarithmic scale)
178+
uint256 valueScore = 0;
179+
if (data.value >= 1000000) {
180+
// 1M+
181+
valueScore = 200;
182+
} else if (data.value >= 500000) {
183+
// 500k+
184+
valueScore = 180;
185+
} else if (data.value >= 100000) {
186+
// 100k+
187+
valueScore = 150;
188+
} else if (data.value >= 50000) {
189+
// 50k+
190+
valueScore = 120;
191+
} else if (data.value >= 10000) {
192+
// 10k+
193+
valueScore = 100;
194+
} else {
195+
valueScore = (data.value * 100) / 10000; // Linear scale below 10k
196+
}
197+
score += valueScore;
198+
weightSum += 200;
199+
}
200+
201+
// 4. Activity bonus (10% weight)
202+
uint256 totalTrades = data.closedPositions.length + data.currentPositions.length;
203+
uint256 activityScore = 0;
204+
if (totalTrades >= 20) {
205+
activityScore = 100;
206+
} else if (totalTrades >= 10) {
207+
activityScore = 80;
208+
} else if (totalTrades >= 5) {
209+
activityScore = 60;
210+
} else if (totalTrades > 0) {
211+
activityScore = (totalTrades * 60) / 5;
212+
}
213+
score += activityScore;
214+
weightSum += 100;
215+
216+
// Normalize to 0-949 range (leaving room for entropy variance)
217+
if (weightSum > 0) {
218+
score = (score * 949) / weightSum;
219+
}
220+
221+
return score;
222+
}
223+
224+
// Entropy callback implementation
225+
function entropyCallback(uint64 sequenceNumber, address, bytes32 randomNumber) internal override {
226+
address user = sequenceToUser[sequenceNumber];
227+
require(user != address(0), "Invalid sequence number");
228+
229+
// Calculate base score
230+
uint256 baseScore = calculateBaseScore(user);
231+
232+
// Add entropy-based variance (±ENTROPY_VARIANCE_WEIGHT points)
233+
uint256 entropyFactor = uint256(randomNumber) % (ENTROPY_VARIANCE_WEIGHT * 2 + 1);
234+
int256 variance = int256(entropyFactor) - int256(ENTROPY_VARIANCE_WEIGHT);
235+
236+
// Calculate final score with bounds checking
237+
int256 finalScoreInt = int256(baseScore) + variance;
238+
uint256 finalScore;
239+
240+
if (finalScoreInt < 0) {
241+
finalScore = 0;
242+
} else if (finalScoreInt > int256(MAX_SCORE)) {
243+
finalScore = MAX_SCORE;
244+
} else {
245+
finalScore = uint256(finalScoreInt);
246+
}
247+
248+
// Store the calculated score
249+
creditScores[user] = CreditScoreData({
250+
score: finalScore,
251+
timestamp: block.timestamp,
252+
entropyUsed: randomNumber,
253+
isCalculated: true
254+
});
255+
256+
emit CreditScoreCalculated(user, finalScore, randomNumber);
257+
258+
// Clean up mappings
259+
delete sequenceToUser[sequenceNumber];
260+
delete userToSequence[user];
261+
}
262+
263+
// Required by IEntropyConsumer
264+
function getEntropy() internal view override returns (address) {
265+
return address(entropy);
266+
}
267+
268+
// Get the fee required for entropy
269+
function getEntropyFee() public view returns (uint256) {
270+
return entropy.getFeeV2();
271+
}
272+
273+
// Get user's credit score
274+
function getCreditScore(address user) external view returns (uint256 score, uint256 timestamp, bool isCalculated) {
275+
CreditScoreData memory data = creditScores[user];
276+
return (data.score, data.timestamp, data.isCalculated);
277+
}
278+
279+
// Get user's pending sequence number
280+
function getPendingSequence(address user) external view returns (uint64) {
281+
return userToSequence[user];
282+
}
283+
284+
// Check if user has pending score calculation
285+
function hasPendingCalculation(address user) external view returns (bool) {
286+
return userToSequence[user] != 0;
287+
}
288+
289+
receive() external payable {}
290+
}

0 commit comments

Comments
 (0)