diff --git a/pkg/fetchers/fetcher.go b/pkg/fetchers/fetcher.go index acf426d..03460f4 100644 --- a/pkg/fetchers/fetcher.go +++ b/pkg/fetchers/fetcher.go @@ -2,6 +2,7 @@ package fetchers import ( "main/pkg/fetchers/cosmos" + "main/pkg/fetchers/neutron" "main/pkg/types" "github.com/rs/zerolog" @@ -16,5 +17,9 @@ type Fetcher interface { } func GetFetcher(chainConfig *types.Chain, logger zerolog.Logger) Fetcher { + if chainConfig.Type == "neutron" { + return neutron.NewFetcher(chainConfig, logger) + } + return cosmos.NewRPC(chainConfig, logger) } diff --git a/pkg/fetchers/neutron/fetcher.go b/pkg/fetchers/neutron/fetcher.go new file mode 100644 index 0000000..1bf4d81 --- /dev/null +++ b/pkg/fetchers/neutron/fetcher.go @@ -0,0 +1,21 @@ +package neutron + +import ( + "github.com/rs/zerolog" + "main/pkg/http" + "main/pkg/types" +) + +type Fetcher struct { + ChainConfig *types.Chain + Logger zerolog.Logger + Client *http.Client +} + +func NewFetcher(chainConfig *types.Chain, logger zerolog.Logger) *Fetcher { + return &Fetcher{ + ChainConfig: chainConfig, + Logger: logger.With().Str("component", "neutron_fetcher").Logger(), + Client: http.NewClient(chainConfig.Name, chainConfig.LCDEndpoints, logger), + } +} diff --git a/pkg/fetchers/neutron/params.go b/pkg/fetchers/neutron/params.go new file mode 100644 index 0000000..389bf7d --- /dev/null +++ b/pkg/fetchers/neutron/params.go @@ -0,0 +1,11 @@ +package neutron + +import "main/pkg/types" + +func (fetcher *Fetcher) GetChainParams() (*types.ChainWithVotingParams, []error) { + // TODO: fix + return &types.ChainWithVotingParams{ + Chain: fetcher.ChainConfig, + Params: []types.ChainParam{}, + }, nil +} diff --git a/pkg/fetchers/neutron/proposals.go b/pkg/fetchers/neutron/proposals.go new file mode 100644 index 0000000..4503b88 --- /dev/null +++ b/pkg/fetchers/neutron/proposals.go @@ -0,0 +1,36 @@ +package neutron + +import ( + "encoding/base64" + "fmt" + "main/pkg/fetchers/neutron/responses" + "main/pkg/types" +) + +func (fetcher *Fetcher) GetAllProposals() ([]types.Proposal, *types.QueryError) { + query := base64.StdEncoding.EncodeToString([]byte("{\"list_proposals\": {}}")) + + url := fmt.Sprintf( + "/cosmwasm/wasm/v1/contract/%s/smart/%s", + fetcher.ChainConfig.NeutronSmartContract, + query, + ) + + var proposals responses.ProposalsResponse + if errs := fetcher.Client.Get(url, &proposals); len(errs) > 0 { + return nil, &types.QueryError{ + QueryError: nil, + NodeErrors: errs, + } + } + + proposalsParsed, err := proposals.ToProposals() + if err != nil { + return nil, &types.QueryError{ + QueryError: err, + NodeErrors: nil, + } + } + + return proposalsParsed, nil +} diff --git a/pkg/fetchers/neutron/responses/proposals.go b/pkg/fetchers/neutron/responses/proposals.go new file mode 100644 index 0000000..76c4600 --- /dev/null +++ b/pkg/fetchers/neutron/responses/proposals.go @@ -0,0 +1,52 @@ +package responses + +import ( + "main/pkg/types" + "main/pkg/utils" + "strconv" + "time" +) + +type ProposalWithID struct { + ID int `json:"id"` + Proposal Proposal `json:"proposal"` +} +type Proposal struct { + Title string `json:"title"` + Description string `json:"description"` + Expiration struct { + AtTime string `json:"at_time"` + } `json:"expiration"` + Status string `json:"status"` + TotalPower string `json:"total_power"` +} + +type ProposalsResponse struct { + Data struct { + Proposals []ProposalWithID `json:"proposals"` + } `json:"data"` +} + +func (p ProposalsResponse) ToProposals() ([]types.Proposal, error) { + allProposals := utils.Filter(p.Data.Proposals, func(p ProposalWithID) bool { + return p.Proposal.Status == "open" + }) + + proposals := make([]types.Proposal, len(allProposals)) + + for index, proposal := range allProposals { + expiresAt, err := strconv.ParseInt(proposal.Proposal.Expiration.AtTime, 10, 64) + if err != nil { + return nil, err + } + + proposals[index] = types.Proposal{ + ID: strconv.Itoa(proposal.ID), + Title: proposal.Proposal.Title, + Description: proposal.Proposal.Description, + EndTime: time.Unix(0, expiresAt), + } + } + + return proposals, nil +} diff --git a/pkg/fetchers/neutron/tally.go b/pkg/fetchers/neutron/tally.go new file mode 100644 index 0000000..5d8f042 --- /dev/null +++ b/pkg/fetchers/neutron/tally.go @@ -0,0 +1,8 @@ +package neutron + +import "main/pkg/types" + +func (fetcher *Fetcher) GetTallies() (types.ChainTallyInfos, error) { + // TODO: fix + return types.ChainTallyInfos{}, nil +} diff --git a/pkg/fetchers/neutron/vote.go b/pkg/fetchers/neutron/vote.go new file mode 100644 index 0000000..e8e2fc0 --- /dev/null +++ b/pkg/fetchers/neutron/vote.go @@ -0,0 +1,8 @@ +package neutron + +import "main/pkg/types" + +func (fetcher *Fetcher) GetVote(proposal, voter string) (*types.Vote, *types.QueryError) { + // TODO: fix + return nil, nil +} diff --git a/pkg/types/chain.go b/pkg/types/chain.go index 9a3b73c..fe1a447 100644 --- a/pkg/types/chain.go +++ b/pkg/types/chain.go @@ -32,6 +32,9 @@ type Chain struct { Wallets []*Wallet `toml:"wallets"` MintscanPrefix string `toml:"mintscan-prefix"` Explorer *Explorer `toml:"explorer"` + + Type string `toml:"type" default:"cosmos"` + NeutronSmartContract string `toml:"neutron-smart-contract" default:"neutron1436kxs0w2es6xlqpp9rd35e3d0cjnw4sv8j3a7483sgks29jqwgshlt6zh"` } func (c Chain) Validate() error { @@ -39,6 +42,10 @@ func (c Chain) Validate() error { return fmt.Errorf("empty chain name") } + if !utils.Contains([]string{"cosmos", "neutron"}, c.Type) { + return fmt.Errorf("expected type to be one of 'cosmos', 'neutron', but got '%s'", c.Type) + } + if len(c.LCDEndpoints) == 0 { return fmt.Errorf("no LCD endpoints provided") } diff --git a/pkg/types/config_test.go b/pkg/types/config_test.go index e574824..f9675d8 100644 --- a/pkg/types/config_test.go +++ b/pkg/types/config_test.go @@ -52,6 +52,7 @@ func TestValidateChainWithValidConfig(t *testing.T) { LCDEndpoints: []string{"endpoint"}, Wallets: []*Wallet{{Address: "wallet"}}, ProposalsType: "v1", + Type: "cosmos", } err := chain.Validate() @@ -159,6 +160,25 @@ func TestValidateConfigInvalidWallet(t *testing.T) { require.Error(t, err, nil, "Error should be presented!") } +func TestValidateConfigInvalidType(t *testing.T) { + t.Parallel() + + config := Config{ + Timezone: "Europe/Moscow", + Chains: []*Chain{ + { + Name: "chain", + LCDEndpoints: []string{"endpoint"}, + Wallets: []*Wallet{{Address: "wallet"}}, + ProposalsType: "v1", + Type: "invalid", + }, + }, + } + err := config.Validate() + require.Error(t, err, nil, "Error should be presented!") +} + func TestValidateConfigValidChain(t *testing.T) { t.Parallel() @@ -170,6 +190,7 @@ func TestValidateConfigValidChain(t *testing.T) { LCDEndpoints: []string{"endpoint"}, Wallets: []*Wallet{{Address: "wallet"}}, ProposalsType: "v1", + Type: "cosmos", }, }, }