@marinade.finance/scoring 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/computing/cluster.ts +102 -0
- package/computing/eligibility.ts +163 -0
- package/computing/score.ts +164 -0
- package/computing/stake.ts +246 -0
- package/computing/validators.ts +133 -0
- package/{src/dto → dto}/bonds.dto.ts +0 -2
- package/{src/dto → dto}/cluster.dto.ts +0 -1
- package/{src/dto → dto}/eligibility.dto.ts +0 -5
- package/{src/dto → dto}/jito.dto.ts +1 -2
- package/{src/dto → dto}/marinade.dto.ts +1 -1
- package/{src/dto → dto}/rewards.dto.ts +1 -3
- package/{src/dto → dto}/scoring.dto.ts +1 -7
- package/{src/dto → dto}/snapshots.dto.ts +0 -3
- package/{src/dto → dto}/stakes.dto.ts +0 -4
- package/{src/dto → dto}/validators.dto.ts +1 -37
- package/package.json +17 -13
- package/{src/providers → providers}/api-data.provider.ts +58 -58
- package/{src/providers → providers}/file-data.provider.ts +7 -15
- package/{src/utils → utils}/csv.ts +3 -3
- package/src/cluster/cluster.module.ts +0 -8
- package/src/cluster/cluster.service.ts +0 -108
- package/src/config/config.module.ts +0 -8
- package/src/config/config.service.ts +0 -136
- package/src/eligibility/eligibility.module.ts +0 -11
- package/src/eligibility/eligibility.service.ts +0 -183
- package/src/scoring/scoring.module.ts +0 -11
- package/src/scoring/scoring.service.ts +0 -184
- package/src/stake/stake.module.ts +0 -10
- package/src/stake/stake.service.ts +0 -242
- package/src/validators/validators.module.ts +0 -48
- package/src/validators/validators.service.ts +0 -177
- /package/{src/constants → constants}/marinade.json +0 -0
- /package/{src/errors → errors}/fetching.ts +0 -0
- /package/{src/interfaces → interfaces}/data-provider.interface.ts +0 -0
- /package/{src/utils → utils}/maths.ts +0 -0
- /package/{src/utils → utils}/solana.ts +0 -0
- /package/{src/utils → utils}/zip.ts +0 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { AggregatedValidator, AggregatedValidators, ValidatorDto } from '../dto/validators.dto';
|
|
2
|
+
import { JitoValidatorDto } from '../dto/jito.dto';
|
|
3
|
+
import { mean, sum } from '../utils/maths';
|
|
4
|
+
import { ClusterInfo } from '../dto/cluster.dto';
|
|
5
|
+
|
|
6
|
+
export function aggregateValidatorData (
|
|
7
|
+
validator: ValidatorDto,
|
|
8
|
+
mevRecord: JitoValidatorDto,
|
|
9
|
+
blacklist: Set<string>,
|
|
10
|
+
firstEpoch: number,
|
|
11
|
+
lastEpoch: number
|
|
12
|
+
): AggregatedValidator {
|
|
13
|
+
const currentMarinadeStake = Number(validator.epochStats[lastEpoch]?.marinade_native_stake || 0) / 1e9 + Number(validator.epochStats[lastEpoch]?.marinade_stake || 0) / 1e9;
|
|
14
|
+
const voteAccount = validator.vote_account;
|
|
15
|
+
const name = validator.info_name || 'Unknown';
|
|
16
|
+
const epochs: number[] = [];
|
|
17
|
+
const commission: number[] = [];
|
|
18
|
+
const stake: number[] = [];
|
|
19
|
+
const externalStake: number[] = [];
|
|
20
|
+
const credits: number[] = [];
|
|
21
|
+
const blocksProduced: number[] = [];
|
|
22
|
+
const leaderSlots: number[] = [];
|
|
23
|
+
const dataAvailable: boolean[] = [];
|
|
24
|
+
|
|
25
|
+
const currentStake = Number(validator.epochStats[lastEpoch]?.activated_stake || 0) / 1e9;
|
|
26
|
+
|
|
27
|
+
const blacklisted = blacklist.has(voteAccount);
|
|
28
|
+
|
|
29
|
+
for (let epoch = lastEpoch; epoch >= firstEpoch; epoch--) {
|
|
30
|
+
const epochStat = validator.epochStats?.[epoch];
|
|
31
|
+
|
|
32
|
+
epochs.push(epoch);
|
|
33
|
+
commission.push(epochStat?.commission_max_observed || epochStat?.commission_advertised || 0);
|
|
34
|
+
stake.push(Number(epochStat?.activated_stake || 0) / 1e9);
|
|
35
|
+
externalStake.push(Number(epochStat?.activated_stake || 0) / 1e9 - Number(epochStat?.marinade_stake || 0) / 1e9 - Number(epochStat?.marinade_native_stake || 0) / 1e9);
|
|
36
|
+
credits.push(epochStat?.credits || 0);
|
|
37
|
+
blocksProduced.push(epochStat?.blocks_produced || 0);
|
|
38
|
+
leaderSlots.push(epochStat?.leader_slots || 0);
|
|
39
|
+
dataAvailable.push(!!epochStat);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const mevCommission = mevRecord?.running_jito ? mevRecord.mev_commission_bps / 100 : 100;
|
|
43
|
+
return {
|
|
44
|
+
voteAccount,
|
|
45
|
+
name,
|
|
46
|
+
epochs,
|
|
47
|
+
currentMarinadeStake,
|
|
48
|
+
currentStake,
|
|
49
|
+
commission,
|
|
50
|
+
stake,
|
|
51
|
+
externalStake,
|
|
52
|
+
credits,
|
|
53
|
+
blocksProduced,
|
|
54
|
+
leaderSlots,
|
|
55
|
+
dataAvailable,
|
|
56
|
+
mevCommission,
|
|
57
|
+
country: validator.dc_country || 'Unknown',
|
|
58
|
+
city: validator.dc_full_city || 'Unknown',
|
|
59
|
+
aso: validator.dc_aso || 'Unknown',
|
|
60
|
+
blacklisted,
|
|
61
|
+
version: validator.version
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export async function aggregateValidatorsData (
|
|
65
|
+
validators: ValidatorDto[],
|
|
66
|
+
basicEligibilityEpochs: number,
|
|
67
|
+
bonusEligibilityExtraEpochs: number
|
|
68
|
+
): Promise<AggregatedValidators> {
|
|
69
|
+
const lastEpoch = this.getMaxEpoch(validators);
|
|
70
|
+
const firstEpoch = lastEpoch - basicEligibilityEpochs - bonusEligibilityExtraEpochs;
|
|
71
|
+
|
|
72
|
+
const jitoMevRecords: Record<string, JitoValidatorDto> = await this.dataProvider.fetchValidatorsJitoMEV(true);
|
|
73
|
+
const blacklist: Set<string> = await this.dataProvider.fetchBlacklist(true);
|
|
74
|
+
|
|
75
|
+
const result: { [voteAccount: string]: AggregatedValidator } = {};
|
|
76
|
+
|
|
77
|
+
for (const validator of validators) {
|
|
78
|
+
const mevRecord = jitoMevRecords[validator.vote_account] ?? {
|
|
79
|
+
vote_account: validator.vote_account,
|
|
80
|
+
mev_commission_bps: 100,
|
|
81
|
+
running_jito: false,
|
|
82
|
+
active_stake: Number(validator.activated_stake)
|
|
83
|
+
} as JitoValidatorDto;
|
|
84
|
+
|
|
85
|
+
if (mevRecord) {
|
|
86
|
+
const aggregatedData = this.aggregateValidatorData(validator, mevRecord, blacklist, firstEpoch, lastEpoch);
|
|
87
|
+
result[validator.vote_account] = aggregatedData;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
export function getMaxEpoch (validators: ValidatorDto[]): number {
|
|
94
|
+
return validators.reduce((maxEpoch, validator) => {
|
|
95
|
+
const validatorMaxEpoch = validator.epoch_stats.reduce((max, { epoch }) => {
|
|
96
|
+
return Math.max(epoch, max);
|
|
97
|
+
}, 0);
|
|
98
|
+
return Math.max(maxEpoch, validatorMaxEpoch);
|
|
99
|
+
}, 0);
|
|
100
|
+
}
|
|
101
|
+
export function selectExternalStakeMin (validator: AggregatedValidator, fullEpochs: number): number {
|
|
102
|
+
if (validator.externalStake.length === 0) {
|
|
103
|
+
throw new Error('Validator has no external stake data.');
|
|
104
|
+
}
|
|
105
|
+
return Math.min(...validator.externalStake.slice(0, fullEpochs + 1));
|
|
106
|
+
}
|
|
107
|
+
export function selectCreditsPctMean (validator: AggregatedValidator, clusterInfo: ClusterInfo, fullEpochs: number): number {
|
|
108
|
+
return mean(validator.epochs.slice(1, fullEpochs + 1).map((epoch, fullEpochIndex) =>
|
|
109
|
+
(validator.credits[fullEpochIndex + 1] ?? 0) / (clusterInfo.targetCreditsByEpoch.get(epoch) ?? 1)));
|
|
110
|
+
}
|
|
111
|
+
export function selectBlockProductionMean (validator: AggregatedValidator, fullEpochs: number): number {
|
|
112
|
+
const leaderSlots = sum(validator.leaderSlots.slice(1, fullEpochs + 1));
|
|
113
|
+
return leaderSlots === 0 ? 1 : sum(validator.blocksProduced.slice(1, fullEpochs + 1)) / leaderSlots;
|
|
114
|
+
}
|
|
115
|
+
export function selectCommissionInflationMax (validator: AggregatedValidator, epochs: number): number {
|
|
116
|
+
return Math.max(...validator.commission.slice(0, epochs));
|
|
117
|
+
}
|
|
118
|
+
export function selectCommissionMEV (validator: AggregatedValidator): number {
|
|
119
|
+
return validator.mevCommission;
|
|
120
|
+
}
|
|
121
|
+
export function selectCountryStakeConcentration (validator: AggregatedValidator, clusterInfo: ClusterInfo): number {
|
|
122
|
+
return clusterInfo.country.get(validator.country ?? '???') ?? 0;
|
|
123
|
+
}
|
|
124
|
+
export function selectCityStakeConcentration (validator: AggregatedValidator, clusterInfo: ClusterInfo): number {
|
|
125
|
+
return clusterInfo.city.get(validator.city ?? '???') ?? 0;
|
|
126
|
+
}
|
|
127
|
+
export function selectASOStakeConcentration (validator: AggregatedValidator, clusterInfo: ClusterInfo): number {
|
|
128
|
+
return clusterInfo.aso.get(validator.aso ?? '???') ?? 0;
|
|
129
|
+
}
|
|
130
|
+
export function selectNodeStake (validator: AggregatedValidator): number {
|
|
131
|
+
return validator.stake[0] ?? 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
@@ -9,7 +9,6 @@ export type EligibilityConfig = {
|
|
|
9
9
|
maxStakeShare: number;
|
|
10
10
|
maxWarnings: number;
|
|
11
11
|
}
|
|
12
|
-
|
|
13
12
|
export type ValidatorEligibility = {
|
|
14
13
|
basicEligibility: boolean;
|
|
15
14
|
bonusEligibility: boolean;
|
|
@@ -17,17 +16,14 @@ export type ValidatorEligibility = {
|
|
|
17
16
|
capFromBond: number;
|
|
18
17
|
capFromExternalStake: number;
|
|
19
18
|
}
|
|
20
|
-
|
|
21
19
|
export type EvaluationResult = {
|
|
22
20
|
criticals: number;
|
|
23
21
|
warnings: number;
|
|
24
22
|
}
|
|
25
|
-
|
|
26
23
|
export type Issue = {
|
|
27
24
|
type: IssueType;
|
|
28
25
|
message: string;
|
|
29
26
|
}
|
|
30
|
-
|
|
31
27
|
export const enum IssueType {
|
|
32
28
|
NO_DATA = 'NO_DATA',
|
|
33
29
|
NO_BOND = 'NO_BOND',
|
|
@@ -39,5 +35,4 @@ export const enum IssueType {
|
|
|
39
35
|
VOTE_CREDITS_LOW = 'VOTE_CREDITS_LOW',
|
|
40
36
|
VOTE_CREDITS_WARNING = 'VOTE_CREDITS_WARNING',
|
|
41
37
|
}
|
|
42
|
-
|
|
43
38
|
export type ValidatorsEligibilities = Record<string, ValidatorEligibility>
|
|
@@ -7,7 +7,6 @@ export class RewardPairDto {
|
|
|
7
7
|
this.amount = pair[1];
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
|
-
|
|
11
10
|
export class RewardsResponseDto {
|
|
12
11
|
rewards_mev: RewardPairDto[];
|
|
13
12
|
rewards_inflation_est: RewardPairDto[];
|
|
@@ -17,5 +16,4 @@ export class RewardsResponseDto {
|
|
|
17
16
|
this.rewards_inflation_est = data.rewards_inflation_est.map(pair => new RewardPairDto(pair));
|
|
18
17
|
}
|
|
19
18
|
}
|
|
20
|
-
|
|
21
|
-
export type Rewards = { inflation: number, mev: number }
|
|
19
|
+
export type Rewards = { inflation: number, mev: number }
|
|
@@ -9,7 +9,6 @@ export type ScoreDto = {
|
|
|
9
9
|
scoreErrors: boolean[]
|
|
10
10
|
tooltips: string[]
|
|
11
11
|
}
|
|
12
|
-
|
|
13
12
|
export type Score = {
|
|
14
13
|
vote_account: string,
|
|
15
14
|
score: string,
|
|
@@ -37,12 +36,10 @@ export type Score = {
|
|
|
37
36
|
target_stake_algo: string,
|
|
38
37
|
target_stake: string,
|
|
39
38
|
}
|
|
40
|
-
|
|
41
39
|
export type ScoreConfig = {
|
|
42
40
|
epochs: number,
|
|
43
41
|
concentrationParams: number[]
|
|
44
42
|
}
|
|
45
|
-
|
|
46
43
|
export type ScoringConfig = {
|
|
47
44
|
DS_PUBKEY: string;
|
|
48
45
|
REWARDS_PAST_EPOCHS: number;
|
|
@@ -94,16 +91,13 @@ export type Variables = {
|
|
|
94
91
|
aso_stake_concentration_last: number;
|
|
95
92
|
node_stake_last: number;
|
|
96
93
|
};
|
|
97
|
-
|
|
98
94
|
export type Scores = Record<string, ScoreDto>
|
|
99
95
|
export type ScoreTooltipBuilder = (validator: AggregatedValidator, clusterInfo: ClusterInfo) => string
|
|
100
|
-
|
|
101
96
|
export type UnstakeHint = {
|
|
102
97
|
vote_account: string;
|
|
103
98
|
marinade_stake: string;
|
|
104
99
|
hints: string[];
|
|
105
100
|
};
|
|
106
|
-
|
|
107
101
|
export type UnstakeHints = {
|
|
108
102
|
unstake_hints: UnstakeHint[];
|
|
109
|
-
};
|
|
103
|
+
};
|
|
@@ -3,17 +3,14 @@ export class RecordDto {
|
|
|
3
3
|
tokenOwner: string;
|
|
4
4
|
validatorVoteAccount: string;
|
|
5
5
|
}
|
|
6
|
-
|
|
7
6
|
export class veMNDESnapshotDto {
|
|
8
7
|
voteRecordsCreatedAt: string;
|
|
9
8
|
records: RecordDto[];
|
|
10
9
|
}
|
|
11
|
-
|
|
12
10
|
export class mSolSnapshotDto {
|
|
13
11
|
mSolSnapshotCreatedAt: string;
|
|
14
12
|
voteRecordsCreatedAt: string;
|
|
15
13
|
records: RecordDto[];
|
|
16
14
|
}
|
|
17
|
-
|
|
18
15
|
export type Votes = Record<string, number>
|
|
19
16
|
|
|
@@ -8,7 +8,6 @@ export type StakesConfig = {
|
|
|
8
8
|
formulaStakeBlockSize: string
|
|
9
9
|
formulaStakeBlocksFromScore: string
|
|
10
10
|
}
|
|
11
|
-
|
|
12
11
|
export type Stake = {
|
|
13
12
|
algoStake: number
|
|
14
13
|
mSolStake: number
|
|
@@ -19,7 +18,6 @@ export type Stake = {
|
|
|
19
18
|
veMndeStakeFromOverflow: number
|
|
20
19
|
totalStakeFromOverlfow: number
|
|
21
20
|
}
|
|
22
|
-
|
|
23
21
|
export type ProcessedVotesAndTVLs = {
|
|
24
22
|
updatedMSolVotes: Votes;
|
|
25
23
|
updatedVeMndeVotes: Votes;
|
|
@@ -27,9 +25,7 @@ export type ProcessedVotesAndTVLs = {
|
|
|
27
25
|
veMndeTvl: number;
|
|
28
26
|
totalTvl: number;
|
|
29
27
|
};
|
|
30
|
-
|
|
31
28
|
export type StakingVariables = {
|
|
32
29
|
tvl: number;
|
|
33
30
|
}
|
|
34
|
-
|
|
35
31
|
export type Stakes = Record<string, Stake>
|
|
@@ -1,67 +1,36 @@
|
|
|
1
|
-
|
|
2
1
|
export class WarningDto {
|
|
3
2
|
warning: string;
|
|
4
3
|
}
|
|
5
|
-
|
|
6
4
|
export class EpochStatDto {
|
|
7
|
-
|
|
8
5
|
epoch: number;
|
|
9
|
-
|
|
10
6
|
epoch_start_at: string;
|
|
11
|
-
|
|
12
7
|
epoch_end_at?: string;
|
|
13
|
-
|
|
14
8
|
commission_max_observed?: number | null;
|
|
15
|
-
|
|
16
9
|
commission_min_observed?: number | null;
|
|
17
|
-
|
|
18
10
|
commission_advertised: number;
|
|
19
|
-
|
|
20
11
|
commission_effective?: number | null;
|
|
21
|
-
|
|
22
12
|
version: string;
|
|
23
|
-
|
|
24
13
|
activated_stake: string;
|
|
25
|
-
|
|
26
14
|
marinade_stake: string;
|
|
27
|
-
|
|
28
15
|
foundation_stake: string;
|
|
29
|
-
|
|
30
16
|
marinade_native_stake: string;
|
|
31
|
-
|
|
32
17
|
self_stake: string;
|
|
33
|
-
|
|
34
18
|
superminority: boolean;
|
|
35
|
-
|
|
36
19
|
stake_to_become_superminority: string;
|
|
37
|
-
|
|
38
20
|
credits: number;
|
|
39
|
-
|
|
40
21
|
leader_slots: number;
|
|
41
|
-
|
|
42
22
|
blocks_produced: number;
|
|
43
|
-
|
|
44
23
|
skip_rate: number;
|
|
45
|
-
|
|
46
24
|
uptime_pct?: number | null;
|
|
47
|
-
|
|
48
25
|
uptime?: number | null;
|
|
49
|
-
|
|
50
26
|
downtime?: number | null;
|
|
51
|
-
|
|
52
27
|
apr?: number | null;
|
|
53
|
-
|
|
54
28
|
apy?: number | null;
|
|
55
|
-
|
|
56
29
|
score: number;
|
|
57
|
-
|
|
58
30
|
rank_score: number;
|
|
59
|
-
|
|
60
31
|
rank_activated_stake: number;
|
|
61
|
-
|
|
62
32
|
rank_apy: number;
|
|
63
33
|
}
|
|
64
|
-
|
|
65
34
|
export class ValidatorDto {
|
|
66
35
|
identity: string;
|
|
67
36
|
vote_account: string;
|
|
@@ -108,19 +77,16 @@ export class ValidatorDto {
|
|
|
108
77
|
avg_uptime_pct: number;
|
|
109
78
|
avg_apy: number;
|
|
110
79
|
}
|
|
111
|
-
|
|
112
80
|
export class ValidatorAggregatedDto {
|
|
113
81
|
epoch: number;
|
|
114
82
|
epoch_start_date: string;
|
|
115
83
|
avg_marinade_score?: number | null;
|
|
116
84
|
avg_apy?: number | null;
|
|
117
85
|
}
|
|
118
|
-
|
|
119
86
|
export class ValidatorsResponseDto {
|
|
120
87
|
validators: ValidatorDto[];
|
|
121
88
|
validators_aggregated: ValidatorAggregatedDto[];
|
|
122
89
|
}
|
|
123
|
-
|
|
124
90
|
export type AggregatedValidator = {
|
|
125
91
|
voteAccount: string;
|
|
126
92
|
name: string;
|
|
@@ -141,6 +107,4 @@ export type AggregatedValidator = {
|
|
|
141
107
|
blacklisted: boolean;
|
|
142
108
|
version: string;
|
|
143
109
|
};
|
|
144
|
-
|
|
145
|
-
export type AggregatedValidators = Record<string, AggregatedValidator>
|
|
146
|
-
|
|
110
|
+
export type AggregatedValidators = Record<string, AggregatedValidator>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marinade.finance/scoring",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Delegation Strategy Scoring",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -9,16 +9,21 @@
|
|
|
9
9
|
"publishConfig": {
|
|
10
10
|
"access": "public"
|
|
11
11
|
},
|
|
12
|
-
|
|
13
12
|
"files": [
|
|
14
|
-
"
|
|
13
|
+
"computing",
|
|
14
|
+
"constants",
|
|
15
|
+
"dto",
|
|
16
|
+
"errors",
|
|
17
|
+
"interfaces",
|
|
18
|
+
"providers",
|
|
19
|
+
"utils",
|
|
15
20
|
"generated",
|
|
16
21
|
"README.md"
|
|
17
22
|
],
|
|
18
23
|
"main": "index.js",
|
|
19
24
|
"scripts": {
|
|
20
|
-
"build": "
|
|
21
|
-
"test": "
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"test": "jest"
|
|
22
27
|
},
|
|
23
28
|
"keywords": [
|
|
24
29
|
"solana",
|
|
@@ -29,16 +34,15 @@
|
|
|
29
34
|
"author": "Marinade Finance",
|
|
30
35
|
"license": "ISC",
|
|
31
36
|
"dependencies": {
|
|
32
|
-
"
|
|
33
|
-
"@nestjs/common": "^10.3.3",
|
|
34
|
-
"axios": "^1.6.7",
|
|
35
|
-
"class-transformer": "^0.5.1",
|
|
36
|
-
"class-validator": "^0.14.1",
|
|
37
|
+
"axios": "^1.6.8",
|
|
37
38
|
"decimal.js": "^10.4.3",
|
|
38
39
|
"expr-eval": "^2.0.2",
|
|
39
|
-
"
|
|
40
|
+
"log4js": "^6.9.1"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
|
-
"@types/
|
|
43
|
+
"@types/jest": "29.5.0",
|
|
44
|
+
"@types/node": "^20.12.7",
|
|
45
|
+
"jest": "29.5.0",
|
|
46
|
+
"ts-jest": "29.0.5"
|
|
43
47
|
}
|
|
44
|
-
}
|
|
48
|
+
}
|