diff --git a/README.md b/README.md index 7b3b787..a1d84cd 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ NOTE: You can get your API Key by signing up [here](https://www.validators.app/u 18. [Sui](https://sui.io/) 19. [Pulsechain](https://pulsechain.com/) 20. [Celestia](https://celestia.org/) +21. [MultiversX](https://multiversx.com/) ### Notes diff --git a/core/chains/chain.go b/core/chains/chain.go index c39ded8..3448fd8 100644 --- a/core/chains/chain.go +++ b/core/chains/chain.go @@ -24,6 +24,7 @@ const ( AVAX Token = "AVAX" BLD Token = "BLD" BNB Token = "BNB" + EGLD Token = "EGLD" ETH2 Token = "ETH2" GRT Token = "GRT" HBAR Token = "HBAR" @@ -52,6 +53,8 @@ func (t Token) ChainName() string { return "Agoric" case BNB: return "Binance" + case EGLD: + return "MultiversX" case ETH2: return "Ethereum Proof-of-Stake" case GRT: @@ -87,7 +90,7 @@ func (t Token) ChainName() string { } } -var Tokens = []Token{ATOM, AVAX, BLD, BNB, ETH2, GRT, HBAR, JUNO, MATIC, MINA, NEAR, OSMO, PLS, REGEN, RUNE, SOL, STARS, SUI, TIA} +var Tokens = []Token{ATOM, AVAX, BLD, BNB, EGLD, ETH2, GRT, HBAR, JUNO, MATIC, MINA, NEAR, OSMO, PLS, REGEN, RUNE, SOL, STARS, SUI, TIA} // NewState returns a new fresh state. func NewState() ChainState { @@ -129,6 +132,8 @@ func newValues(token Token) (int, error) { currVal, err = Agoric() case BNB: currVal, err = Binance() + case EGLD: + currVal, err = MultiversX() case ETH2: currVal, err = Eth2() case GRT: diff --git a/core/chains/multiversx.go b/core/chains/multiversx.go new file mode 100644 index 0000000..7491fd4 --- /dev/null +++ b/core/chains/multiversx.go @@ -0,0 +1,112 @@ +package chains + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "sort" + + "github.com/xenowits/nakamoto-coefficient-calculator/core/utils" +) + +const totalValidatorsUrl = "https://api.multiversx.com/stake" +const identitiesUrl = "https://api.multiversx.com/identities" + +type MultiversXTotalValidatorsResponse struct { + TotalValidators int64 `json:"totalValidators"` +} + +type MultiversXIdentitiesResponse []struct { + Locked string `json:"locked"` + NumValidators int64 `json:"validators"` +} + +func MultiversX() (int, error) { + numValidatorsPerIdentity := make([]int64, 0) + + totalNumberOfValidators, err := getTotalValidatorsNumber() + if err != nil { + return 0, err + } + + identities, err := getIdentities() + if err != nil { + return 0, err + } + + for _, identity := range identities { + if identity.Locked == "0" { + continue + } + numValidatorsPerIdentity = append(numValidatorsPerIdentity, identity.NumValidators) + } + + sort.Slice(numValidatorsPerIdentity, func(i, j int) bool { + return numValidatorsPerIdentity[i] > numValidatorsPerIdentity[j] + }) + + fmt.Println("Total voting power:", totalNumberOfValidators) + + // there is a fixed number of validator seats in MultiversX - currently 3200 + // the Nakamoto coefficient can be computed by counting the identities (node operators) + // that control more than 33% of the total number of validators + nakamotoCoefficient := utils.CalcNakamotoCoefficient(totalNumberOfValidators, numValidatorsPerIdentity) + fmt.Println("The Nakamoto coefficient for MultiversX is", nakamotoCoefficient) + + return nakamotoCoefficient, nil +} + +func getTotalValidatorsNumber() (int64, error) { + resp, err := http.Get(totalValidatorsUrl) + if err != nil { + return 0, err + } + + defer closeBody(resp) + + body, err := io.ReadAll(resp.Body) + if err != nil { + return 0, err + } + + var response MultiversXTotalValidatorsResponse + err = json.Unmarshal(body, &response) + if err != nil { + return 0, err + } + + return response.TotalValidators, nil +} + +func getIdentities() (MultiversXIdentitiesResponse, error) { + resp, err := http.Get(identitiesUrl) + if err != nil { + return nil, err + } + + defer closeBody(resp) + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + var response MultiversXIdentitiesResponse + err = json.Unmarshal(body, &response) + if err != nil { + return nil, err + } + + return response, nil +} + +func closeBody(resp *http.Response) { + if resp == nil || resp.Body == nil { + return + } + if closeErr := resp.Body.Close(); closeErr != nil { + log.Printf("failed to close response body: %s", closeErr) + } +}