-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10 from streamingfast/enol/mantra
Mantra Codegen
- Loading branch information
Showing
24 changed files
with
986 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package mantra_events | ||
|
||
import "sort" | ||
|
||
type ChainConfig struct { | ||
ExplorerLink string | ||
ID string // Public | ||
DisplayName string // Public | ||
Network string | ||
} | ||
|
||
var ChainConfigs []*ChainConfig | ||
|
||
var ChainConfigByID = map[string]*ChainConfig{ | ||
"mantra-mainnet": { | ||
ExplorerLink: "https://explorer.mantrachain.io/", | ||
DisplayName: "Mantra Mainnet", | ||
Network: "mantra-mainnet", | ||
}, | ||
"mantra-testnet": { | ||
ExplorerLink: "https://testnet.mantra.explorers.guru/", | ||
DisplayName: "Mantra Testnet", | ||
Network: "mantra-testnet", | ||
}, | ||
} | ||
|
||
func init() { | ||
for k, v := range ChainConfigByID { | ||
v.ID = k | ||
ChainConfigs = append(ChainConfigs, v) | ||
} | ||
sort.Slice(ChainConfigs, func(i, j int) bool { | ||
return ChainConfigs[i].DisplayName < ChainConfigs[j].DisplayName | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,304 @@ | ||
package mantra_events | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"strconv" | ||
"strings" | ||
|
||
codegen "github.com/streamingfast/substreams-codegen" | ||
"github.com/streamingfast/substreams-codegen/loop" | ||
) | ||
|
||
var QuitInvalidContext = loop.Quit(fmt.Errorf("invalid state context: no current contract")) | ||
var MantraTestnetDefaultStartBlock uint64 = 1 | ||
|
||
type Convo struct { | ||
*codegen.Conversation[*Project] | ||
} | ||
|
||
func init() { | ||
codegen.RegisterConversation( | ||
"mantra-events", | ||
"Stream Mantra Events with specific attributes if specified", | ||
"Create an Mantra Substreams module from specific events", | ||
codegen.ConversationFactory(New), | ||
70, | ||
) | ||
} | ||
|
||
func New() codegen.Converser { | ||
return &Convo{&codegen.Conversation[*Project]{ | ||
State: &Project{}, | ||
}} | ||
} | ||
|
||
func (c *Convo) contextEventDesc() *eventDesc { | ||
if c.State.currentEventIdx > len(c.State.EventDescs)-1 { | ||
return nil | ||
} | ||
return c.State.EventDescs[c.State.currentEventIdx] | ||
} | ||
|
||
func (c *Convo) NextStep() (out loop.Cmd) { | ||
p := c.State | ||
|
||
if p.Name == "" { | ||
return cmd(codegen.AskProjectName{}) | ||
} | ||
|
||
if p.ChainName == "" { | ||
return cmd(codegen.AskChainName{}) | ||
} | ||
|
||
if !isValidChainName(p.ChainName) { | ||
return loop.Seq(cmd(codegen.MsgInvalidChainName{}), cmd(codegen.AskChainName{})) | ||
} | ||
|
||
if !p.InitialBlockSet { | ||
return cmd(AskInitialStartBlockType{}) | ||
} | ||
|
||
switch p.DataType { | ||
case "": | ||
return cmd(AskDataType{}) | ||
case "events", "event_groups": | ||
case "transactions": | ||
default: | ||
return loop.Quit(fmt.Errorf("invalid data type %q", p.DataType)) | ||
} | ||
|
||
if len(p.EventDescs) == 0 { | ||
p.currentEventIdx = -1 | ||
p.EventDescs = append(p.EventDescs, &eventDesc{Incomplete: true}) | ||
} | ||
|
||
previousEventIdx := p.currentEventIdx | ||
for idx, evt := range p.EventDescs { | ||
p.currentEventIdx = idx | ||
notifyContext := func(next loop.Cmd) loop.Cmd { | ||
if previousEventIdx != p.currentEventIdx { | ||
return loop.Seq(cmd(MsgEventSwitch{}), next) | ||
} | ||
return next | ||
} | ||
if evt.EventType == "" { | ||
return notifyContext(cmd(AskEventType{})) | ||
} | ||
|
||
if evt.Incomplete { | ||
return notifyContext(cmd(AskEventAttribute{})) | ||
} | ||
} | ||
|
||
if !p.EventsComplete { | ||
return cmd(AskAnotherEventType{}) | ||
} | ||
|
||
return cmd(codegen.RunGenerate{}) | ||
} | ||
|
||
func (c *Convo) Update(msg loop.Msg) loop.Cmd { | ||
switch msg := msg.(type) { | ||
case codegen.MsgStart: | ||
var msgCmd loop.Cmd | ||
if msg.Hydrate != nil { | ||
if err := json.Unmarshal([]byte(msg.Hydrate.SavedState), &c.State); err != nil { | ||
return loop.Quit(fmt.Errorf(`something went wrong, here's an error message to share with our devs (%s); we've notified them already`, err)) | ||
} | ||
msgCmd = c.Msg().Message("Ok, I reloaded your state.").Cmd() | ||
} else { | ||
msgCmd = c.Msg().Message("Ok, let's start a new package.").Cmd() | ||
} | ||
return loop.Seq(msgCmd, c.NextStep()) | ||
|
||
case codegen.AskProjectName: | ||
return c.CmdAskProjectName() | ||
|
||
case codegen.InputProjectName: | ||
c.State.Name = msg.Value | ||
return c.NextStep() | ||
|
||
case codegen.AskChainName: | ||
var labels, values []string | ||
for _, conf := range ChainConfigs { | ||
labels = append(labels, conf.DisplayName) | ||
values = append(values, conf.ID) | ||
} | ||
return c.Action(codegen.InputChainName{}).ListSelect("Please select the chain"). | ||
Labels(labels...). | ||
Values(values...). | ||
Cmd() | ||
|
||
case codegen.MsgInvalidChainName: | ||
return c.Msg(). | ||
Messagef(`Hmm, %q seems like an invalid chain name. Maybe it was supported and is not anymore?`, c.State.ChainName). | ||
Cmd() | ||
|
||
case codegen.InputChainName: | ||
c.State.ChainName = msg.Value | ||
if isValidChainName(msg.Value) { | ||
return loop.Seq( | ||
c.Msg().Messagef("Got it, will be using chain %q", c.State.ChainConfig().DisplayName).Cmd(), | ||
c.NextStep(), | ||
) | ||
} | ||
return c.NextStep() | ||
|
||
case AskInitialStartBlockType: | ||
textInputMessage := "At what block do you want to start indexing data?" | ||
defaultValue := "0" | ||
if isTestnet(c.State.ChainName) { | ||
defaultValue = fmt.Sprintf("%d", MantraTestnetDefaultStartBlock) | ||
textInputMessage = fmt.Sprintf("At what block do you want to start indexing data? (the first available block on %s is: %s)", c.State.ChainName, defaultValue) | ||
} | ||
return c.Action(InputAskInitialStartBlockType{}). | ||
TextInput(textInputMessage, "Submit"). | ||
DefaultValue(defaultValue). | ||
Validation(`^\d+$`, "The start block cannot be empty and must be a number"). | ||
Cmd() | ||
|
||
case InputAskInitialStartBlockType: | ||
initialBlock, err := strconv.ParseUint(msg.Value, 10, 64) | ||
if err != nil { | ||
return loop.Quit(fmt.Errorf("invalid start block input value %q, expected a number", msg.Value)) | ||
} | ||
if isTestnet(c.State.ChainName) && initialBlock < MantraTestnetDefaultStartBlock { | ||
initialBlock = MantraTestnetDefaultStartBlock | ||
} | ||
|
||
c.State.InitialBlock = initialBlock | ||
c.State.InitialBlockSet = true | ||
return c.NextStep() | ||
|
||
case AskDataType: | ||
labels := []string{ | ||
"Specific events", | ||
"All events in transactions where at least one event matches your query", | ||
} | ||
values := []string{EVENTS_DATA_TYPE, EVENT_GROUPS_DATA_TYPE} | ||
return c.Action(InputDataType{}). | ||
ListSelect(fmt.Sprintf("This codegen will build a substreams that filters data based on events.\n" + | ||
"Do you want to target:")). | ||
Labels(labels...). | ||
Values(values...). | ||
Cmd() | ||
|
||
case InputDataType: | ||
c.State.DataType = msg.Value | ||
return c.NextStep() | ||
|
||
case AskEventType: | ||
var cmds []loop.Cmd | ||
|
||
if len(c.State.EventDescs) == 0 { | ||
cmds = append(cmds, c.Msg().Message("Let's start by filtering event types").Cmd()) | ||
} | ||
|
||
cmds = append(cmds, c.Action(InputEventType{}). | ||
TextInput(fmt.Sprintf("Please enter the type of Event that you want to track.\n\nYou can usually find them under the transaction details in the explorer: %s.\nExamples: message, wasm ...", c.State.ChainConfig().ExplorerLink), "Submit"). | ||
Validation(`(.|\s)*\S(.|\s)*`, "The event type cannot be empty"). | ||
Cmd(), | ||
) | ||
|
||
return loop.Seq(cmds...) | ||
|
||
case InputEventType: | ||
evt := c.contextEventDesc() | ||
if evt == nil { | ||
return QuitInvalidContext | ||
} | ||
evt.EventType = strings.TrimSpace(msg.Value) | ||
return c.NextStep() | ||
|
||
case AskEventAttribute: | ||
evt := c.contextEventDesc() | ||
if evt == nil { | ||
return QuitInvalidContext | ||
} | ||
textInput := fmt.Sprintf("Do you want the substreams to match only %q events that contain specific attributes ?\n"+ | ||
"Enter either {attribute_key} or {attribute_key}:{attribute_value} to add such a constraint, or leave empty to skip.", evt.EventType) | ||
|
||
if len(evt.Attributes) > 0 { | ||
textInput = fmt.Sprintf("Do you want to add another attribute constraint to matching the %q event ? (current conditions: %q)\n"+ | ||
"All conditions must be met for an event to match. You can define additional events of the same type to match different conditions.\n"+ | ||
"Enter either {attribute_key} or {attribute_key}:{attribute_value} to add such a constraint, or leave empty to skip.", evt.EventType, evt.GetEventQuery()) | ||
} | ||
return c.Action(InputEventAttribute{}). | ||
TextInput(textInput, | ||
"Submit"). | ||
Cmd() | ||
|
||
case InputEventAttribute: | ||
evt := c.contextEventDesc() | ||
if evt == nil { | ||
return QuitInvalidContext | ||
} | ||
if msg.Value == "" { | ||
evt.Incomplete = false | ||
return c.NextStep() | ||
} | ||
if evt.Attributes == nil { | ||
evt.Attributes = make(map[string]string) | ||
} | ||
kv := strings.SplitN(msg.Value, ":", 2) | ||
k := kv[0] | ||
v := "" | ||
if len(kv) == 2 { | ||
v = kv[1] | ||
} | ||
evt.Attributes[k] = v | ||
return c.NextStep() | ||
|
||
case AskAnotherEventType: | ||
return loop.Seq( | ||
c.Msg().Messagef("Current filtering event types %q", c.State.GetEventsQuery()).Cmd(), | ||
c.Action(InputAskAnotherEventType{}). | ||
ListSelect("Do you want to add another event type"). | ||
Labels("Yes", "No"). | ||
Values("yes", "no").Cmd(), | ||
) | ||
|
||
case InputAskAnotherEventType: | ||
switch msg.Value { | ||
case "yes": | ||
c.State.EventDescs = append(c.State.EventDescs, &eventDesc{Incomplete: true}) | ||
return c.NextStep() | ||
case "no": | ||
c.State.EventsComplete = true | ||
return c.NextStep() | ||
default: | ||
return loop.Quit(fmt.Errorf("invalid selection input value %q, expected 'yes', 'more' or 'no'", msg.Value)) | ||
} | ||
|
||
case MsgEventSwitch: | ||
evt := c.contextEventDesc() | ||
if evt == nil { | ||
return QuitInvalidContext | ||
} | ||
if evt.EventType != "" { | ||
return c.Msg().Messagef("Ok, now let's talk about event %q", | ||
evt.EventType, | ||
).Cmd() | ||
} | ||
return nil | ||
|
||
case codegen.RunGenerate: | ||
return c.CmdGenerate(c.State.Generate) | ||
|
||
case codegen.ReturnGenerate: | ||
return c.CmdDownloadFiles(msg) | ||
} | ||
|
||
return loop.Quit(fmt.Errorf("invalid loop message: %T", msg)) | ||
} | ||
|
||
func isValidChainName(input string) bool { | ||
return ChainConfigByID[input] != nil | ||
} | ||
|
||
func isTestnet(input string) bool { | ||
return ChainConfigByID[input].Network == "mantra-testnet" | ||
} | ||
|
||
var cmd = codegen.Cmd |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package mantra_events | ||
|
||
import ( | ||
"embed" | ||
|
||
codegen "github.com/streamingfast/substreams-codegen" | ||
) | ||
|
||
//go:embed templates/* | ||
var templatesFS embed.FS | ||
|
||
func (p *Project) Generate() codegen.ReturnGenerate { | ||
return codegen.GenerateTemplateTree(p, templatesFS, map[string]string{ | ||
".gitignore.gotmpl": ".gitignore", | ||
"README.md.gotmpl": "README.md", | ||
"substreams.yaml.gotmpl": "substreams.yaml", | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package mantra_events | ||
|
||
import ( | ||
"github.com/streamingfast/logging" | ||
) | ||
|
||
var zlog, tracer = logging.PackageLogger("mantra-events", "github.com/streamingfast/substreams-codegen/mantra-events") |
Oops, something went wrong.