Updated robots placement

pull/807/head
kompotkot 2023-06-06 12:25:07 +00:00
rodzic 43a8e09d35
commit ac1a75d043
18 zmienionych plików z 2958 dodań i 0 usunięć

55
robots/.gitignore vendored 100644
Wyświetl plik

@ -0,0 +1,55 @@
# Created by https://www.toptal.com/developers/gitignore/api/go,visualstudiocode
# Edit at https://www.toptal.com/developers/gitignore?templates=go,visualstudiocode
### Go ###
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
# End of https://www.toptal.com/developers/gitignore/api/go,visualstudiocode
# Custom
robots_dev
.secrets/
dev.env
test.env
prod.env
abi/

49
robots/README.md 100644
Wyświetl plik

@ -0,0 +1,49 @@
# robots
Generate terminus interface:
```bash
mkdir -p pkg/terminus
abigen --abi abi/TerminusFacet.json --pkg terminus --out pkg/terminus/terminus.go
```
## Airdrop preparations
Prepare Entity collection:
```bash
curl "https://api.moonstream.to/entity/collections" \
--header "Authorization: Bearer ${MOONSTREAM_ACCESS_TOKEN}" \
--header "Content-Type: application/json" \
--data '{
"name": "Test whitelist 1"
}'
```
### Spire
Currently, the creation of public entity collections is available only to administrators through `spire` CLI.
```bash
export COLLECTION_ID="<uuid_of_previously_created_entity_collection>"
```
List available public autogenerated users:
```bash
public-journals users list
```
Create public user, if required:
```bash
public-journals users create --name "test-public-user"
```
Make collection public with `--entry-update` or `--entry-create` flags if required:
```bash
public-journals journals make --token "${MOONSTREAM_ACCESS_TOKEN}" \
--journal-id "${COLLECTION_ID}" \
--public-user-id "${PUBLIC_USER_ID}"
```

Wyświetl plik

@ -0,0 +1,285 @@
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"math"
"math/big"
"sync"
"sync/atomic"
"time"
humbug "github.com/bugout-dev/humbug/go/pkg"
"github.com/google/uuid"
)
type RobotInstance struct {
ValueToClaim int64
MaxValueToClaim int64
ContractTerminusInstance ContractTerminusInstance
EntityInstance EntityInstance
NetworkInstance NetworkInstance
SignerInstance SignerInstance
MintCounter int64
}
func Airdrop(configs *[]RobotsConfig) {
sessionID := uuid.New().String()
consent := humbug.CreateHumbugConsent(humbug.True)
reporter, err := humbug.CreateHumbugReporter(consent, "moonstream-robots", sessionID, HUMBUG_REPORTER_ROBOTS_HEARTBEAT_TOKEN)
if err != nil {
log.Printf("Unable to specify humbug heartbeat reporter, %v", err)
}
// Record system information
reporter.Publish(humbug.SystemReport())
var robots []RobotInstance
// Configure networks
networks, err := InitializeNetworks()
if err != nil {
log.Fatal(err)
}
log.Println("Initialized configuration of network endpoints and chain IDs")
ctx := context.Background()
for _, config := range *configs {
robot := RobotInstance{
ValueToClaim: config.ValueToClaim,
MaxValueToClaim: config.MaxValueToClaim,
MintCounter: 0, // TODO(kompotkot): Fetch minted number from blockchain
}
// Configure network client
network := networks[config.Blockchain]
client, err := GenDialRpcClient(network.Endpoint)
if err != nil {
log.Fatal(err)
}
robot.NetworkInstance = NetworkInstance{
Blockchain: config.Blockchain,
Endpoint: network.Endpoint,
ChainID: network.ChainID,
Client: client,
}
log.Printf("Initialized configuration of JSON RPC network client for %s blockchain", config.Blockchain)
// Fetch required opts
err = robot.NetworkInstance.FetchSuggestedGasPrice(ctx)
if err != nil {
log.Fatal(err)
}
// Define contract instance
contractAddress := GetTerminusContractAddress(config.TerminusAddress)
contractTerminusInstance, err := InitializeTerminusContractInstance(client, contractAddress)
if err != nil {
log.Fatal(err)
}
robot.ContractTerminusInstance = ContractTerminusInstance{
Address: contractAddress,
Instance: contractTerminusInstance,
TerminusPoolId: config.TerminusPoolId,
}
log.Printf("Initialized configuration of terminus contract instance for %s blockchain", config.Blockchain)
// Configure entity client
entityInstance, err := InitializeEntityInstance(config.CollectionId)
if err != nil {
log.Fatal(err)
}
robot.EntityInstance = *entityInstance
log.Printf("Initialized configuration of entity client for '%s' collection", robot.EntityInstance.CollectionId)
// Configure signer
signer, err := initializeSigner(config.SignerKeyfileName, config.SignerPasswordFileName)
if err != nil {
log.Fatal(err)
}
robot.SignerInstance = *signer
log.Printf("Initialized configuration of signer %s", robot.SignerInstance.Address.String())
robots = append(robots, robot)
}
var wg sync.WaitGroup
for idx, robot := range robots {
wg.Add(1)
go robotRun(
&wg,
robot,
reporter,
idx,
)
}
wg.Wait()
}
type RobotHeartBeatReport struct {
CollectionId string `json:"collection_id"`
CollectionName string `json:"collection_name"`
SignerAddress string `json:"signer_address"`
TerminusPoolId int64 `json:"terminus_pool_id"`
Blockchain string `json:"blockchain"`
MintCounter int64 `json:"mint_counter"`
}
// heartBeat prepares and send HeartBeat report for robot
func heartBeat(
robot RobotInstance,
reporter *humbug.HumbugReporter,
idx int,
) {
reportContent := []byte{}
robotHeartBeatReport := &RobotHeartBeatReport{
CollectionId: robot.EntityInstance.CollectionId,
CollectionName: robot.EntityInstance.CollectionName,
SignerAddress: robot.SignerInstance.Address.String(),
TerminusPoolId: robot.ContractTerminusInstance.TerminusPoolId,
Blockchain: robot.NetworkInstance.Blockchain,
MintCounter: robot.MintCounter,
}
reportContent, err := json.Marshal(robotHeartBeatReport)
if err != nil {
log.Printf("Unable to prepare report content for HeartBeat %v", err)
}
heartBeatReport := humbug.Report{
Title: fmt.Sprintf("Robot %d HB - %s - %s", idx, robot.NetworkInstance.Blockchain, robot.EntityInstance.CollectionName),
Tags: []string{
fmt.Sprintf("index:%d", idx),
fmt.Sprintf("blockchain:%s", robot.NetworkInstance.Blockchain),
fmt.Sprintf("collection_id:%s", robot.EntityInstance.CollectionId),
fmt.Sprintf("terminus_pool_id:%d", robot.ContractTerminusInstance.TerminusPoolId),
fmt.Sprintf("signer_address:%s", robot.SignerInstance.Address.String()),
},
Content: string(reportContent),
}
reporter.Publish(heartBeatReport)
}
// robotRun represents of each robot instance for specific airdrop
func robotRun(
wg *sync.WaitGroup,
robot RobotInstance,
reporter *humbug.HumbugReporter,
idx int,
) {
defer wg.Done()
log.Printf(
"Spawned robot %d for blockchain %s, signer %s, entity collection %s, pool %d",
idx,
robot.NetworkInstance.Blockchain,
robot.SignerInstance.Address.String(),
robot.EntityInstance.CollectionId,
robot.ContractTerminusInstance.TerminusPoolId,
)
minSleepTime := 5
maxSleepTime := 60
timer := minSleepTime
ticker := time.NewTicker(time.Duration(minSleepTime) * time.Second)
for {
select {
case <-ticker.C:
heartBeat(robot, reporter, idx)
empty_addresses_len, err := airdropRun(&robot, idx)
if err != nil {
log.Printf("Robot %d - During AirdropRun an error occurred, err: %v", idx, err)
timer = timer + 10
ticker.Reset(time.Duration(timer) * time.Second)
continue
}
if empty_addresses_len == 0 {
timer = int(math.Min(float64(maxSleepTime), float64(timer+1)))
ticker.Reset(time.Duration(timer) * time.Second)
log.Printf("Robot %d - Sleeping for %d seconds because of no new empty addresses", idx, timer)
continue
}
timer = int(math.Max(float64(minSleepTime), float64(timer-10)))
ticker.Reset(time.Duration(timer) * time.Second)
}
}
}
type Claimant struct {
EntityId string
Address string
}
func airdropRun(robot *RobotInstance, idx int) (int64, error) {
status_code, search_data, err := robot.EntityInstance.FetchPublicSearchUntouched(JOURNAL_SEARCH_BATCH_SIZE)
if err != nil {
return 0, err
}
log.Printf("Robot %d - Received response %d from entities API for collection %s with %d results", idx, status_code, robot.EntityInstance.CollectionId, search_data.TotalResults)
var claimants_len int64
var claimants []Claimant
for _, entity := range search_data.Entities {
claimants = append(claimants, Claimant{
EntityId: entity.EntityId,
Address: entity.Address,
})
claimants_len++
}
if claimants_len == 0 {
return claimants_len, nil
}
// Fetch balances for addresses and update list
balances, err := robot.ContractTerminusInstance.BalanceOfBatch(nil, claimants, robot.ContractTerminusInstance.TerminusPoolId)
if err != nil {
return 0, err
}
maxMintBigInt := big.NewInt(robot.MaxValueToClaim)
var emptyClaimantsLen int64
var emptyClaimants []Claimant
for i, balance := range balances {
// Allow to claim only if less then maxMintBigInt
if balance.Cmp(maxMintBigInt) == -1 {
emptyClaimants = append(emptyClaimants, claimants[i])
emptyClaimantsLen++
}
}
if emptyClaimantsLen > 0 {
log.Printf("Robot %d - Ready to send tokens for %d addresses from collection %s", idx, emptyClaimantsLen, robot.EntityInstance.CollectionId)
auth, err := robot.SignerInstance.CreateTransactor(robot.NetworkInstance)
if err != nil {
return emptyClaimantsLen, err
}
if robot.NetworkInstance.Blockchain == "wyrm" {
auth.GasPrice = big.NewInt(0)
}
tx, err := robot.ContractTerminusInstance.PoolMintBatch(auth, emptyClaimants, robot.ValueToClaim)
if err != nil {
return emptyClaimantsLen, err
}
atomic.AddInt64(&robot.MintCounter, emptyClaimantsLen)
log.Printf("Robot %d - Pending tx for PoolMintBatch on blockchain %s at pool ID %d: 0x%x", idx, robot.NetworkInstance.Blockchain, robot.ContractTerminusInstance.TerminusPoolId, tx.Hash())
}
var touched_entities int64
for _, claimant := range claimants {
_, _, err := robot.EntityInstance.TouchPublicEntity(claimant.EntityId, 10)
if err != nil {
log.Printf("Robot %d - Unable to touch entity with ID: %s for claimant: %s, err: %v", idx, claimant.EntityId, claimant.Address, err)
continue
}
touched_entities++
}
log.Printf("Robot %d - Marked %d entities from %d claimants total", idx, touched_entities, claimants_len)
return emptyClaimantsLen, nil
}

Wyświetl plik

@ -0,0 +1,139 @@
package main
import (
"flag"
"fmt"
"os"
"strings"
)
var (
// Storing CLI definitions at server startup
stateCLI StateCLI
)
// Command Line Interface state
type StateCLI struct {
generateConfigCmd *flag.FlagSet
airdropCmd *flag.FlagSet
versionCmd *flag.FlagSet
// Common flags
configPathFlag string
helpFlag bool
// Airdrop flags
reportMapDuration int
}
type flagSlice []string
func (i *flagSlice) String() string {
return strings.Join(*i, ", ")
}
func (i *flagSlice) Set(value string) error {
*i = append(*i, value)
return nil
}
func (s *StateCLI) usage() {
fmt.Printf(`usage: robots [-h] {%[1]s,%[2]s,%[3]s} ...
Moonstream robots CLI
optional arguments:
-h, --help show this help message and exit
subcommands:
{%[1]s,%[2]s,%[3]s}
`, s.generateConfigCmd.Name(), s.airdropCmd.Name(), s.versionCmd.Name())
}
// Check if required flags are set
func (s *StateCLI) checkRequirements() {
if s.helpFlag {
switch {
case s.generateConfigCmd.Parsed():
fmt.Printf("Generate new configuration\n\n")
s.generateConfigCmd.PrintDefaults()
os.Exit(0)
case s.airdropCmd.Parsed():
fmt.Printf("Run Airdrop robots\n\n")
s.airdropCmd.PrintDefaults()
os.Exit(0)
case s.versionCmd.Parsed():
fmt.Printf("Show version\n\n")
s.versionCmd.PrintDefaults()
os.Exit(0)
default:
s.usage()
os.Exit(0)
}
}
}
func (s *StateCLI) populateCLI() {
// Subcommands setup
s.generateConfigCmd = flag.NewFlagSet("generate-config", flag.ExitOnError)
s.airdropCmd = flag.NewFlagSet("airdrop", flag.ExitOnError)
s.versionCmd = flag.NewFlagSet("version", flag.ExitOnError)
// Common flag pointers
for _, fs := range []*flag.FlagSet{s.generateConfigCmd, s.airdropCmd, s.versionCmd} {
fs.BoolVar(&s.helpFlag, "help", false, "Show help message")
fs.StringVar(&s.configPathFlag, "config", "", "Path to configuration file (default: ~/.robots/config.json)")
}
// Airdrop list subcommand flag pointers
s.airdropCmd.IntVar(&s.reportMapDuration, "report-map-duration", 60, "How often to push report map in Humbug journal in seconds, default: 60")
}
func cli() {
stateCLI.populateCLI()
if len(os.Args) < 2 {
stateCLI.usage()
os.Exit(1)
}
// Parse subcommands and appropriate FlagSet
switch os.Args[1] {
case "generate-config":
stateCLI.generateConfigCmd.Parse(os.Args[2:])
stateCLI.checkRequirements()
configPlacement, err := PrepareConfigPlacement(stateCLI.configPathFlag)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if err := GenerateDefaultConfig(configPlacement); err != nil {
fmt.Println(err)
os.Exit(1)
}
case "airdrop":
stateCLI.airdropCmd.Parse(os.Args[2:])
stateCLI.checkRequirements()
// Load configuration
configPlacement, err := PrepareConfigPlacement(stateCLI.configPathFlag)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
configs, err := LoadConfig(configPlacement.ConfigPath)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
Airdrop(configs)
case "version":
stateCLI.versionCmd.Parse(os.Args[2:])
fmt.Printf("v%s\n", ROBOTS_VERSION)
default:
stateCLI.usage()
os.Exit(1)
}
}

Wyświetl plik

@ -0,0 +1,133 @@
/*
Configurations for robots server.
*/
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
)
var (
NODEBALANCER_ACCESS_ID = os.Getenv("ENGINE_NODEBALANCER_ACCESS_ID")
MUMBAI_WEB3_PROVIDER_URI = os.Getenv("MOONSTREAM_MUMBAI_WEB3_PROVIDER_URI")
POLYGON_WEB3_PROVIDER_URI = os.Getenv("MOONSTREAM_POLYGON_WEB3_PROVIDER_URI")
WYRM_WEB3_PROVIDER_URI = os.Getenv("MOONSTREAM_WYRM_WEB3_PROVIDER_URI")
JOURNAL_SEARCH_BATCH_SIZE = 20
ROBOTS_SIGNER_SECRETS_DIR_PATH = os.Getenv("ENGINE_ROBOTS_SECRETS_DIR")
HUMBUG_REPORTER_ROBOTS_HEARTBEAT_TOKEN = os.Getenv("HUMBUG_REPORTER_ROBOTS_HEARTBEAT_TOKEN")
)
type RobotsConfig struct {
CollectionId string `json:"collection_id"`
SignerKeyfileName string `json:"signer_keyfile_name"`
SignerPasswordFileName string `json:"signer_password_file_name"`
TerminusPoolId int64 `json:"terminus_pool_id"`
Blockchain string `json:"blockchain"`
TerminusAddress string `json:"terminus_address"`
ValueToClaim int64 `json:"value_to_claim"`
MaxValueToClaim int64 `json:"max_value_to_claim"`
}
func LoadConfig(configPath string) (*[]RobotsConfig, error) {
rawBytes, err := ioutil.ReadFile(configPath)
if err != nil {
return nil, err
}
robotsConfigs := &[]RobotsConfig{}
err = json.Unmarshal(rawBytes, robotsConfigs)
if err != nil {
return nil, err
}
return robotsConfigs, nil
}
type ConfigPlacement struct {
ConfigDirPath string
ConfigDirExists bool
ConfigPath string
ConfigExists bool
}
// CheckPathExists checks if path to file exists
func CheckPathExists(path string) (bool, error) {
var exists = true
_, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
exists = false
} else {
return exists, fmt.Errorf("Error due checking file path exists, err: %v", err)
}
}
return exists, nil
}
func PrepareConfigPlacement(providedPath string) (*ConfigPlacement, error) {
var configDirPath, configPath string
if providedPath == "" {
homeDir, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("Unable to find user home directory, %v", err)
}
configDirPath = fmt.Sprintf("%s/.robots", homeDir)
configPath = fmt.Sprintf("%s/config.json", configDirPath)
} else {
configPath = strings.TrimSuffix(providedPath, "/")
configDirPath = filepath.Dir(configPath)
}
configDirPathExists, err := CheckPathExists(configDirPath)
if err != nil {
return nil, err
}
configPathExists, err := CheckPathExists(configPath)
if err != nil {
return nil, err
}
configPlacement := &ConfigPlacement{
ConfigDirPath: configDirPath,
ConfigDirExists: configDirPathExists,
ConfigPath: configPath,
ConfigExists: configPathExists,
}
return configPlacement, nil
}
// Generates empty list of robots configuration
func GenerateDefaultConfig(config *ConfigPlacement) error {
if !config.ConfigDirExists {
if err := os.MkdirAll(config.ConfigDirPath, os.ModePerm); err != nil {
return fmt.Errorf("Unable to create directory, %v", err)
}
log.Printf("Config directory created at: %s", config.ConfigDirPath)
}
if !config.ConfigExists {
tempConfig := []RobotsConfig{}
tempConfigJson, err := json.Marshal(tempConfig)
if err != nil {
return fmt.Errorf("Unable to marshal configuration data, err: %v", err)
}
err = ioutil.WriteFile(config.ConfigPath, tempConfigJson, os.ModePerm)
if err != nil {
return fmt.Errorf("Unable to write default config to file %s, err: %v", config.ConfigPath, err)
}
log.Printf("Created default configuration at %s", config.ConfigPath)
}
return nil
}

Wyświetl plik

@ -0,0 +1,145 @@
package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"time"
)
type EntityInstance struct {
PublicEndpoint string
CollectionId string
CollectionName string
Headers map[string]string
}
type EntityResponse struct {
EntityId string `json:"entity_id"`
CollectionId string `json:"collection_id"`
Address string `json:"address"`
Blockchain string `json:"blockchain"`
Name string `json:"name"`
RequiredFields []map[string]string `json:"required_fields"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
type EntitySearchResponse struct {
TotalResults int64 `json:"total_results"`
Offset int64 `json:"offset"`
NextOffset int64 `json:"next_offset"`
MaxScore float64 `json:"max_score"`
Entities []EntityResponse `json:"entities"`
}
type EntityCollectionResponse struct {
CollectionId string `json:"collection_id"`
Name string `json:"name"`
}
func InitializeEntityInstance(collectionId string) (*EntityInstance, error) {
MOONSTREAM_ENTITY_URL := os.Getenv("MOONSTREAM_ENTITY_URL")
if MOONSTREAM_ENTITY_URL == "" {
return nil, errors.New("Environment variable MOONSTREAM_ENTITY_URL should be specified")
}
publicEndpoint := fmt.Sprintf("%s/public", MOONSTREAM_ENTITY_URL)
headers := make(map[string]string)
headers["X-Moonstream-Robots"] = "airdrop-robot"
url := fmt.Sprintf("%s/collections/%s", publicEndpoint, collectionId)
body, _, err := caller("GET", url, nil, headers, 15)
if err != nil {
return nil, err
}
var resp EntityCollectionResponse
err = json.Unmarshal(*body, &resp)
if err != nil {
return nil, err
}
entityInstance := EntityInstance{
PublicEndpoint: publicEndpoint,
CollectionId: collectionId,
CollectionName: resp.Name,
Headers: headers,
}
return &entityInstance, nil
}
// Make HTTP calls to required servers
func caller(method, url string, reqBody io.Reader, headers map[string]string, timeout int) (*[]byte, int, error) {
req, err := http.NewRequest(method, url, reqBody)
if err != nil {
return nil, 0, err
}
if len(headers) > 0 {
for k, v := range headers {
req.Header.Set(k, v)
}
}
client := http.Client{Timeout: time.Second * time.Duration(timeout)}
resp, err := client.Do(req)
if err != nil {
return nil, 0, err
}
defer resp.Body.Close()
// Parse response
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, resp.StatusCode, err
}
return &body, resp.StatusCode, nil
}
// FetchPublicSearchUntouched request not touched entities, ready to airdrop
// TODO(kompotkot): Pass with robots header unique identifier of robot
func (ec *EntityInstance) FetchPublicSearchUntouched(limit int) (int, EntitySearchResponse, error) {
data := EntitySearchResponse{}
url := fmt.Sprintf("%s/collections/%s/search?required_field=!touch:true&limit=%d", ec.PublicEndpoint, ec.CollectionId, limit)
body, status_code, err := caller("GET", url, nil, ec.Headers, 15)
if err != nil {
return status_code, data, err
}
var resp EntitySearchResponse
err = json.Unmarshal(*body, &resp)
if err != nil {
return status_code, data, err
}
data = resp
return status_code, data, nil
}
// TODO(kompotkot): Create batch endpoint for tags creation
func (ec *EntityInstance) TouchPublicEntity(entityId string, timeout int) (int, []string, error) {
var data []string
url := fmt.Sprintf("%s/collections/%s/entities/%s", ec.PublicEndpoint, ec.CollectionId, entityId)
body, status_code, err := caller("PUT", url, nil, ec.Headers, timeout)
if err != nil {
return status_code, data, err
}
var resp []string
err = json.Unmarshal(*body, &resp)
if err != nil {
return status_code, data, err
}
data = resp
return status_code, data, nil
}

Wyświetl plik

@ -0,0 +1,5 @@
package main
func main() {
cli()
}

Wyświetl plik

@ -0,0 +1,80 @@
package main
import (
"context"
"errors"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/ethclient"
)
type NetworkClient struct {
Endpoint string
ChainID *big.Int
}
type NetworkInstance struct {
Blockchain string
Endpoint string
ChainID *big.Int
Client *ethclient.Client
GasPrice *big.Int
}
func InitializeNetworks() (map[string]NetworkClient, error) {
networks := make(map[string]NetworkClient)
if NODEBALANCER_ACCESS_ID == "" {
return nil, errors.New("Environment variable ENGINE_NODEBALANCER_ACCESS_ID should be specified")
}
if MUMBAI_WEB3_PROVIDER_URI == "" {
return nil, errors.New("Environment variable MUMBAI_WEB3_PROVIDER_URI should be specified")
}
if POLYGON_WEB3_PROVIDER_URI == "" {
return nil, errors.New("Environment variable POLYGON_WEB3_PROVIDER_URI should be specified")
}
if WYRM_WEB3_PROVIDER_URI == "" {
return nil, errors.New("Environment variable MOONSTREAM_WYRM_WEB3_PROVIDER_URI should be specified")
}
networks["mumbai"] = NetworkClient{
Endpoint: fmt.Sprintf("%s?access_id=%s&data_source=blockchain", MUMBAI_WEB3_PROVIDER_URI, NODEBALANCER_ACCESS_ID),
ChainID: big.NewInt(80001),
}
networks["polygon"] = NetworkClient{
Endpoint: fmt.Sprintf("%s?access_id=%s&data_source=blockchain", POLYGON_WEB3_PROVIDER_URI, NODEBALANCER_ACCESS_ID),
ChainID: big.NewInt(137),
}
networks["wyrm"] = NetworkClient{
Endpoint: WYRM_WEB3_PROVIDER_URI,
ChainID: big.NewInt(322),
}
return networks, nil
}
// GenDialRpcClient parse PRC endpoint to dial client
func GenDialRpcClient(rpc_endpoint_uri string) (*ethclient.Client, error) {
client, err := ethclient.Dial(rpc_endpoint_uri)
if err != nil {
return nil, err
}
return client, nil
}
// FetchSuggestedGasPrice fetch network for suggested gas price
func (ni *NetworkInstance) FetchSuggestedGasPrice(ctx context.Context) error {
gas_price, err := ni.Client.SuggestGasPrice(ctx)
if err != nil {
return err
}
ni.GasPrice = gas_price
return nil
}

Wyświetl plik

@ -0,0 +1,65 @@
package main
import (
"errors"
"fmt"
"io/ioutil"
"strings"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
)
type SignerInstance struct {
Address common.Address
PrivateKey *keystore.Key
}
// initializeSigner parse secrets directory with keyfile and passfile,
// then opens keyfile with password to privateKey
func initializeSigner(keyfileName, passfileName string) (*SignerInstance, error) {
if ROBOTS_SIGNER_SECRETS_DIR_PATH == "" {
return nil, errors.New("Directory with signer secrets not set")
}
keyfilePath := fmt.Sprintf("%s/%s", ROBOTS_SIGNER_SECRETS_DIR_PATH, keyfileName)
keyfilePasswordPath := fmt.Sprintf("%s/%s", ROBOTS_SIGNER_SECRETS_DIR_PATH, passfileName)
passfile, err := ioutil.ReadFile(keyfilePasswordPath)
if err != nil {
return nil, err
}
passfile_lines := strings.Split(string(passfile), "\n")
password := passfile_lines[0]
keyfile, err := ioutil.ReadFile(keyfilePath)
if err != nil {
return nil, err
}
privateKey, err := keystore.DecryptKey(keyfile, password)
if err != nil {
return nil, err
}
signer := SignerInstance{
Address: privateKey.Address,
PrivateKey: privateKey,
}
return &signer, nil
}
func (s *SignerInstance) CreateTransactor(network NetworkInstance) (*bind.TransactOpts, error) {
auth, err := bind.NewKeyedTransactorWithChainID(s.PrivateKey.PrivateKey, network.ChainID)
if err != nil {
return nil, err
}
// auth.Nonce = big.NewInt(int64(nonce))
// auth.Value = big.NewInt(0)
// auth.GasLimit = uint64(300000)
// auth.GasPrice = gasPrice
return auth, nil
}

Wyświetl plik

@ -0,0 +1,73 @@
package main
import (
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
terminus_contract "github.com/bugout-dev/engine/robots/pkg/terminus"
)
type ContractTerminusInstance struct {
Address common.Address
Instance *terminus_contract.Terminus
TerminusPoolId int64
}
func GetTerminusContractAddress(terminusAddress string) common.Address {
return common.HexToAddress(terminusAddress)
}
// InitializeContractInstance parse contract to instance
func InitializeTerminusContractInstance(client *ethclient.Client, address common.Address) (*terminus_contract.Terminus, error) {
contractInstance, err := terminus_contract.NewTerminus(address, client)
if err != nil {
return nil, err
}
return contractInstance, nil
}
func (ct *ContractTerminusInstance) FetchPoolCapacity(pool_id int64) (*big.Int, error) {
pool_capacity, err := ct.Instance.TerminusPoolCapacity(nil, big.NewInt(pool_id))
if err != nil {
return nil, err
}
return pool_capacity, nil
}
// PoolMintBatch executes PoolMintBatch for list of address with same value amount
func (cti *ContractTerminusInstance) PoolMintBatch(auth *bind.TransactOpts, claimants []Claimant, value int64) (*types.Transaction, error) {
to_addresses := []common.Address{}
values := []*big.Int{}
for _, claimant := range claimants {
to_addresses = append(to_addresses, common.HexToAddress(claimant.Address))
values = append(values, big.NewInt(value))
}
tx, err := cti.Instance.PoolMintBatch(auth, big.NewInt(cti.TerminusPoolId), to_addresses, values)
if err != nil {
return nil, err
}
return tx, nil
}
func (cti *ContractTerminusInstance) BalanceOfBatch(auth *bind.CallOpts, claimants []Claimant, id_int int64) ([]*big.Int, error) {
addresses := []common.Address{}
ids := []*big.Int{}
for _, claimant := range claimants {
addresses = append(addresses, common.HexToAddress(claimant.Address))
ids = append(ids, big.NewInt(id_int))
}
balances, err := cti.Instance.BalanceOfBatch(auth, addresses, ids)
if err != nil {
return nil, err
}
return balances, nil
}

Wyświetl plik

@ -0,0 +1,3 @@
package main
var ROBOTS_VERSION = "0.0.2"

Wyświetl plik

@ -0,0 +1,57 @@
#!/usr/bin/env bash
# Deployment script - intended to run on Robots server
# Colors
C_RESET='\033[0m'
C_RED='\033[1;31m'
C_GREEN='\033[1;32m'
C_YELLOW='\033[1;33m'
# Logs
PREFIX_INFO="${C_GREEN}[INFO]${C_RESET} [$(date +%d-%m\ %T)]"
PREFIX_WARN="${C_YELLOW}[WARN]${C_RESET} [$(date +%d-%m\ %T)]"
PREFIX_CRIT="${C_RED}[CRIT]${C_RESET} [$(date +%d-%m\ %T)]"
# Main
APP_DIR="${APP_DIR:-/home/ubuntu/engine/robots}"
AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION:-us-east-1}"
SCRIPT_DIR="$(realpath $(dirname $0))"
SECRETS_DIR="${SECRETS_DIR:-/home/ubuntu/robots-secrets}"
PARAMETERS_ENV_PATH="${SECRETS_DIR}/app.env"
# Airdrop service
ROBOTS_AIRDROP_SERVICE_FILE="robots-airdrop.service"
set -eu
if [ ! -d "$SECRETS_DIR" ]; then
echo -e "${PREFIX_WARN} Created new directory for environment variables"
mkdir "$SECRETS_DIR"
fi
echo
echo
echo -e "${PREFIX_INFO} Install checkenv"
HOME=/home/ubuntu /usr/local/go/bin/go install github.com/bugout-dev/checkenv@latest
echo
echo
echo -e "${PREFIX_INFO} Retrieving addition deployment parameters"
AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION}" /home/ubuntu/go/bin/checkenv show aws_ssm+robots:true > "${PARAMETERS_ENV_PATH}"
echo
echo
echo -e "${PREFIX_INFO} Building executable robots script with Go"
EXEC_DIR=$(pwd)
cd "${APP_DIR}"
HOME=/home/ubuntu /usr/local/go/bin/go build -o "${APP_DIR}/robots" "${APP_DIR}/cmd/robots/"
cd "${EXEC_DIR}"
echo
echo
echo -e "${PREFIX_INFO} Replacing existing Airdrop robots service definition with ${ROBOTS_AIRDROP_SERVICE_FILE}"
chmod 644 "${SCRIPT_DIR}/${ROBOTS_AIRDROP_SERVICE_FILE}"
cp "${SCRIPT_DIR}/${ROBOTS_AIRDROP_SERVICE_FILE}" "/home/ubuntu/.config/systemd/user/${ROBOTS_AIRDROP_SERVICE_FILE}"
XDG_RUNTIME_DIR="/run/user/1000" systemctl --user daemon-reload
XDG_RUNTIME_DIR="/run/user/1000" systemctl --user restart "${ROBOTS_AIRDROP_SERVICE_FILE}"

Wyświetl plik

@ -0,0 +1,16 @@
[Unit]
Description=Airdrop Engine robots
After=network.target
StartLimitIntervalSec=300
StartLimitBurst=3
[Service]
WorkingDirectory=/home/ubuntu/engine/robots
EnvironmentFile=/home/ubuntu/robots-secrets/app.env
Restart=on-failure
RestartSec=15s
ExecStart=/home/ubuntu/engine/robots/robots airdrop
SyslogIdentifier=robots-airdrop
[Install]
WantedBy=multi-user.target

10
robots/dev.sh 100755
Wyświetl plik

@ -0,0 +1,10 @@
#!/usr/bin/env sh
# Compile application and run with provided arguments
set -e
PROGRAM_NAME="robots_dev"
go build -o "$PROGRAM_NAME" cmd/robots/*.go
./"$PROGRAM_NAME" "$@"

24
robots/go.mod 100644
Wyświetl plik

@ -0,0 +1,24 @@
module github.com/bugout-dev/engine/robots
go 1.19
require github.com/ethereum/go-ethereum v1.10.26
require (
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
github.com/bugout-dev/humbug/go v0.0.0-20230221171050-e0a1715ec546 // indirect
github.com/deckarep/golang-set v1.8.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/go-ole/go-ole v1.2.1 // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/google/uuid v1.2.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/rjeczalik/notify v0.9.1 // indirect
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
github.com/tklauser/go-sysconf v0.3.5 // indirect
github.com/tklauser/numcpus v0.2.2 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
)

75
robots/go.sum 100644
Wyświetl plik

@ -0,0 +1,75 @@
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6 h1:fLjPD/aNc3UIOA6tDi6QXUemppXK3P9BI7mr2hd6gx8=
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/VictoriaMetrics/fastcache v1.6.0 h1:C/3Oi3EiBCqufydp1neRZkqcwmEiuRT9c3fqvvgKm5o=
github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k=
github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
github.com/bugout-dev/humbug/go v0.0.0-20230221171050-e0a1715ec546 h1:qRPzjQAQwNxqfW+U+XkfBLQbCdXCp+sRxwVE0ydtqFM=
github.com/bugout-dev/humbug/go v0.0.0-20230221171050-e0a1715ec546/go.mod h1:U/NXHfc3tzGeQz+xVfpifXdPZi7p6VV8xdP/4ZKeWJU=
github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4=
github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo=
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s=
github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI=
github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE=
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs=
github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM=
github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ=
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA=
github.com/rjeczalik/notify v0.9.1 h1:CLCKso/QK1snAlnhNR/CNvNiFU2saUtjV0bx3EwNeCE=
github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4 h1:Gb2Tyox57NRNuZ2d3rmvB3pcmbu7O1RS3m8WRx7ilrg=
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4=
github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA=
github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4=
github.com/urfave/cli/v2 v2.10.2 h1:x3p8awjp/2arX+Nl/G2040AZpOCHS/eMJJ1/a+mye4Y=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiTs/Igowgfkj25AcZrtiE=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU=
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

File diff suppressed because one or more lines are too long

11
robots/sample.env 100644
Wyświetl plik

@ -0,0 +1,11 @@
export MOONSTREAM_ENTITY_URL="https://api.moonstream.to/entity"
export MOONSTREAM_POLYGON_WEB3_PROVIDER_URI="<JSON_RPC_API_URL>"
export MOONSTREAM_MUMBAI_WEB3_PROVIDER_URI="<JSON_RPC_API_URL>"
export MOONSTREAM_WYRM_WEB3_PROVIDER_URI="<JSON_RPC_API_URL>"
export ENGINE_NODEBALANCER_ACCESS_ID="<access_id_for_Moonstream_Node_Balancer-if_provided_it_is_interpolated_into_provider_URIs>"
export ENGINE_ROBOTS_SECRETS_DIR="<path_to_directory_with_keyfile>"
export HUMBUG_REPORTER_ROBOTS_HEARTBEAT_TOKEN="<Humbug_token_for_robots_heartbeat>"
export MOONSTREAM_TERMINUS_DIAMOND_CONTRACT_POLYGON_ADDRESS="<blockchain_smartcontract_address>"
export MOONSTREAM_TERMINUS_DIAMOND_CONTRACT_MUMBAI_ADDRESS="<blockchain_smartcontract_address>"
export MOONSTREAM_TERMINUS_DIAMOND_CONTRACT_WYRM_ADDRESS="<blockchain_smartcontract_address>"