From fc97144f18c285280ef49601dc78a1525ffa821d Mon Sep 17 00:00:00 2001 From: Filippo Scaramuzza Date: Fri, 17 Nov 2023 22:59:25 +0100 Subject: [PATCH 1/6] feat: added cardano --- core/chains/cardano.go | 73 ++++++++++++++++++++++++++++++++++++++++++ core/chains/chain.go | 7 +++- 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 core/chains/cardano.go diff --git a/core/chains/cardano.go b/core/chains/cardano.go new file mode 100644 index 0000000..98a57d1 --- /dev/null +++ b/core/chains/cardano.go @@ -0,0 +1,73 @@ +package chains + +import ( + "encoding/json" + "fmt" + "log" + "math/big" + "net/http" + "sort" + + utils "github.com/xenowits/nakamoto-coefficient-calculator/core/utils" +) + +type CardanoResponse struct { + Code int `json:"code"` + Time string `json:"time"` + Msg string `json:"msg"` + Data []CardanoPool `json:"data"` +} + +type CardanoPool struct { + PoolID string `json:"pool_id"` + Name string `json:"name"` + Stake string `json:"stake"` + BlocksLifetime string `json:"blocks_lifetime"` + ROALifetime string `json:"roa_lifetime"` + Pledge string `json:"pledge"` + Delegators string `json:"delegators"` + Saturation float64 `json:"saturation"` +} + +func Cardano() (int, error) { + url := "https://js.cexplorer.io/api-static/pool/list.json" + + var stakeAmounts []big.Int + + resp, err := http.Get(url) + if err != nil { + log.Println(err) + return 0, err + } + defer resp.Body.Close() + + var response CardanoResponse + err = json.NewDecoder(resp.Body).Decode(&response) + if err != nil { + return 0, err + } + + // Loop through the pools and extract stake amounts + for _, pool := range response.Data { + stakeInt, ok := new(big.Int).SetString(pool.Stake, 10) + if !ok { + log.Println("Error converting stake amount to big.Int") + continue + } + stakeAmounts = append(stakeAmounts, *stakeInt) + } + + // need to sort the stake amounts in descending order + sort.Slice(stakeAmounts, func(i, j int) bool { + return stakeAmounts[i].Cmp(&stakeAmounts[j]) == 1 + }) + + totalStake := utils.CalculateTotalVotingPowerBigNums(stakeAmounts) + fmt.Println("Total voting power:", new(big.Float).SetInt(totalStake)) + + // now we're ready to calculate the nakamoto coefficient + nakamotoCoefficient := utils.CalcNakamotoCoefficientBigNums(totalStake, stakeAmounts) + fmt.Println("The Nakamoto coefficient for Cardano is", nakamotoCoefficient) + + return nakamotoCoefficient, nil +} diff --git a/core/chains/chain.go b/core/chains/chain.go index c39ded8..99fa62f 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" + ADA Token = "ADA" 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 ADA: + return "Cardano" 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, ADA, 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 ADA: + currVal, err = Cardano() case ETH2: currVal, err = Eth2() case GRT: From f295ff6e0c87a0d190e885af8f6ef60758442e24 Mon Sep 17 00:00:00 2001 From: Filippo Scaramuzza Date: Tue, 2 Jul 2024 15:56:30 +0200 Subject: [PATCH 2/6] fix: changed cardano data source --- core/chains/cardano.go | 92 ++++++++++--------- .../calc_nakamoto_coefficient_big_nums.go | 18 ++++ 2 files changed, 65 insertions(+), 45 deletions(-) diff --git a/core/chains/cardano.go b/core/chains/cardano.go index 98a57d1..9796ec0 100644 --- a/core/chains/cardano.go +++ b/core/chains/cardano.go @@ -6,68 +6,70 @@ import ( "log" "math/big" "net/http" - "sort" utils "github.com/xenowits/nakamoto-coefficient-calculator/core/utils" ) -type CardanoResponse struct { - Code int `json:"code"` - Time string `json:"time"` - Msg string `json:"msg"` - Data []CardanoPool `json:"data"` -} - -type CardanoPool struct { - PoolID string `json:"pool_id"` - Name string `json:"name"` - Stake string `json:"stake"` - BlocksLifetime string `json:"blocks_lifetime"` - ROALifetime string `json:"roa_lifetime"` - Pledge string `json:"pledge"` - Delegators string `json:"delegators"` - Saturation float64 `json:"saturation"` +type ChartData struct { + AvgStake float64 `json:"avgstake"` + DelegateCount int `json:"delegatecount"` + Epoch int `json:"epoch"` + Label string `json:"label"` + Leverage string `json:"leverage"` + MavGroup string `json:"mavgroup"` + Pledge float64 `json:"pledge"` + PoolCount int `json:"poolcount"` + PrctStake float64 `json:"prctstake"` + Stake float64 `json:"stake"` } func Cardano() (int, error) { - url := "https://js.cexplorer.io/api-static/pool/list.json" + url := "https://api.balanceanalytics.io/rpc/pool_group_stake_donut" - var stakeAmounts []big.Int + req, err := http.NewRequest("GET", url, nil) + if err != nil { + log.Println("Error creating request:", err) + return 0, err + } - resp, err := http.Get(url) - if err != nil { - log.Println(err) - return 0, err - } - defer resp.Body.Close() + // This is the PUBLIC_BALANCE_API_TOKEN taken from https://www.balanceanalytics.io/chartboards/donut_shop + req.Header.Set("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoid2ViYXBwX3VzZXIifQ.eg3Zb9ZduibYJr1pgUrfqy4PFhkVU1uO_F9gFPBZnBI") + req.Header.Set("Content-Type", "application/json") - var response CardanoResponse - err = json.NewDecoder(resp.Body).Decode(&response) - if err != nil { - return 0, err - } + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Println("Error making request:", err) + return 0, err + } + defer resp.Body.Close() + + var responseData []struct { + Chartdata []ChartData `json:"chartdata"` + } + err = json.NewDecoder(resp.Body).Decode(&responseData) + if err != nil { + log.Println("Error decoding JSON:", err) + return 0, err + } - // Loop through the pools and extract stake amounts - for _, pool := range response.Data { - stakeInt, ok := new(big.Int).SetString(pool.Stake, 10) - if !ok { - log.Println("Error converting stake amount to big.Int") - continue + var votingPowers []big.Int + for _, data := range responseData { + for _, chartData := range data.Chartdata { + stakeInt := big.NewInt(int64(chartData.Stake)) + votingPowers = append(votingPowers, *stakeInt) } - stakeAmounts = append(stakeAmounts, *stakeInt) } - // need to sort the stake amounts in descending order - sort.Slice(stakeAmounts, func(i, j int) bool { - return stakeAmounts[i].Cmp(&stakeAmounts[j]) == 1 - }) + // Calculate total voting power + totalVotingPower := utils.CalculateTotalVotingPowerBigNums(votingPowers) - totalStake := utils.CalculateTotalVotingPowerBigNums(stakeAmounts) - fmt.Println("Total voting power:", new(big.Float).SetInt(totalStake)) + // Calculate Nakamoto coefficient + nakamotoCoefficient := utils.CalcNakamotoCoefficientBigNums51(totalVotingPower, votingPowers) - // now we're ready to calculate the nakamoto coefficient - nakamotoCoefficient := utils.CalcNakamotoCoefficientBigNums(totalStake, stakeAmounts) + fmt.Println("Total voting power:", totalVotingPower) fmt.Println("The Nakamoto coefficient for Cardano is", nakamotoCoefficient) + // Return Nakamoto coefficient return nakamotoCoefficient, nil } diff --git a/core/utils/calc_nakamoto_coefficient_big_nums.go b/core/utils/calc_nakamoto_coefficient_big_nums.go index 80890f2..257c9c9 100644 --- a/core/utils/calc_nakamoto_coefficient_big_nums.go +++ b/core/utils/calc_nakamoto_coefficient_big_nums.go @@ -19,5 +19,23 @@ func CalcNakamotoCoefficientBigNums(totalVotingPower *big.Int, votingPowers []bi } } + return nakamotoCoefficient +} + +func CalcNakamotoCoefficientBigNums51(totalVotingPower *big.Int, votingPowers []big.Int) int { + thresholdPercent := big.NewFloat(0.51) + thresholdVal := new(big.Float).Mul(new(big.Float).SetInt(totalVotingPower), thresholdPercent) + cumulativeVal := big.NewFloat(0.00) + nakamotoCoefficient := 0 + + for _, vp := range votingPowers { + z := new(big.Float).Add(cumulativeVal, new(big.Float).SetInt(&vp)) + cumulativeVal = z + nakamotoCoefficient += 1 + if cumulativeVal.Cmp(thresholdVal) == +1 { + break + } + } + return nakamotoCoefficient } \ No newline at end of file From 055bcba1d325769acfbe901b34ea96a04b261a5d Mon Sep 17 00:00:00 2001 From: Filippo Scaramuzza Date: Tue, 2 Jul 2024 16:13:03 +0200 Subject: [PATCH 3/6] fix: other merge fixes --- core/chains/chain.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/core/chains/chain.go b/core/chains/chain.go index e51c78c..c8a2583 100644 --- a/core/chains/chain.go +++ b/core/chains/chain.go @@ -64,8 +64,6 @@ func (t Token) ChainName() string { return "MultiversX" case ADA: return "Cardano" - case ETH2: - return "Ethereum Proof-of-Stake" case GRT: return "Graph Protocol" case HBAR: @@ -99,8 +97,7 @@ func (t Token) ChainName() string { } } -var Tokens = []Token{APT, ATOM, AVAX, BLD, BNB, DOT, EGLD, GRT, HBAR, JUNO, MATIC, MINA, NEAR, OSMO, PLS, REGEN, RUNE, SOL, STARS, SUI, TIA} -var Tokens = []Token{ATOM, AVAX, BLD, BNB, ADA, ETH2, GRT, HBAR, JUNO, MATIC, MINA, NEAR, OSMO, PLS, REGEN, RUNE, SOL, STARS, SUI, TIA} +var Tokens = []Token{ADA, APT, ATOM, AVAX, BLD, BNB, DOT, EGLD, GRT, HBAR, JUNO, MATIC, MINA, NEAR, OSMO, PLS, REGEN, RUNE, SOL, STARS, SUI, TIA} // NewState returns a new fresh state. func NewState() ChainState { @@ -134,6 +131,8 @@ func newValues(token Token) (int, error) { ) switch token { + case ADA: + currVal, err = Cardano() case APT: currVal, err = Aptos() case ATOM: @@ -148,10 +147,6 @@ func newValues(token Token) (int, error) { currVal, err = Polkadot() case EGLD: currVal, err = MultiversX() - case ADA: - currVal, err = Cardano() - case ETH2: - currVal, err = Eth2() case GRT: currVal, err = Graph() case HBAR: From 34e170b79b1d2a0ca707d48cdca2d06cc4af8095 Mon Sep 17 00:00:00 2001 From: Filippo Scaramuzza Date: Mon, 8 Jul 2024 22:56:14 +0200 Subject: [PATCH 4/6] fix: changed data source to new API by BALANCE and fixed threshold percent value to 0.50 --- core/chains/cardano.go | 67 ++++++++----------- .../calc_nakamoto_coefficient_big_nums.go | 2 +- 2 files changed, 29 insertions(+), 40 deletions(-) diff --git a/core/chains/cardano.go b/core/chains/cardano.go index 9796ec0..48f53bf 100644 --- a/core/chains/cardano.go +++ b/core/chains/cardano.go @@ -10,53 +10,42 @@ import ( utils "github.com/xenowits/nakamoto-coefficient-calculator/core/utils" ) -type ChartData struct { - AvgStake float64 `json:"avgstake"` - DelegateCount int `json:"delegatecount"` - Epoch int `json:"epoch"` - Label string `json:"label"` - Leverage string `json:"leverage"` - MavGroup string `json:"mavgroup"` - Pledge float64 `json:"pledge"` - PoolCount int `json:"poolcount"` - PrctStake float64 `json:"prctstake"` - Stake float64 `json:"stake"` +type CardanoResponse struct { + Label string `json:"label"` + Epoch int `json:"epoch"` + Stake float64 `json:"stake"` } func Cardano() (int, error) { - url := "https://api.balanceanalytics.io/rpc/pool_group_stake_donut" + url := "https://www.balanceanalytics.io/api/mavdata.json" - req, err := http.NewRequest("GET", url, nil) - if err != nil { - log.Println("Error creating request:", err) - return 0, err - } - - // This is the PUBLIC_BALANCE_API_TOKEN taken from https://www.balanceanalytics.io/chartboards/donut_shop - req.Header.Set("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoid2ViYXBwX3VzZXIifQ.eg3Zb9ZduibYJr1pgUrfqy4PFhkVU1uO_F9gFPBZnBI") - req.Header.Set("Content-Type", "application/json") + req, err := http.NewRequest("GET", url, nil) + if err != nil { + log.Println("Error creating request:", err) + return 0, err + } - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - log.Println("Error making request:", err) - return 0, err - } - defer resp.Body.Close() + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + log.Println("Error making request:", err) + return 0, err + } + defer resp.Body.Close() - var responseData []struct { - Chartdata []ChartData `json:"chartdata"` - } - err = json.NewDecoder(resp.Body).Decode(&responseData) - if err != nil { - log.Println("Error decoding JSON:", err) - return 0, err - } + var responseData []struct { + CardanoResponse []CardanoResponse `json:"mav_data"` + } + err = json.NewDecoder(resp.Body).Decode(&responseData) + if err != nil { + log.Println("Error decoding JSON:", err) + return 0, err + } var votingPowers []big.Int - for _, data := range responseData { - for _, chartData := range data.Chartdata { - stakeInt := big.NewInt(int64(chartData.Stake)) + for _, data := range responseData { + for _, mavData := range data.CardanoResponse { + stakeInt := big.NewInt(int64(mavData.Stake)) votingPowers = append(votingPowers, *stakeInt) } } diff --git a/core/utils/calc_nakamoto_coefficient_big_nums.go b/core/utils/calc_nakamoto_coefficient_big_nums.go index 73c3ba9..c344c00 100644 --- a/core/utils/calc_nakamoto_coefficient_big_nums.go +++ b/core/utils/calc_nakamoto_coefficient_big_nums.go @@ -28,7 +28,7 @@ func CalcNakamotoCoefficientBigNums(totalVotingPower *big.Int, votingPowers []bi } func CalcNakamotoCoefficientBigNums51(totalVotingPower *big.Int, votingPowers []big.Int) int { - thresholdPercent := big.NewFloat(0.51) + thresholdPercent := big.NewFloat(0.50) thresholdVal := new(big.Float).Mul(new(big.Float).SetInt(totalVotingPower), thresholdPercent) cumulativeVal := big.NewFloat(0.00) nakamotoCoefficient := 0 From cf427d0ea33c633e17aa465ee2ffc2cda28a7292 Mon Sep 17 00:00:00 2001 From: Filippo Scaramuzza Date: Wed, 24 Jul 2024 11:40:18 +0200 Subject: [PATCH 5/6] fix: stakes were not sorted --- core/chains/cardano.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/core/chains/cardano.go b/core/chains/cardano.go index 48f53bf..0415780 100644 --- a/core/chains/cardano.go +++ b/core/chains/cardano.go @@ -6,12 +6,14 @@ import ( "log" "math/big" "net/http" - + "sort" + utils "github.com/xenowits/nakamoto-coefficient-calculator/core/utils" ) type CardanoResponse struct { Label string `json:"label"` + Class string `json:"class"` Epoch int `json:"epoch"` Stake float64 `json:"stake"` } @@ -50,6 +52,15 @@ func Cardano() (int, error) { } } + // need to sort the powers in descending order since they are in random order + sort.Slice(votingPowers, func(i, j int) bool { + res := (&votingPowers[i]).Cmp(&votingPowers[j]) + if res == 1 { + return true + } + return false + }) + // Calculate total voting power totalVotingPower := utils.CalculateTotalVotingPowerBigNums(votingPowers) From 83af00e27e32ea460eef57b102f22a119fe43787 Mon Sep 17 00:00:00 2001 From: Filippo Scaramuzza Date: Wed, 24 Jul 2024 11:45:17 +0200 Subject: [PATCH 6/6] doc: added cardano to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e622469..93c2c8b 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ we simply calculate: nakamoto-coefficient: no of validators controlling 33% of the total network stake ``` +Note that the threshold may be different for some blockchains, for example, 50%. So, I would suggest users to understand the context, cross-verify and examine the results. For any feedback, please join this [discord](https://discord.gg/Una8qmFg). ### Programming Languages @@ -61,6 +62,7 @@ NOTE: You can get your API Key by signing up [here](https://www.validators.app/u 20. [MultiversX](https://multiversx.com/) 21. [Polkadot](https://polkadot.network/) 22. [Aptos](https://aptosfoundation.org/) +23. [Cardano](https://cardano.org/) ### Notes