Skip to content
This repository was archived by the owner on Feb 8, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions cmd/copytrader/server/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package main

import (
"fmt"
"os"
"strings"

"github.com/spf13/viper"
"github.com/vultisig/verifier/vault_config"

"github.com/vultisig/plugin/api"
"github.com/vultisig/plugin/storage"
)

type CopytraderServerConfig struct {
Server api.ServerConfig `mapstructure:"server" json:"server"`
Database struct {
DSN string `mapstructure:"dsn" json:"dsn,omitempty"`
} `mapstructure:"database" json:"database,omitempty"`
BaseConfigPath string `mapstructure:"base_config_path" json:"base_config_path,omitempty"`
Redis storage.RedisConfig `mapstructure:"redis" json:"redis,omitempty"`
BlockStorage vault_config.BlockStorage `mapstructure:"block_storage" json:"block_storage,omitempty"`
Datadog struct {
Host string `mapstructure:"host" json:"host,omitempty"`
Port string `mapstructure:"port" json:"port,omitempty"`
} `mapstructure:"datadog" json:"datadog"`
}
Comment on lines +15 to +27

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Extract inline structs and consider sensitive data handling

The inline struct definitions for Database and Datadog reduce code reusability and maintainability. Also, the Database DSN may contain sensitive credentials.

Consider extracting these to separate types:

+type DatabaseConfig struct {
+	DSN string `mapstructure:"dsn" json:"dsn,omitempty"`
+}
+
+type DatadogConfig struct {
+	Host string `mapstructure:"host" json:"host,omitempty"`
+	Port string `mapstructure:"port" json:"port,omitempty"`
+}
+
 type CopytraderServerConfig struct {
 	Server   api.ServerConfig `mapstructure:"server" json:"server"`
-	Database struct {
-		DSN string `mapstructure:"dsn" json:"dsn,omitempty"`
-	} `mapstructure:"database" json:"database,omitempty"`
+	Database DatabaseConfig `mapstructure:"database" json:"database,omitempty"`
 	BaseConfigPath string                    `mapstructure:"base_config_path" json:"base_config_path,omitempty"`
 	Redis          storage.RedisConfig       `mapstructure:"redis" json:"redis,omitempty"`
 	BlockStorage   vault_config.BlockStorage `mapstructure:"block_storage" json:"block_storage,omitempty"`
-	Datadog        struct {
-		Host string `mapstructure:"host" json:"host,omitempty"`
-		Port string `mapstructure:"port" json:"port,omitempty"`
-	} `mapstructure:"datadog" json:"datadog"`
+	Datadog        DatadogConfig `mapstructure:"datadog" json:"datadog"`
 }

Also, ensure that sensitive configuration values like database DSN are properly secured (e.g., using environment variables or secret management systems).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type CopytraderServerConfig struct {
Server api.ServerConfig `mapstructure:"server" json:"server"`
Database struct {
DSN string `mapstructure:"dsn" json:"dsn,omitempty"`
} `mapstructure:"database" json:"database,omitempty"`
BaseConfigPath string `mapstructure:"base_config_path" json:"base_config_path,omitempty"`
Redis storage.RedisConfig `mapstructure:"redis" json:"redis,omitempty"`
BlockStorage vault_config.BlockStorage `mapstructure:"block_storage" json:"block_storage,omitempty"`
Datadog struct {
Host string `mapstructure:"host" json:"host,omitempty"`
Port string `mapstructure:"port" json:"port,omitempty"`
} `mapstructure:"datadog" json:"datadog"`
}
// extract these above your server config
type DatabaseConfig struct {
DSN string `mapstructure:"dsn" json:"dsn,omitempty"`
}
type DatadogConfig struct {
Host string `mapstructure:"host" json:"host,omitempty"`
Port string `mapstructure:"port" json:"port,omitempty"`
}
type CopytraderServerConfig struct {
Server api.ServerConfig `mapstructure:"server" json:"server"`
Database DatabaseConfig `mapstructure:"database" json:"database,omitempty"`
BaseConfigPath string `mapstructure:"base_config_path" json:"base_config_path,omitempty"`
Redis storage.RedisConfig `mapstructure:"redis" json:"redis,omitempty"`
BlockStorage vault_config.BlockStorage `mapstructure:"block_storage" json:"block_storage,omitempty"`
Datadog DatadogConfig `mapstructure:"datadog" json:"datadog"`
}
🤖 Prompt for AI Agents
In cmd/copytrader/server/config.go around lines 15 to 27, the inline struct
definitions for Database and Datadog reduce code reusability and
maintainability, and the Database DSN may contain sensitive credentials. Extract
the Database and Datadog inline structs into separate named types to improve
clarity and reuse. Additionally, modify the handling of the Database DSN to
avoid hardcoding sensitive data by loading it securely from environment
variables or a secret management system instead of embedding it directly in the
config struct.


func GetConfigure() (*CopytraderServerConfig, error) {
configName := os.Getenv("VS_CONFIG_NAME")
if configName == "" {
configName = "config"
}

return ReadConfig(configName)
}

func ReadConfig(configName string) (*CopytraderServerConfig, error) {
viper.SetConfigName(configName)
viper.AddConfigPath(".")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()

viper.SetDefault("Server.VaultsFilePath", "vaults")

if err := viper.ReadInConfig(); err != nil {
return nil, fmt.Errorf("failed to read config file, %w", err)
}
var cfg CopytraderServerConfig
err := viper.Unmarshal(&cfg)
if err != nil {
return nil, fmt.Errorf("unable to decode into struct, %w", err)
}
return &cfg, nil
}
Comment on lines +38 to +55

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add configuration validation

The configuration loading doesn't validate required fields, which could lead to runtime errors.

Add validation after unmarshaling:

 	var cfg CopytraderServerConfig
 	err := viper.Unmarshal(&cfg)
 	if err != nil {
 		return nil, fmt.Errorf("unable to decode into struct, %w", err)
 	}
+	
+	// Validate required fields
+	if cfg.Database.DSN == "" {
+		return nil, fmt.Errorf("database DSN is required")
+	}
+	if cfg.Redis.Host == "" || cfg.Redis.Port == "" {
+		return nil, fmt.Errorf("redis host and port are required")
+	}
+	
 	return &cfg, nil
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func ReadConfig(configName string) (*CopytraderServerConfig, error) {
viper.SetConfigName(configName)
viper.AddConfigPath(".")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
viper.SetDefault("Server.VaultsFilePath", "vaults")
if err := viper.ReadInConfig(); err != nil {
return nil, fmt.Errorf("failed to read config file, %w", err)
}
var cfg CopytraderServerConfig
err := viper.Unmarshal(&cfg)
if err != nil {
return nil, fmt.Errorf("unable to decode into struct, %w", err)
}
return &cfg, nil
}
func ReadConfig(configName string) (*CopytraderServerConfig, error) {
viper.SetConfigName(configName)
viper.AddConfigPath(".")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
viper.SetDefault("Server.VaultsFilePath", "vaults")
if err := viper.ReadInConfig(); err != nil {
return nil, fmt.Errorf("failed to read config file, %w", err)
}
var cfg CopytraderServerConfig
err := viper.Unmarshal(&cfg)
if err != nil {
return nil, fmt.Errorf("unable to decode into struct, %w", err)
}
// Validate required fields
if cfg.Database.DSN == "" {
return nil, fmt.Errorf("database DSN is required")
}
if cfg.Redis.Host == "" || cfg.Redis.Port == "" {
return nil, fmt.Errorf("redis host and port are required")
}
return &cfg, nil
}
🤖 Prompt for AI Agents
In cmd/copytrader/server/config.go around lines 38 to 55, after unmarshaling the
config into the struct, add validation logic to check that all required fields
are set and valid. Implement checks for mandatory fields in the
CopytraderServerConfig struct and return an error if any required field is
missing or invalid before returning the config. This ensures the configuration
is validated early to prevent runtime errors.

102 changes: 102 additions & 0 deletions cmd/copytrader/server/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package main

import (
"context"
"fmt"
"net"

"github.com/DataDog/datadog-go/statsd"
"github.com/hibiken/asynq"
"github.com/sirupsen/logrus"
"github.com/vultisig/plugin/plugin/copytrader"
"github.com/vultisig/verifier/tx_indexer"
tx_indexer_storage "github.com/vultisig/verifier/tx_indexer/pkg/storage"
"github.com/vultisig/verifier/vault"

"github.com/vultisig/plugin/api"
"github.com/vultisig/plugin/storage"
"github.com/vultisig/plugin/storage/postgres"
)

func main() {
ctx := context.Background()

cfg, err := GetConfigure()
if err != nil {
panic(err)
}
logger := logrus.New()

sdClient, err := statsd.New(net.JoinHostPort(cfg.Datadog.Host, cfg.Datadog.Port))
if err != nil {
panic(err)
}
redisStorage, err := storage.NewRedisStorage(cfg.Redis)
if err != nil {
panic(err)
}
redisOptions := asynq.RedisClientOpt{
Addr: net.JoinHostPort(cfg.Redis.Host, cfg.Redis.Port),
Username: cfg.Redis.User,
Password: cfg.Redis.Password,
DB: cfg.Redis.DB,
}

client := asynq.NewClient(redisOptions)
defer func() {
if err := client.Close(); err != nil {
fmt.Println("fail to close asynq client,", err)
}
}()

inspector := asynq.NewInspector(redisOptions)

vaultStorage, err := vault.NewBlockStorageImp(cfg.BlockStorage)
if err != nil {
panic(err)
}

db, err := postgres.NewPostgresBackend(cfg.Database.DSN, nil)
if err != nil {
logger.Fatalf("Failed to connect to database: %v", err)
}

txIndexerStore, err := tx_indexer_storage.NewPostgresTxIndexStore(ctx, cfg.Database.DSN)
if err != nil {
panic(fmt.Errorf("tx_indexer_storage.NewPostgresTxIndexStore: %w", err))
}

txIndexerService := tx_indexer.NewService(
logger,
txIndexerStore,
tx_indexer.Chains(),
)

ct, err := copytrader.NewPlugin(
db,
nil, // not used by server
vaultStorage,
nil,
txIndexerService,
client,
cfg.Server.EncryptionSecret,
)
if err != nil {
logger.Fatalf("failed to create payroll plugin,err: %s", err)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect error message referencing wrong plugin.

The error message mentions "payroll plugin" instead of "copytrader plugin".

-		logger.Fatalf("failed to create payroll plugin,err: %s", err)
+		logger.Fatalf("failed to create copytrader plugin, err: %s", err)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
logger.Fatalf("failed to create payroll plugin,err: %s", err)
logger.Fatalf("failed to create copytrader plugin, err: %s", err)
🤖 Prompt for AI Agents
In cmd/copytrader/server/main.go at line 85, the error message incorrectly
references "payroll plugin" instead of "copytrader plugin". Update the log
message to correctly state "failed to create copytrader plugin" to accurately
reflect the context of the error.

}

server := api.NewServer(
cfg.Server,
db,
redisStorage,
vaultStorage,
redisOptions,
client,
inspector,
sdClient,
ct,
)
if err := server.StartServer(); err != nil {
panic(err)
}
}
Comment on lines +21 to +102

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding graceful shutdown handling.

The main function lacks graceful shutdown capabilities. Consider implementing signal handling to properly close connections and clean up resources on shutdown.

+import (
+	"os"
+	"os/signal"
+	"syscall"
+)

func main() {
	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	// Setup signal handling
+	sigChan := make(chan os.Signal, 1)
+	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
+	go func() {
+		<-sigChan
+		logger.Info("Shutdown signal received")
+		cancel()
+	}()

	// ... existing initialization code ...

-	if err := server.StartServer(); err != nil {
-		panic(err)
+	go func() {
+		if err := server.StartServer(); err != nil {
+			logger.Fatalf("Server failed to start: %v", err)
+		}
	}()
+
+	<-ctx.Done()
+	logger.Info("Shutting down server...")
}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In cmd/copytrader/server/main.go around lines 21 to 102, the main function
currently does not handle graceful shutdown, which can lead to improper resource
cleanup. Add signal handling for termination signals (e.g., SIGINT, SIGTERM)
using a channel and context cancellation. Modify the server start logic to
listen for these signals and on receiving them, properly close all clients,
connections, and other resources before exiting the program.

66 changes: 66 additions & 0 deletions cmd/copytrader/worker/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package main

import (
"fmt"
"os"
"strings"

"github.com/spf13/viper"
"github.com/vultisig/verifier/vault_config"

"github.com/vultisig/plugin/storage"
)

type CopytraderWorkerConfig struct {
Redis storage.RedisConfig `mapstructure:"redis" json:"redis,omitempty"`
Rpc Rpc `mapstructure:"Rpc" json:"Rpc,omitempty"`
Verifier verifier `mapstructure:"verifier" json:"verifier,omitempty"`
BlockStorage vault_config.BlockStorage `mapstructure:"block_storage" json:"block_storage,omitempty"`
VaultServiceConfig vault_config.Config `mapstructure:"vault_service" json:"vault_service,omitempty"`
Datadog struct {
Host string `mapstructure:"host" json:"host,omitempty"`
Port string `mapstructure:"port" json:"port,omitempty"`
} `mapstructure:"datadog" json:"datadog"`
Database struct {
DSN string `mapstructure:"dsn" json:"dsn,omitempty"`
} `mapstructure:"database" json:"database,omitempty"`
}

type Rpc struct {
Ethereum rpcItem `mapstructure:"ethereum" json:"ethereum,omitempty"`
}

type rpcItem struct {
URL string `mapstructure:"url" json:"url,omitempty"`
}

type verifier struct {
URL string `mapstructure:"url"`
Token string `mapstructure:"token"`
PartyPrefix string `mapstructure:"party_prefix"`
}

func GetConfigure() (*CopytraderWorkerConfig, error) {
configName := os.Getenv("VS_CONFIG_NAME")
if configName == "" {
configName = "config"
}
return ReadConfig(configName)
}

func ReadConfig(configName string) (*CopytraderWorkerConfig, error) {
viper.SetConfigName(configName)
viper.AddConfigPath(".")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()

if err := viper.ReadInConfig(); err != nil {
return nil, fmt.Errorf("fail to reading config file, %w", err)
}
var cfg CopytraderWorkerConfig
err := viper.Unmarshal(&cfg)
if err != nil {
return nil, fmt.Errorf("unable to decode into struct, %w", err)
}
return &cfg, nil
}
124 changes: 124 additions & 0 deletions cmd/copytrader/worker/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package main

import (
"context"
"fmt"

"github.com/DataDog/datadog-go/statsd"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/hibiken/asynq"
"github.com/sirupsen/logrus"
"github.com/vultisig/plugin/internal/keysign"
"github.com/vultisig/plugin/plugin/copytrader"
"github.com/vultisig/plugin/storage/postgres"
"github.com/vultisig/verifier/tx_indexer"
"github.com/vultisig/verifier/tx_indexer/pkg/storage"
"github.com/vultisig/verifier/vault"
"github.com/vultisig/vultiserver/relay"

"github.com/vultisig/plugin/internal/tasks"
)

// Don't scale payroll.worker, it has scheduler which must be single instance running
// Consider moving scheduler to separate worker
Comment on lines +22 to +23

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect comment referencing payroll.worker

The comment references "payroll.worker" but this is the copytrader worker.

-// Don't scale payroll.worker, it has scheduler which must be single instance running
-// Consider moving scheduler to separate worker
+// Don't scale copytrader.worker if it has any singleton components
+// Consider architectural patterns for horizontal scaling if needed
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Don't scale payroll.worker, it has scheduler which must be single instance running
// Consider moving scheduler to separate worker
// Don't scale copytrader.worker if it has any singleton components
// Consider architectural patterns for horizontal scaling if needed
🤖 Prompt for AI Agents
In cmd/copytrader/worker/main.go at lines 22 to 23, the comment incorrectly
references "payroll.worker" instead of the correct "copytrader worker." Update
the comment to replace "payroll.worker" with "copytrader worker" to accurately
reflect the context and avoid confusion.

func main() {
ctx := context.Background()

cfg, err := GetConfigure()
if err != nil {
panic(err)
}

sdClient, err := statsd.New(cfg.Datadog.Host + ":" + cfg.Datadog.Port)
if err != nil {
panic(err)
}
vaultStorage, err := vault.NewBlockStorageImp(cfg.BlockStorage)
if err != nil {
panic(fmt.Sprintf("failed to initialize vault storage: %v", err))
}

redisOptions := asynq.RedisClientOpt{
Addr: cfg.Redis.Host + ":" + cfg.Redis.Port,
Username: cfg.Redis.User,
Password: cfg.Redis.Password,
DB: cfg.Redis.DB,
}

logger := logrus.StandardLogger()
client := asynq.NewClient(redisOptions)
srv := asynq.NewServer(
redisOptions,
asynq.Config{
Logger: logger,
Concurrency: 10,
Queues: map[string]int{
tasks.QUEUE_NAME: 10,
},
},
)

txIndexerStore, err := storage.NewPostgresTxIndexStore(ctx, cfg.Database.DSN)
if err != nil {
panic(fmt.Errorf("storage.NewPostgresTxIndexStore: %w", err))
}

txIndexerService := tx_indexer.NewService(
logger,
txIndexerStore,
tx_indexer.Chains(),
)

vaultService, err := vault.NewManagementService(
cfg.VaultServiceConfig,
client,
sdClient,
vaultStorage,
txIndexerService,
)
if err != nil {
panic(fmt.Errorf("failed to create vault service: %w", err))
}

postgressDB, err := postgres.NewPostgresBackend(cfg.Database.DSN, nil)
if err != nil {
panic(fmt.Errorf("failed to create postgres backend: %w", err))
}

rpcClient, err := ethclient.Dial(cfg.Rpc.Ethereum.URL)
if err != nil {
panic(fmt.Errorf("failed to create eth client: %w", err))
}

ct, err := copytrader.NewPlugin(
postgressDB,
keysign.NewSigner(
logger.WithField("pkg", "keysign.Signer").Logger,
relay.NewRelayClient(cfg.VaultServiceConfig.Relay.Server),
[]keysign.Emitter{
keysign.NewVerifierEmitter(cfg.Verifier.URL, cfg.Verifier.Token),
keysign.NewPluginEmitter(client, tasks.TypeKeySignDKLS, tasks.QUEUE_NAME),
},
[]string{
cfg.Verifier.PartyPrefix,
cfg.VaultServiceConfig.LocalPartyPrefix,
},
),
vaultStorage,
rpcClient,
txIndexerService,
client,
cfg.VaultServiceConfig.EncryptionSecret,
)
if err != nil {
panic(fmt.Errorf("failed to create copytrader plugin: %w", err))
}

mux := asynq.NewServeMux()
mux.HandleFunc(tasks.TypePluginTransaction, ct.HandleSwapTask)
mux.HandleFunc(tasks.TypeKeySignDKLS, vaultService.HandleKeySignDKLS)
mux.HandleFunc(tasks.TypeReshareDKLS, vaultService.HandleReshareDKLS)
if err := srv.Run(mux); err != nil {
panic(fmt.Errorf("could not run server: %w", err))
}
}
Comment on lines +117 to +124

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add graceful shutdown handling

The worker doesn't handle OS signals for graceful shutdown, which could lead to data loss or incomplete transactions.

Replace the current server run with signal handling:

+	// Handle graceful shutdown
+	sigChan := make(chan os.Signal, 1)
+	signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
+	
+	go func() {
+		if err := srv.Run(mux); err != nil {
+			logger.Fatalf("could not run server: %v", err)
+		}
+	}()
+	
+	<-sigChan
+	logger.Info("Shutting down worker...")
+	
+	srv.Shutdown()
+	client.Close()
+	
-	if err := srv.Run(mux); err != nil {
-		panic(fmt.Errorf("could not run server: %w", err))
-	}

Don't forget to import os, os/signal, and syscall.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
mux := asynq.NewServeMux()
mux.HandleFunc(tasks.TypePluginTransaction, ct.HandleSwapTask)
mux.HandleFunc(tasks.TypeKeySignDKLS, vaultService.HandleKeySignDKLS)
mux.HandleFunc(tasks.TypeReshareDKLS, vaultService.HandleReshareDKLS)
if err := srv.Run(mux); err != nil {
panic(fmt.Errorf("could not run server: %w", err))
}
}
mux := asynq.NewServeMux()
mux.HandleFunc(tasks.TypePluginTransaction, ct.HandleSwapTask)
mux.HandleFunc(tasks.TypeKeySignDKLS, vaultService.HandleKeySignDKLS)
mux.HandleFunc(tasks.TypeReshareDKLS, vaultService.HandleReshareDKLS)
// Handle graceful shutdown
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
go func() {
if err := srv.Run(mux); err != nil {
logger.Fatalf("could not run server: %v", err)
}
}()
<-sigChan
logger.Info("Shutting down worker...")
srv.Shutdown()
client.Close()
}
🤖 Prompt for AI Agents
In cmd/copytrader/worker/main.go around lines 117 to 124, the server run logic
lacks handling of OS signals for graceful shutdown, risking data loss or
incomplete transactions. Modify the code to listen for termination signals
(e.g., SIGINT, SIGTERM) using the os/signal and syscall packages, and upon
receiving such a signal, gracefully stop the server instead of panicking. Import
the necessary packages os, os/signal, and syscall, set up a signal channel, run
the server in a goroutine, and block until a shutdown signal is received, then
cleanly shut down the server.

1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -202,4 +202,5 @@ replace (
github.com/cwespare/xxhash/v2 => github.com/cespare/xxhash/v2 v2.1.1
github.com/gogo/protobuf => github.com/gogo/protobuf v1.3.2
nhooyr.io/websocket => github.com/coder/websocket v1.8.6
github.com/vultisig/verifier => ../verifier
)
6 changes: 6 additions & 0 deletions plugin/copytrader/const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package copytrader

const (
UniswapV2RouterAddress = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"
SwapExactTokensForTokens = "38ed1739"
)
Comment on lines +3 to +6

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add network specificity and improve constant formatting

The constants are hardcoded for Ethereum mainnet but lack network context. Additionally, the method ID should include the "0x" prefix for consistency with Ethereum conventions.

Consider these improvements:

 const (
-	UniswapV2RouterAddress   = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"
-	SwapExactTokensForTokens = "38ed1739"
+	// UniswapV2RouterAddress is the mainnet address for Uniswap V2 Router
+	UniswapV2RouterAddress   = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"
+	// SwapExactTokensForTokens is the function selector for swapExactTokensForTokens(uint256,uint256,address[],address,uint256)
+	SwapExactTokensForTokens = "0x38ed1739"
 )

Additionally, consider creating a configuration or constants file that maps router addresses per network (mainnet, testnet, etc.) to avoid hardcoding network-specific values.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const (
UniswapV2RouterAddress = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"
SwapExactTokensForTokens = "38ed1739"
)
const (
// UniswapV2RouterAddress is the mainnet address for Uniswap V2 Router
UniswapV2RouterAddress = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"
// SwapExactTokensForTokens is the function selector for swapExactTokensForTokens(uint256,uint256,address[],address,uint256)
SwapExactTokensForTokens = "0x38ed1739"
)
🤖 Prompt for AI Agents
In plugin/copytrader/const.go around lines 3 to 6, the constants are hardcoded
for Ethereum mainnet without network context, and the method ID lacks the "0x"
prefix. To fix this, add the "0x" prefix to the SwapExactTokensForTokens
constant for Ethereum convention consistency, and refactor the router address
into a network-specific configuration or constants map that supports multiple
networks (mainnet, testnet, etc.) instead of hardcoding a single address.

Loading
Loading