diff --git a/pkg/data/manager.go b/pkg/data/manager.go
index 3e1510a..c74d667 100644
--- a/pkg/data/manager.go
+++ b/pkg/data/manager.go
@@ -28,10 +28,7 @@ func (m *Manager) GetTallies() (map[string]types.ChainTallyInfos, error) {
var mutex sync.Mutex
errors := make([]error, 0)
-
- pools := make(map[string]types.Pool, 0)
- proposals := make(map[string][]types.Proposal, 0)
- tallies := make(map[string]map[string]types.Tally, 0)
+ tallies := make(map[string]types.ChainTallyInfos, 0)
for _, chain := range m.Chains {
rpc := cosmos.NewRPC(chain, m.Logger)
@@ -40,81 +37,18 @@ func (m *Manager) GetTallies() (map[string]types.ChainTallyInfos, error) {
go func(c *types.Chain, rpc *cosmos.RPC) {
defer wg.Done()
- pool, err := rpc.GetStakingPool()
+ talliesForChain, err := rpc.GetTallies()
mutex.Lock()
if err != nil {
m.Logger.Error().Err(err).Str("chain", c.Name).Msg("Error fetching staking pool")
errors = append(errors, err)
- } else if pool.Pool == nil {
- m.Logger.Error().Err(err).Str("chain", c.Name).Msg("Staking pool is empty!")
- errors = append(errors, fmt.Errorf("staking pool is empty"))
} else {
- pools[c.Name] = *pool.Pool
+ tallies[c.Name] = talliesForChain
}
mutex.Unlock()
}(chain, rpc)
-
- wg.Add(1)
- go func(c *types.Chain, rpc *cosmos.RPC) {
- defer wg.Done()
-
- chainProposals, err := rpc.GetAllProposals()
-
- mutex.Lock()
-
- if err != nil {
- m.Logger.Error().Err(err).Str("chain", c.Name).Msg("Error fetching chain proposals")
- errors = append(errors, err)
-
- mutex.Unlock()
- return
- } else {
- proposals[c.Name] = chainProposals
- }
-
- mutex.Unlock()
-
- var internalWg sync.WaitGroup
-
- for _, proposal := range chainProposals {
- internalWg.Add(1)
-
- go func(c *types.Chain, p types.Proposal) {
- defer internalWg.Done()
-
- tally, err := rpc.GetTally(p.ID)
-
- mutex.Lock()
- defer mutex.Unlock()
-
- if err != nil {
- m.Logger.Error().
- Err(err).
- Str("chain", c.Name).
- Str("proposal_id", p.ID).
- Msg("Error fetching tally for proposal")
- errors = append(errors, err)
- } else if tally.Tally == nil {
- m.Logger.Error().
- Err(err).
- Str("chain", c.Name).
- Str("proposal_id", p.ID).
- Msg("Tally is empty")
- errors = append(errors, fmt.Errorf("tally is empty"))
- } else {
- if _, ok := tallies[c.Name]; !ok {
- tallies[c.Name] = make(map[string]types.Tally, 0)
- }
-
- tallies[c.Name][p.ID] = *tally.Tally
- }
- }(c, proposal)
- }
-
- internalWg.Wait()
- }(chain, rpc)
}
wg.Wait()
@@ -124,41 +58,7 @@ func (m *Manager) GetTallies() (map[string]types.ChainTallyInfos, error) {
return map[string]types.ChainTallyInfos{}, fmt.Errorf("could not get tallies info: got %d errors", len(errors))
}
- tallyInfos := make(map[string]types.ChainTallyInfos, 0)
-
- for chainName, chainProposals := range proposals {
- chain := m.Chains.FindByName(chainName)
- if chain == nil {
- return map[string]types.ChainTallyInfos{}, fmt.Errorf("could not chain with name %s", chainName)
- }
-
- if _, ok := tallyInfos[chainName]; !ok {
- tallyInfos[chainName] = types.ChainTallyInfos{
- Chain: chain,
- TallyInfos: make([]types.TallyInfo, len(chainProposals)),
- }
- }
-
- for index, proposal := range chainProposals {
- tally, ok := tallies[chainName][proposal.ID]
- if !ok {
- return map[string]types.ChainTallyInfos{}, fmt.Errorf("could not get tallies info")
- }
-
- pool, ok := pools[chainName]
- if !ok {
- return map[string]types.ChainTallyInfos{}, fmt.Errorf("could not get tallies info")
- }
-
- tallyInfos[chainName].TallyInfos[index] = types.TallyInfo{
- Proposal: proposal,
- Tally: tally,
- Pool: pool,
- }
- }
- }
-
- return tallyInfos, nil
+ return tallies, nil
}
func (m *Manager) GetChainParams(chain *types.Chain) (*types.ChainWithVotingParams, []error) {
diff --git a/pkg/fetchers/cosmos/fetcher.go b/pkg/fetchers/cosmos/fetcher.go
index 2366135..476ea38 100644
--- a/pkg/fetchers/cosmos/fetcher.go
+++ b/pkg/fetchers/cosmos/fetcher.go
@@ -2,12 +2,9 @@ package cosmos
import (
"encoding/json"
- "errors"
"fmt"
"main/pkg/fetchers/cosmos/responses"
- "main/pkg/utils"
"net/http"
- "strings"
"time"
"main/pkg/types"
@@ -18,6 +15,7 @@ import (
const PaginationLimit = 1000
type RPC struct {
+ ChainConfig *types.Chain
URLs []string
ProposalsType string
Logger zerolog.Logger
@@ -25,6 +23,7 @@ type RPC struct {
func NewRPC(chainConfig *types.Chain, logger zerolog.Logger) *RPC {
return &RPC{
+ ChainConfig: chainConfig,
URLs: chainConfig.LCDEndpoints,
ProposalsType: chainConfig.ProposalsType,
Logger: logger.With().Str("component", "rpc").Logger(),
@@ -39,131 +38,13 @@ func (rpc *RPC) GetAllProposals() ([]types.Proposal, *types.QueryError) {
return rpc.GetAllV1beta1Proposals()
}
-func (rpc *RPC) GetAllV1beta1Proposals() ([]types.Proposal, *types.QueryError) {
- proposals := []types.Proposal{}
- offset := 0
-
- for {
- url := fmt.Sprintf(
- // 2 is for PROPOSAL_STATUS_VOTING_PERIOD
- "/cosmos/gov/v1beta1/proposals?pagination.limit=%d&pagination.offset=%d&proposal_status=2",
- PaginationLimit,
- offset,
- )
-
- var batchProposals responses.V1Beta1ProposalsRPCResponse
- if errs := rpc.Get(url, &batchProposals); len(errs) > 0 {
- return nil, &types.QueryError{
- QueryError: nil,
- NodeErrors: errs,
- }
- }
-
- if batchProposals.Message != "" {
- return nil, &types.QueryError{
- QueryError: errors.New(batchProposals.Message),
- }
- }
-
- parsedProposals := utils.Map(batchProposals.Proposals, func(p responses.V1beta1Proposal) types.Proposal {
- return p.ToProposal()
- })
- proposals = append(proposals, parsedProposals...)
- if len(batchProposals.Proposals) < PaginationLimit {
- break
- }
-
- offset += PaginationLimit
- }
-
- return proposals, nil
-}
-
-func (rpc *RPC) GetAllV1Proposals() ([]types.Proposal, *types.QueryError) {
- proposals := []types.Proposal{}
- offset := 0
-
- for {
- url := fmt.Sprintf(
- // 2 is for PROPOSAL_STATUS_VOTING_PERIOD
- "/cosmos/gov/v1/proposals?pagination.limit=%d&pagination.offset=%d&proposal_status=2",
- PaginationLimit,
- offset,
- )
-
- var batchProposals responses.V1ProposalsRPCResponse
- if errs := rpc.Get(url, &batchProposals); len(errs) > 0 {
- return nil, &types.QueryError{
- QueryError: nil,
- NodeErrors: errs,
- }
- }
-
- if batchProposals.Message != "" {
- return nil, &types.QueryError{
- QueryError: errors.New(batchProposals.Message),
- }
- }
-
- parsedProposals := utils.Map(batchProposals.Proposals, func(p responses.V1Proposal) types.Proposal {
- return p.ToProposal()
- })
- proposals = append(proposals, parsedProposals...)
- if len(batchProposals.Proposals) < PaginationLimit {
- break
- }
-
- offset += PaginationLimit
- }
-
- return proposals, nil
-}
-
-func (rpc *RPC) GetVote(proposal, voter string) (*types.Vote, *types.QueryError) {
- url := fmt.Sprintf(
- "/cosmos/gov/v1beta1/proposals/%s/votes/%s",
- proposal,
- voter,
- )
-
- var vote responses.VoteRPCResponse
- if errs := rpc.Get(url, &vote); len(errs) > 0 {
- return nil, &types.QueryError{
- QueryError: nil,
- NodeErrors: errs,
- }
- }
-
- if vote.IsError() {
- // not voted
- if strings.Contains(vote.Message, "not found") {
- return nil, nil
- }
-
- // some other errors
- return nil, &types.QueryError{
- QueryError: errors.New(vote.Message),
- }
- }
-
- voteParsed, err := vote.ToVote()
- if err != nil {
- return nil, &types.QueryError{
- QueryError: err,
- NodeErrors: nil,
- }
- }
-
- return voteParsed, nil
-}
-
-func (rpc *RPC) GetTally(proposal string) (*types.TallyRPCResponse, *types.QueryError) {
+func (rpc *RPC) GetTally(proposal string) (*types.Tally, *types.QueryError) {
url := fmt.Sprintf(
"/cosmos/gov/v1beta1/proposals/%s/tally",
proposal,
)
- var tally types.TallyRPCResponse
+ var tally responses.TallyRPCResponse
if errs := rpc.Get(url, &tally); len(errs) > 0 {
return nil, &types.QueryError{
QueryError: nil,
@@ -171,7 +52,7 @@ func (rpc *RPC) GetTally(proposal string) (*types.TallyRPCResponse, *types.Query
}
}
- return &tally, nil
+ return tally.Tally.ToTally(), nil
}
func (rpc *RPC) GetStakingPool() (*types.PoolRPCResponse, *types.QueryError) {
diff --git a/pkg/fetchers/cosmos/proposals_v1.go b/pkg/fetchers/cosmos/proposals_v1.go
new file mode 100644
index 0000000..dfe8278
--- /dev/null
+++ b/pkg/fetchers/cosmos/proposals_v1.go
@@ -0,0 +1,49 @@
+package cosmos
+
+import (
+ "errors"
+ "fmt"
+ "main/pkg/fetchers/cosmos/responses"
+ "main/pkg/types"
+ "main/pkg/utils"
+)
+
+func (rpc *RPC) GetAllV1Proposals() ([]types.Proposal, *types.QueryError) {
+ proposals := []types.Proposal{}
+ offset := 0
+
+ for {
+ url := fmt.Sprintf(
+ // 2 is for PROPOSAL_STATUS_VOTING_PERIOD
+ "/cosmos/gov/v1/proposals?pagination.limit=%d&pagination.offset=%d&proposal_status=2",
+ PaginationLimit,
+ offset,
+ )
+
+ var batchProposals responses.V1ProposalsRPCResponse
+ if errs := rpc.Get(url, &batchProposals); len(errs) > 0 {
+ return nil, &types.QueryError{
+ QueryError: nil,
+ NodeErrors: errs,
+ }
+ }
+
+ if batchProposals.Message != "" {
+ return nil, &types.QueryError{
+ QueryError: errors.New(batchProposals.Message),
+ }
+ }
+
+ parsedProposals := utils.Map(batchProposals.Proposals, func(p responses.V1Proposal) types.Proposal {
+ return p.ToProposal()
+ })
+ proposals = append(proposals, parsedProposals...)
+ if len(batchProposals.Proposals) < PaginationLimit {
+ break
+ }
+
+ offset += PaginationLimit
+ }
+
+ return proposals, nil
+}
diff --git a/pkg/fetchers/cosmos/proposals_v1beta1.go b/pkg/fetchers/cosmos/proposals_v1beta1.go
new file mode 100644
index 0000000..7dfbbf9
--- /dev/null
+++ b/pkg/fetchers/cosmos/proposals_v1beta1.go
@@ -0,0 +1,49 @@
+package cosmos
+
+import (
+ "errors"
+ "fmt"
+ "main/pkg/fetchers/cosmos/responses"
+ "main/pkg/types"
+ "main/pkg/utils"
+)
+
+func (rpc *RPC) GetAllV1beta1Proposals() ([]types.Proposal, *types.QueryError) {
+ proposals := []types.Proposal{}
+ offset := 0
+
+ for {
+ url := fmt.Sprintf(
+ // 2 is for PROPOSAL_STATUS_VOTING_PERIOD
+ "/cosmos/gov/v1beta1/proposals?pagination.limit=%d&pagination.offset=%d&proposal_status=2",
+ PaginationLimit,
+ offset,
+ )
+
+ var batchProposals responses.V1Beta1ProposalsRPCResponse
+ if errs := rpc.Get(url, &batchProposals); len(errs) > 0 {
+ return nil, &types.QueryError{
+ QueryError: nil,
+ NodeErrors: errs,
+ }
+ }
+
+ if batchProposals.Message != "" {
+ return nil, &types.QueryError{
+ QueryError: errors.New(batchProposals.Message),
+ }
+ }
+
+ parsedProposals := utils.Map(batchProposals.Proposals, func(p responses.V1beta1Proposal) types.Proposal {
+ return p.ToProposal()
+ })
+ proposals = append(proposals, parsedProposals...)
+ if len(batchProposals.Proposals) < PaginationLimit {
+ break
+ }
+
+ offset += PaginationLimit
+ }
+
+ return proposals, nil
+}
diff --git a/pkg/fetchers/cosmos/responses/tally.go b/pkg/fetchers/cosmos/responses/tally.go
new file mode 100644
index 0000000..890323d
--- /dev/null
+++ b/pkg/fetchers/cosmos/responses/tally.go
@@ -0,0 +1,28 @@
+package responses
+
+import (
+ "cosmossdk.io/math"
+ "main/pkg/types"
+)
+
+type TallyRPCResponse struct {
+ Code int64 `json:"code"`
+ Message string `json:"message"`
+ Tally *Tally `json:"tally"`
+}
+
+type Tally struct {
+ Yes math.LegacyDec `json:"yes"`
+ No math.LegacyDec `json:"no"`
+ NoWithVeto math.LegacyDec `json:"no_with_veto"`
+ Abstain math.LegacyDec `json:"abstain"`
+}
+
+func (t Tally) ToTally() *types.Tally {
+ return &types.Tally{
+ {Option: "Yes", Voted: t.Yes},
+ {Option: "No", Voted: t.No},
+ {Option: "Abstain", Voted: t.Abstain},
+ {Option: "No with veto", Voted: t.NoWithVeto},
+ }
+}
diff --git a/pkg/fetchers/cosmos/tally.go b/pkg/fetchers/cosmos/tally.go
new file mode 100644
index 0000000..e913655
--- /dev/null
+++ b/pkg/fetchers/cosmos/tally.go
@@ -0,0 +1,120 @@
+package cosmos
+
+import (
+ "cosmossdk.io/math"
+ "fmt"
+ "main/pkg/types"
+ "sync"
+)
+
+func (rpc *RPC) GetTallies() (types.ChainTallyInfos, error) {
+ var wg sync.WaitGroup
+ var mutex sync.Mutex
+
+ errors := make([]error, 0)
+
+ var pool math.LegacyDec
+ var proposals []types.Proposal
+ tallies := make(map[string]types.Tally, 0)
+
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+
+ poolResponse, err := rpc.GetStakingPool()
+
+ mutex.Lock()
+
+ if err != nil {
+ rpc.Logger.Error().Err(err).Msg("Error fetching staking pool")
+ errors = append(errors, err)
+ } else if poolResponse.Pool == nil {
+ rpc.Logger.Error().Err(err).Msg("Staking pool is empty!")
+ errors = append(errors, fmt.Errorf("staking pool is empty"))
+ } else {
+ pool = poolResponse.Pool.BondedTokens
+ }
+ mutex.Unlock()
+ }()
+
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+
+ chainProposals, err := rpc.GetAllProposals()
+
+ mutex.Lock()
+
+ if err != nil {
+ rpc.Logger.Error().Err(err).Msg("Error fetching chain proposals")
+ errors = append(errors, err)
+
+ mutex.Unlock()
+ return
+ } else {
+ proposals = chainProposals
+ }
+
+ mutex.Unlock()
+
+ var internalWg sync.WaitGroup
+
+ for _, proposal := range chainProposals {
+ internalWg.Add(1)
+
+ go func(p types.Proposal) {
+ defer internalWg.Done()
+
+ tally, err := rpc.GetTally(p.ID)
+
+ mutex.Lock()
+ defer mutex.Unlock()
+
+ if err != nil {
+ rpc.Logger.Error().
+ Err(err).
+ Str("proposal_id", p.ID).
+ Msg("Error fetching tally for proposal")
+ errors = append(errors, err)
+ } else if tally == nil {
+ rpc.Logger.Error().
+ Err(err).
+ Str("proposal_id", p.ID).
+ Msg("Tally is empty")
+ errors = append(errors, fmt.Errorf("tally is empty"))
+ } else {
+ tallies[p.ID] = *tally
+ }
+ }(proposal)
+ }
+
+ internalWg.Wait()
+ }()
+
+ wg.Wait()
+
+ if len(errors) > 0 {
+ rpc.Logger.Error().Msg("Errors getting tallies info, not processing")
+ return types.ChainTallyInfos{}, fmt.Errorf("could not get tallies info: got %d errors", len(errors))
+ }
+
+ tallyInfos := types.ChainTallyInfos{
+ Chain: rpc.ChainConfig,
+ TallyInfos: make([]types.TallyInfo, len(proposals)),
+ }
+
+ for index, proposal := range proposals {
+ tally, ok := tallies[proposal.ID]
+ if !ok {
+ return types.ChainTallyInfos{}, fmt.Errorf("could not get tallies info")
+ }
+
+ tallyInfos.TallyInfos[index] = types.TallyInfo{
+ Proposal: proposal,
+ Tally: tally,
+ TotalVotingPower: pool,
+ }
+ }
+
+ return tallyInfos, nil
+}
diff --git a/pkg/fetchers/cosmos/vote.go b/pkg/fetchers/cosmos/vote.go
new file mode 100644
index 0000000..aa34dce
--- /dev/null
+++ b/pkg/fetchers/cosmos/vote.go
@@ -0,0 +1,47 @@
+package cosmos
+
+import (
+ "errors"
+ "fmt"
+ "main/pkg/fetchers/cosmos/responses"
+ "main/pkg/types"
+ "strings"
+)
+
+func (rpc *RPC) GetVote(proposal, voter string) (*types.Vote, *types.QueryError) {
+ url := fmt.Sprintf(
+ "/cosmos/gov/v1beta1/proposals/%s/votes/%s",
+ proposal,
+ voter,
+ )
+
+ var vote responses.VoteRPCResponse
+ if errs := rpc.Get(url, &vote); len(errs) > 0 {
+ return nil, &types.QueryError{
+ QueryError: nil,
+ NodeErrors: errs,
+ }
+ }
+
+ if vote.IsError() {
+ // not voted
+ if strings.Contains(vote.Message, "not found") {
+ return nil, nil
+ }
+
+ // some other errors
+ return nil, &types.QueryError{
+ QueryError: errors.New(vote.Message),
+ }
+ }
+
+ voteParsed, err := vote.ToVote()
+ if err != nil {
+ return nil, &types.QueryError{
+ QueryError: err,
+ NodeErrors: nil,
+ }
+ }
+
+ return voteParsed, nil
+}
diff --git a/pkg/types/responses.go b/pkg/types/responses.go
index 73ffadb..45e0733 100644
--- a/pkg/types/responses.go
+++ b/pkg/types/responses.go
@@ -10,19 +10,6 @@ import (
"cosmossdk.io/math"
)
-type TallyRPCResponse struct {
- Code int64 `json:"code"`
- Message string `json:"message"`
- Tally *Tally `json:"tally"`
-}
-
-type Tally struct {
- Yes math.LegacyDec `json:"yes"`
- No math.LegacyDec `json:"no"`
- NoWithVeto math.LegacyDec `json:"no_with_veto"`
- Abstain math.LegacyDec `json:"abstain"`
-}
-
type PoolRPCResponse struct {
Code int64 `json:"code"`
Message string `json:"message"`
diff --git a/pkg/types/tally.go b/pkg/types/tally.go
new file mode 100644
index 0000000..d221c2f
--- /dev/null
+++ b/pkg/types/tally.go
@@ -0,0 +1,62 @@
+package types
+
+import (
+ "cosmossdk.io/math"
+ "fmt"
+)
+
+type TallyOption struct {
+ Option string
+ Voted math.LegacyDec
+}
+
+type Tally []TallyOption
+
+func (t Tally) GetTotalVoted() math.LegacyDec {
+ sum := math.LegacyNewDec(0)
+
+ for _, option := range t {
+ sum = sum.Add(option.Voted)
+ }
+
+ return sum
+}
+
+func (t Tally) GetVoted(option TallyOption) string {
+ votedPercent := option.Voted.
+ Quo(t.GetTotalVoted()).
+ Mul(math.LegacyNewDec(100)).
+ MustFloat64()
+
+ return fmt.Sprintf(
+ "%.2f%%",
+ votedPercent,
+ )
+}
+
+type TallyInfo struct {
+ Proposal Proposal
+ Tally Tally
+ TotalVotingPower math.LegacyDec
+}
+
+func (t TallyInfo) GetQuorum() string {
+ return fmt.Sprintf(
+ "%.2f%%",
+ t.Tally.GetTotalVoted().
+ Quo(t.TotalVotingPower).
+ Mul(math.LegacyNewDec(100)).
+ MustFloat64(),
+ )
+}
+
+func (t TallyInfo) GetNotVoted() string {
+ return fmt.Sprintf(
+ "%.2f%%",
+ math.LegacyNewDec(100).
+ Sub(t.Tally.GetTotalVoted().
+ Quo(t.TotalVotingPower).
+ Mul(math.LegacyNewDec(100)),
+ ).MustFloat64(),
+ )
+}
diff --git a/pkg/types/types.go b/pkg/types/types.go
index c086419..a2fa4e5 100644
--- a/pkg/types/types.go
+++ b/pkg/types/types.go
@@ -2,8 +2,6 @@ package types
import (
"fmt"
-
- "cosmossdk.io/math"
)
type Link struct {
@@ -19,71 +17,7 @@ func (l Link) Serialize() string {
return fmt.Sprintf("%s", l.Href, l.Name)
}
-type TallyInfo struct {
- Proposal Proposal
- Tally Tally
- Pool Pool
-}
-
type ChainTallyInfos struct {
Chain *Chain
TallyInfos []TallyInfo
}
-
-func (t *TallyInfo) GetNotAbstained() math.LegacyDec {
- return t.Tally.Yes.Add(t.Tally.No).Add(t.Tally.NoWithVeto)
-}
-
-func (t *TallyInfo) GetTotalVoted() math.LegacyDec {
- return t.GetNotAbstained().Add(t.Tally.Abstain)
-}
-
-func (t *TallyInfo) GetQuorum() string {
- return fmt.Sprintf(
- "%.2f%%",
- t.GetTotalVoted().Quo(t.Pool.BondedTokens).Mul(math.LegacyNewDec(100)).MustFloat64(),
- )
-}
-
-func (t *TallyInfo) GetNotVoted() string {
- return fmt.Sprintf(
- "%.2f%%",
- math.LegacyNewDec(100).Sub(t.GetTotalVoted().Quo(t.Pool.BondedTokens).Mul(math.LegacyNewDec(100))).MustFloat64(),
- )
-}
-
-func (t *TallyInfo) GetAbstained() string {
- abstainedPercent := t.Tally.Abstain.Quo(t.GetTotalVoted()).Mul(math.LegacyNewDec(100)).MustFloat64()
-
- return fmt.Sprintf(
- "%.2f%%",
- abstainedPercent,
- )
-}
-
-func (t *TallyInfo) GetYesVotes() string {
- percent := t.Tally.Yes.Quo(t.GetTotalVoted()).Mul(math.LegacyNewDec(100)).MustFloat64()
-
- return fmt.Sprintf(
- "%.2f%%",
- percent,
- )
-}
-
-func (t *TallyInfo) GetNoVotes() string {
- percent := t.Tally.No.Quo(t.GetTotalVoted()).Mul(math.LegacyNewDec(100)).MustFloat64()
-
- return fmt.Sprintf(
- "%.2f%%",
- percent,
- )
-}
-
-func (t *TallyInfo) GetNoWithVetoVotes() string {
- percent := t.Tally.NoWithVeto.Quo(t.GetTotalVoted()).Mul(math.LegacyNewDec(100)).MustFloat64()
-
- return fmt.Sprintf(
- "%.2f%%",
- percent,
- )
-}
diff --git a/templates/telegram/tally.html b/templates/telegram/tally.html
index c908c10..ea65406 100644
--- a/templates/telegram/tally.html
+++ b/templates/telegram/tally.html
@@ -4,16 +4,15 @@
{{- range $chainName, $tallyInfos := . }}
{{- if $tallyInfos.TallyInfos }}
Proposals on chain {{ $tallyInfos.Chain.GetName }}:
-{{ range $tallyInfos.TallyInfos }}
+{{ range $chainIndex, $tallyInfo := $tallyInfos.TallyInfos }}
{{- $proposalLink := $tallyInfos.Chain.GetProposalLink .Proposal }}
Proposal #{{ .Proposal.ID }}: {{ SerializeLink $proposalLink }}
Ends in: {{ .Proposal.GetTimeLeft }}
- Not voted: {{ .GetNotVoted }}
- Voted: {{ .GetQuorum }}
-- Voted "Abstain": {{ .GetAbstained }}
-- Voted "Yes": {{ .GetYesVotes }}
-- Voted "No": {{ .GetNoVotes }}
-- Voted "NoWithVeto": {{ .GetNoWithVetoVotes }}
+{{- range $tallyOptionIndex, $tallyOption := .Tally }}
+- Voted "{{ $tallyOption.Option }}": {{ $tallyInfo.Tally.GetVoted $tallyOption }}
+{{- end }}
{{ end }}
{{- end }}
{{ end }}