From 9b6aa9e4f9d9afd40c24427f6988ac93d5d908ff Mon Sep 17 00:00:00 2001 From: webpiratt <213060745+webpiratt@users.noreply.github.com> Date: Fri, 15 Aug 2025 19:45:04 +0300 Subject: [PATCH 1/2] delete dca --- .github/workflows/migration-test.yml | 40 +- .gitignore | 1 - api/server.go | 2 +- cmd/dca/server/config.go | 53 -- cmd/dca/server/main.go | 93 --- cmd/dca/worker/config.go | 49 -- cmd/dca/worker/main.go | 80 --- create_buckets.sh | 2 +- etc/vultisig/dca.yml | 8 - init-scripts/01_create_vultisig_plugin.sql | 3 +- plugin/dca/config.go | 59 -- plugin/dca/dca.go | 776 --------------------- plugin/dca/dcaPluginUiSchema.json | 287 -------- plugin/dca/policy.go | 25 - scripts/dev/create_dca_policy/main.go | 117 ---- storage/postgres/schema/schema.sql | 1 - 16 files changed, 21 insertions(+), 1575 deletions(-) delete mode 100644 cmd/dca/server/config.go delete mode 100644 cmd/dca/server/main.go delete mode 100644 cmd/dca/worker/config.go delete mode 100644 cmd/dca/worker/main.go delete mode 100644 etc/vultisig/dca.yml delete mode 100644 plugin/dca/config.go delete mode 100644 plugin/dca/dca.go delete mode 100644 plugin/dca/dcaPluginUiSchema.json delete mode 100644 plugin/dca/policy.go delete mode 100644 scripts/dev/create_dca_policy/main.go diff --git a/.github/workflows/migration-test.yml b/.github/workflows/migration-test.yml index e12b4c3c..83ecc981 100644 --- a/.github/workflows/migration-test.yml +++ b/.github/workflows/migration-test.yml @@ -47,7 +47,7 @@ jobs: - name: Create test config run: | - cat > dca.json < payroll.json </dev/null; then - echo "DCA process failed to start" + if ! kill -0 $PAYROLL_PID 2>/dev/null; then + echo "Payroll process failed to start" exit 1 fi # Check if migrations ran successfully by verifying tables exist - PGPASSWORD=mypassword psql -h localhost -U myuser -d vultisig-plugin -c "\dt" | grep -E "(plugin_policies|time_triggers) | wc -l" > /dev/null + PGPASSWORD=mypassword psql -h localhost -U myuser -d vultisig-plugin -c "\dt" | grep -E "(plugin_policies|scheduler)" | wc -l > /dev/null if [ $? -ne 0 ]; then echo "Migrations did not run successfully - expected tables not found" - kill $DCA_PID + kill $PAYROLL_PID exit 1 fi echo "All migrations ran successfully!" - # Stop dca - kill $DCA_PID + # Stop payroll + kill $PAYROLL_PID - name: Check migration integrity run: | @@ -143,7 +139,7 @@ jobs: -e '/^--.*/d' \ -e '/^SET /d' \ -e '/^SELECT pg_catalog./d' \ - -e 's/"public"\.//' | awk '/./ { e=0 } /^$$/ { e += 1 } e <= 1' \ + -e 's/"public"\.//' | awk '/./ { e=0 } /^$/ { e += 1 } e <= 1' \ > ./current_schema.sql # Compare with repository schema @@ -153,4 +149,4 @@ jobs: exit 1 fi - echo "Schema is up to date!" + echo "Schema is up to date!" \ No newline at end of file diff --git a/.gitignore b/.gitignore index b2ce360b..a1cea0cd 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,6 @@ localconfig* worker/worker vultisigner cmd/payroll/server/server -cmd/dca/server/server tmp/ test/vaults/* diff --git a/api/server.go b/api/server.go index 5f2616a2..0d06de37 100644 --- a/api/server.go +++ b/api/server.go @@ -111,7 +111,7 @@ func (s *Server) StartServer() error { } func (s *Server) Ping(c echo.Context) error { - return c.String(http.StatusOK, "Payroll & DCA Plugin server is running") + return c.String(http.StatusOK, "server is running") } // ReshareVault is a handler to reshare a vault diff --git a/cmd/dca/server/config.go b/cmd/dca/server/config.go deleted file mode 100644 index a0ca4b8f..00000000 --- a/cmd/dca/server/config.go +++ /dev/null @@ -1,53 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/spf13/viper" - "github.com/vultisig/verifier/vault_config" - - "github.com/vultisig/plugin/api" - "github.com/vultisig/plugin/storage" -) - -type DCAServerConfig 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"` -} - -func GetConfigure() (*DCAServerConfig, error) { - configName := os.Getenv("VS_CONFIG_NAME") - if configName == "" { - configName = "config" - } - - return ReadConfig(configName) -} - -func ReadConfig(configName string) (*DCAServerConfig, error) { - viper.SetConfigName(configName) - viper.AddConfigPath(".") - viper.AutomaticEnv() - - viper.SetDefault("Server.VaultsFilePath", "vaults") - - if err := viper.ReadInConfig(); err != nil { - return nil, fmt.Errorf("fail to reading config file, %w", err) - } - var cfg DCAServerConfig - err := viper.Unmarshal(&cfg) - if err != nil { - return nil, fmt.Errorf("unable to decode into struct, %w", err) - } - return &cfg, nil -} diff --git a/cmd/dca/server/main.go b/cmd/dca/server/main.go deleted file mode 100644 index e4274851..00000000 --- a/cmd/dca/server/main.go +++ /dev/null @@ -1,93 +0,0 @@ -package main - -import ( - "context" - "fmt" - "net" - - "github.com/DataDog/datadog-go/statsd" - "github.com/hibiken/asynq" - "github.com/sirupsen/logrus" - "github.com/vultisig/plugin/internal/scheduler" - "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/plugin/dca" - "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) - } - - txIndexerStore, err := tx_indexer_storage.NewPostgresTxIndexStore(ctx, cfg.Database.DSN) - if err != nil { - panic(fmt.Errorf("tx_indexer_storage.NewPostgresTxIndexStore: %w", err)) - } - - // TODO: @webpiratt: add to dca.NewDCAPlugin constructor - _ = tx_indexer.NewService( - logger, - txIndexerStore, - tx_indexer.Chains(), - ) - - db, err := postgres.NewPostgresBackend(cfg.Database.DSN, nil) - if err != nil { - logger.Fatalf("Failed to connect to database: %v", err) - } - p, err := dca.NewDCAPlugin(db, logger, cfg.BaseConfigPath) - if err != nil { - logger.Fatalf("failed to create DCA plugin,err: %s", err) - } - server := api.NewServer( - cfg.Server, - db, - redisStorage, - vaultStorage, - client, - inspector, - sdClient, - p, - scheduler.NewNilService(), - ) - if err := server.StartServer(); err != nil { - panic(err) - } -} diff --git a/cmd/dca/worker/config.go b/cmd/dca/worker/config.go deleted file mode 100644 index 86b2f9d7..00000000 --- a/cmd/dca/worker/config.go +++ /dev/null @@ -1,49 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/spf13/viper" - "github.com/vultisig/verifier/vault_config" - - "github.com/vultisig/plugin/storage" -) - -type DCAWorkerConfig struct { - Database struct { - DSN string `mapstructure:"dsn" json:"dsn,omitempty"` - } `mapstructure:"database" json:"database,omitempty"` - Redis storage.RedisConfig `mapstructure:"redis" json:"redis,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"` -} - -func GetConfigure() (*DCAWorkerConfig, error) { - configName := os.Getenv("VS_CONFIG_NAME") - if configName == "" { - configName = "config" - } - - return ReadConfig(configName) -} - -func ReadConfig(configName string) (*DCAWorkerConfig, error) { - viper.SetConfigName(configName) - viper.AddConfigPath(".") - viper.AutomaticEnv() - - if err := viper.ReadInConfig(); err != nil { - return nil, fmt.Errorf("fail to reading config file, %w", err) - } - var cfg DCAWorkerConfig - err := viper.Unmarshal(&cfg) - if err != nil { - return nil, fmt.Errorf("unable to decode into struct, %w", err) - } - return &cfg, nil -} diff --git a/cmd/dca/worker/main.go b/cmd/dca/worker/main.go deleted file mode 100644 index c1cbb775..00000000 --- a/cmd/dca/worker/main.go +++ /dev/null @@ -1,80 +0,0 @@ -package main - -import ( - "context" - "fmt" - - "github.com/DataDog/datadog-go/statsd" - "github.com/hibiken/asynq" - "github.com/sirupsen/logrus" - "github.com/vultisig/verifier/plugin/tasks" - "github.com/vultisig/verifier/tx_indexer" - "github.com/vultisig/verifier/tx_indexer/pkg/storage" - "github.com/vultisig/verifier/vault" -) - -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)) - } - mux := asynq.NewServeMux() - // mux.HandleFunc(tasks.TypePluginTransaction, vaultService.HandlePluginTransaction) - 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)) - } -} diff --git a/create_buckets.sh b/create_buckets.sh index c1610176..456ed051 100755 --- a/create_buckets.sh +++ b/create_buckets.sh @@ -5,7 +5,7 @@ sleep 5 mc alias set local http://localhost:9000 minioadmin minioadmin -buckets="vultisig-payroll vultisig-dca vultisig-fee" +buckets="vultisig-payroll vultisig-fee" for bucket in $buckets; do mc mb --ignore-existing local/$bucket diff --git a/etc/vultisig/dca.yml b/etc/vultisig/dca.yml deleted file mode 100644 index 2c727156..00000000 --- a/etc/vultisig/dca.yml +++ /dev/null @@ -1,8 +0,0 @@ -# plugin/dca/dca.yml -type: dca -version: 0.0.1 - -rpc_url: https://eth.llamarpc.com -uniswap: - v2_router: 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D - deadline: 5 \ No newline at end of file diff --git a/init-scripts/01_create_vultisig_plugin.sql b/init-scripts/01_create_vultisig_plugin.sql index bab1f08f..4806ddee 100644 --- a/init-scripts/01_create_vultisig_plugin.sql +++ b/init-scripts/01_create_vultisig_plugin.sql @@ -1,3 +1,2 @@ CREATE DATABASE "vultisig-payroll"; -CREATE DATABASE "vultisig-fee"; -CREATE DATABASE "vultisig-dca"; \ No newline at end of file +CREATE DATABASE "vultisig-fee"; \ No newline at end of file diff --git a/plugin/dca/config.go b/plugin/dca/config.go deleted file mode 100644 index 000ce935..00000000 --- a/plugin/dca/config.go +++ /dev/null @@ -1,59 +0,0 @@ -// plugin/dca/config.go -package dca - -import ( - "errors" - "fmt" - "strings" - - "github.com/spf13/viper" -) - -type PluginConfig struct { - Type string `mapstructure:"type"` - Version string `mapstructure:"version"` - RpcURL string `mapstructure:"rpc_url"` - Uniswap struct { - V2Router string `mapstructure:"v2_router"` - Deadline int64 `mapstructure:"deadline"` - } `mapstructure:"uniswap"` -} - -func loadPluginConfig(basePath string) (*PluginConfig, error) { - v := viper.New() - v.SetConfigName("dca") - - // Add config paths in order of precedence - if basePath != "" { - v.AddConfigPath(basePath) - } - v.AddConfigPath(".") - v.AddConfigPath("/etc/vultisig") - - // Enable environment variable overrides - v.AutomaticEnv() - v.SetEnvPrefix("DCA") - v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - - if err := v.ReadInConfig(); err != nil { - return nil, fmt.Errorf("failed to read config: %w", err) - } - - var config PluginConfig - if err := v.Unmarshal(&config); err != nil { - return nil, fmt.Errorf("failed to unmarshal config: %w", err) - } - - // Validate configuration - if config.RpcURL == "" { - return nil, errors.New("rpc_url is required") - } - if config.Uniswap.V2Router == "" { - return nil, errors.New("uniswap v2 router address is required") - } - if config.Uniswap.Deadline <= 0 { - return nil, errors.New("uniswap deadline must be positive") - } - - return &config, nil -} diff --git a/plugin/dca/dca.go b/plugin/dca/dca.go deleted file mode 100644 index 92dd018e..00000000 --- a/plugin/dca/dca.go +++ /dev/null @@ -1,776 +0,0 @@ -package dca - -import ( - "context" - "encoding/hex" - "errors" - "fmt" - "math/big" - "strconv" - "strings" - "time" - - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - gcommon "github.com/ethereum/go-ethereum/common" - gtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/rlp" - "github.com/google/uuid" - "github.com/sirupsen/logrus" - "github.com/vultisig/mobile-tss-lib/tss" - "github.com/vultisig/verifier/address" - vcommon "github.com/vultisig/verifier/common" - "github.com/vultisig/verifier/plugin" - vtypes "github.com/vultisig/verifier/types" - - rtypes "github.com/vultisig/recipes/types" - - "github.com/vultisig/plugin/internal/sigutil" - "github.com/vultisig/plugin/pkg/uniswap" - "github.com/vultisig/plugin/storage" -) - -const ( - pluginType = "dca" - pluginVersion = "0.0.1" - policyVersion = "0.0.1" -) - -// TODO: remove once the plugin installation is implemented (resharding) -const ( - hexEncryptionKey = "539440138236b389cb0355aa1e81d11e51e9ad7c94b09bb45704635913604a73" -) - -var ( - ErrCompletedPolicy = errors.New("policy completed all swaps") -) - -var _ plugin.Plugin = (*DCAPlugin)(nil) - -type DCAPlugin struct { - uniswapClient *uniswap.Client - rpcClient *ethclient.Client - db storage.DatabaseStorage - logger *logrus.Logger -} - -func NewDCAPlugin(db storage.DatabaseStorage, logger *logrus.Logger, baseConfigPath string) (*DCAPlugin, error) { - cfg, err := loadPluginConfig(baseConfigPath) - if err != nil { - return nil, fmt.Errorf("fail to load plugin config: %w", err) - } - - rpcClient, err := ethclient.Dial(cfg.RpcURL) - if err != nil { - return nil, fmt.Errorf("fail to connect to RPC client: %w", err) - } - - routerAddress := gcommon.HexToAddress(cfg.Uniswap.V2Router) - uniswapCfg := uniswap.NewConfig( - rpcClient, - &routerAddress, - 2000000, // TODO: config - 50000, // TODO: config - time.Duration(cfg.Uniswap.Deadline)*time.Minute, - ) - - uniswapClient, err := uniswap.NewClient(uniswapCfg) - if err != nil { - return nil, fmt.Errorf("fail to initialize Uniswap client: %w", err) - } - - return &DCAPlugin{ - uniswapClient: uniswapClient, - rpcClient: rpcClient, - db: db, - logger: logger, - }, nil -} - -func (p *DCAPlugin) SigningComplete( - ctx context.Context, - signature tss.KeysignResponse, - signRequest vtypes.PluginKeysignRequest, - policy vtypes.PluginPolicy, -) error { - var dcaPolicy DCAPolicy - // TODO: convert recipe to DCAPolicy - - chainID, ok := new(big.Int).SetString(dcaPolicy.ChainID, 10) - if !ok { - return errors.New("fail to parse chain ID") - } - - // currently we are only signing one transaction - txHash := signRequest.Messages[0].Hash - if len(txHash) == 0 { - return errors.New("transaction hash is missing") - } - - signedTx, _, err := sigutil.SignLegacyTx(signature, txHash, signRequest.Transaction, chainID) - if err != nil { - p.logger.Error("fail to sign transaction: ", err) - return fmt.Errorf("fail to sign transaction: %w", err) - } - - err = p.rpcClient.SendTransaction(context.Background(), signedTx) - if err != nil { - p.logger.Error("fail to send transaction: ", err) - return fmt.Errorf("failed to send transaction: %w", err) - } - - receipt, err := bind.WaitMined(context.Background(), p.rpcClient, signedTx) - if err != nil { - p.logger.Error("fail to wait for transaction receipt: ", err) - return fmt.Errorf("fail to wait for transaction to be mined: %w", err) - } - if receipt.Status != 1 { - return fmt.Errorf("transaction reverted: %d", receipt.Status) - } - - p.logger.Info("transaction receipt: ", "status: ", receipt.Status) - return nil -} - -func (p *DCAPlugin) ValidatePluginPolicy(policyDoc vtypes.PluginPolicy) error { - if policyDoc.PluginID != vtypes.PluginVultisigDCA_0000 { - return fmt.Errorf("policy does not match plugin type, expected: %s, got: %s", pluginType, policyDoc.PluginID) - } - - if policyDoc.PluginVersion != pluginVersion { - return fmt.Errorf("policy does not match plugin version, expected: %s, got: %s", pluginVersion, policyDoc.PluginVersion) - } - - if fmt.Sprintf("%d", policyDoc.PolicyVersion) != policyVersion { //TODO policy version will essentially be a nonce that is incremented by 1, not, like plugin versions that will follow traditional engineering versioning e.g. v2.12.3. For now we can compare string types - return fmt.Errorf("policy does not match policy version, expected: %s, got: %d", policyVersion, policyDoc.PolicyVersion) - } - - if policyDoc.PublicKey == "" { - return fmt.Errorf("policy does not contain public_key") - } - - var dcaPolicy DCAPolicy - // TODO: convert recipe to DCAPolicy - - mixedCaseTokenIn, err := gcommon.NewMixedcaseAddressFromString(dcaPolicy.SourceTokenID) - if err != nil { - return fmt.Errorf("invalid source token address: %s", dcaPolicy.SourceTokenID) - } - if strings.ToLower(dcaPolicy.SourceTokenID) != dcaPolicy.SourceTokenID { - if !mixedCaseTokenIn.ValidChecksum() { - return fmt.Errorf("invalid source token address checksum: %s", dcaPolicy.SourceTokenID) - } - } - - mixedCaseTokenOut, err := gcommon.NewMixedcaseAddressFromString(dcaPolicy.DestinationTokenID) - if err != nil { - return fmt.Errorf("invalid destination token address: %s", dcaPolicy.DestinationTokenID) - } - if strings.ToLower(dcaPolicy.DestinationTokenID) != dcaPolicy.DestinationTokenID { - if !mixedCaseTokenOut.ValidChecksum() { - return fmt.Errorf("invalid destination token address checksum: %s", dcaPolicy.DestinationTokenID) - } - } - - sourceAddrPolicy := gcommon.HexToAddress(dcaPolicy.SourceTokenID) - destAddrPolicy := gcommon.HexToAddress(dcaPolicy.DestinationTokenID) - if sourceAddrPolicy == gcommon.HexToAddress("0x0") || destAddrPolicy == gcommon.HexToAddress("0x0") { - return fmt.Errorf("invalid token addresses") - } - - if dcaPolicy.SourceTokenID == dcaPolicy.DestinationTokenID { - return fmt.Errorf("source token and destination token addresses are the same") - } - - if dcaPolicy.TotalAmount == "" { - return fmt.Errorf("total amount is required") - } - totalAmount, ok := new(big.Int).SetString(dcaPolicy.TotalAmount, 10) - if !ok { - return fmt.Errorf("invalid total amount %s", dcaPolicy.TotalAmount) - } - if totalAmount.Cmp(big.NewInt(0)) <= 0 { - return fmt.Errorf("total amount must be greater than 0") - } - - if dcaPolicy.TotalOrders == "" { - return fmt.Errorf("total orders is required") - } - totalOrders, ok := new(big.Int).SetString(dcaPolicy.TotalOrders, 10) - if !ok { - return fmt.Errorf("invalid total orders %s", dcaPolicy.TotalOrders) - } - if totalOrders.Cmp(big.NewInt(0)) <= 0 { - return fmt.Errorf("total orders must be greater than 0") - } - - if dcaPolicy.PriceRange.Min != "" && dcaPolicy.PriceRange.Max != "" { - minPrice, ok := new(big.Int).SetString(dcaPolicy.PriceRange.Min, 10) - if !ok { - return fmt.Errorf("invalid min price %s", dcaPolicy.PriceRange.Min) - } - maxPrice, ok := new(big.Int).SetString(dcaPolicy.PriceRange.Max, 10) - if !ok { - return fmt.Errorf("invalid max price %s", dcaPolicy.PriceRange.Max) - } - if minPrice.Cmp(maxPrice) > 0 { - return fmt.Errorf("min price should be equal or lower than max price") - } - } - - if dcaPolicy.ChainID == "" { - return fmt.Errorf("chain id is required") - } - - if err := validateInterval(dcaPolicy.Schedule.Interval, dcaPolicy.Schedule.Frequency); err != nil { - return err - } - - return nil -} - -func validateInterval(intervalStr string, frequency string) error { - interval, err := strconv.Atoi(intervalStr) - if err != nil { - return fmt.Errorf("invalid interval: %w", err) - } - - if interval <= 0 { - return fmt.Errorf("interval must be greater than 0") - } - - switch frequency { - case "minutely": - if interval < 15 { - return fmt.Errorf("minutely interval must be at least 15 minutes") - } - case "hourly": - if interval > 23 { - return fmt.Errorf("hourly interval must be at most 23 hours") - } - case "daily": - if interval > 31 { - return fmt.Errorf("daily interval must be at most 31 days") - } - case "weekly": - if interval > 52 { - return fmt.Errorf("weekly interval must be at most 52 weeks") - } - case "monthly": - if interval > 12 { - return fmt.Errorf("monthly interval must be at most 12 months") - } - default: - return fmt.Errorf("invalid frequency: %s", frequency) - } - - return nil -} - -func (p *DCAPlugin) ProposeTransactions(policy vtypes.PluginPolicy) ([]vtypes.PluginKeysignRequest, error) { - p.logger.Info("DCA: PROPOSE TRANSACTIONS") - - var txs []vtypes.PluginKeysignRequest - - // validate policy - err := p.ValidatePluginPolicy(policy) - if err != nil { - return txs, fmt.Errorf("fail to validate plugin policy: %w", err) - } - - var dcaPolicy DCAPolicy - // TODO: convert recipe to DCAPolicy - // Parse TotalAmount and TotalOrders - totalAmount, ok := new(big.Int).SetString(dcaPolicy.TotalAmount, 10) - if !ok { - return txs, fmt.Errorf("invalid total amount %s", dcaPolicy.TotalAmount) - } - - totalOrders, ok := new(big.Int).SetString(dcaPolicy.TotalOrders, 10) - if !ok { - return txs, fmt.Errorf("invalid total orders %s", dcaPolicy.TotalOrders) - } - - // Check number of completed swaps - completedSwaps, err := p.getCompletedSwapTransactionsCount(context.Background(), policy.ID) - if err != nil { - return txs, fmt.Errorf("fail to get completed swap transactions count: %w", err) - } - - if completedSwaps >= totalOrders.Int64() { - if err := p.completePolicy(context.Background(), policy); err != nil { - return txs, fmt.Errorf("fail to complete policy: %w", err) - } - return txs, ErrCompletedPolicy - } - - // Calculate base amount and remainder - swapAmount := p.calculateSwapAmountPerOrder(totalAmount, totalOrders, completedSwaps) - p.logger.Info("DCA: SWAP AMOUNT: ", swapAmount.String()) - - chain := vcommon.Ethereum - - // build transactions - derivedAddress, _, _, err := address.GetAddress(policy.PublicKey, "", chain) - if err != nil { - return txs, fmt.Errorf("fail to derive address: %w", err) - } - - signerAddress := gcommon.HexToAddress(derivedAddress) - - chainID, ok := new(big.Int).SetString(dcaPolicy.ChainID, 10) - if !ok { - return txs, fmt.Errorf("fail to parse chain ID: %s", dcaPolicy.ChainID) - } - - rawTxsData, err := p.generateSwapTransactions(chainID, &signerAddress, dcaPolicy.SourceTokenID, dcaPolicy.DestinationTokenID, swapAmount) - if err != nil { - return txs, fmt.Errorf("fail to generate transaction hash: %w", err) - } - - for _, data := range rawTxsData { - signRequest := vtypes.PluginKeysignRequest{ - KeysignRequest: vtypes.KeysignRequest{ - PublicKey: policy.PublicKey, - Messages: []vtypes.KeysignMessage{ - { - Message: hex.EncodeToString(data.RlpTxBytes), - Hash: hex.EncodeToString(data.TxHash), - }, - }, - SessionID: uuid.New().String(), - HexEncryptionKey: hexEncryptionKey, - Parties: []string{}, - PluginID: policy.PluginID.String(), - PolicyID: policy.ID, - }, - Transaction: hex.EncodeToString(data.RlpTxBytes), - TransactionType: data.Type, - } - txs = append(txs, signRequest) - } - - return txs, nil -} - -func (p *DCAPlugin) ValidateProposedTransactions(policy vtypes.PluginPolicy, txs []vtypes.PluginKeysignRequest) error { - p.logger.Info("DCA: VALIDATE TRANSACTION PROPOSAL") - - if len(txs) == 0 { - return fmt.Errorf("no transactions provided for validation") - } - - if err := p.ValidatePluginPolicy(policy); err != nil { - return fmt.Errorf("failed to validate plugin policy: %w", err) - } - - var dcaPolicy DCAPolicy - // TODO: convert recipe to DCAPolicy - - // Validate policy params. - policyChainID, ok := new(big.Int).SetString(dcaPolicy.ChainID, 10) - if !ok { - return fmt.Errorf("failed to parse chain ID: %s", dcaPolicy.ChainID) - } - - sourceAddrPolicy := gcommon.HexToAddress(dcaPolicy.SourceTokenID) - destAddrPolicy := gcommon.HexToAddress(dcaPolicy.DestinationTokenID) - - totalAmount, ok := new(big.Int).SetString(dcaPolicy.TotalAmount, 10) - if !ok { - return fmt.Errorf("invalid total amount") - } - - totalOrders, ok := new(big.Int).SetString(dcaPolicy.TotalOrders, 10) - if !ok { - return fmt.Errorf("invalid total orders") - } - - chain := vcommon.Ethereum - // TODO: hexChainCode is stored with vault , so we need to get it from vault - derivedAddress, _, _, err := address.GetAddress(policy.PublicKey, "", chain) - if err != nil { - return fmt.Errorf("failed to derive address: %w", err) - } - - signerAddress := gcommon.HexToAddress(derivedAddress) - p.logger.Warn("Signer address used for swaps: ", signerAddress.String()) - - completedSwaps, err := p.getCompletedSwapTransactionsCount(context.Background(), policy.ID) - if err != nil { - return fmt.Errorf("fail to get completed swaps: %w", err) - } - // TODO: Change this to make the policy to status COMPLETED if: completed swaps == total orders. - if completedSwaps >= totalOrders.Int64() { - if err := p.completePolicy(context.Background(), policy); err != nil { - return fmt.Errorf("fail to complete policy: %w", err) - } - p.logger.Info("DCA: COMPLETED SWAPS: ", totalOrders.Int64()) - return ErrCompletedPolicy - } - - // Validate each transaction - for _, tx := range txs { - if err := p.validateTransaction(tx, completedSwaps, totalAmount, totalOrders, policyChainID, &sourceAddrPolicy, &destAddrPolicy, &signerAddress); err != nil { - return fmt.Errorf("failed to validate transaction: %w", err) - } - } - return nil -} - -func (p *DCAPlugin) validateTransaction(keysignRequest vtypes.PluginKeysignRequest, completedSwaps int64, policyTotalAmount, policyTotalOrders, policyChainID *big.Int, sourceAddrPolicy, destAddrPolicy, signerAddress *gcommon.Address) error { - // Parse the transaction - var tx *gtypes.Transaction - txBytes, err := hex.DecodeString(keysignRequest.Transaction) - if err != nil { - p.logger.Error("failed to decode transaction bytes: ", err) - return fmt.Errorf("failed to decode transaction bytes: %w", err) - } - err = rlp.DecodeBytes(txBytes, &tx) - if err != nil { - p.logger.Error("failed to parse RLP transaction: ", err) - return fmt.Errorf("fail to parse RLP transaction: %w", err) - } - - // Validate chain ID - if tx.ChainId().Cmp(policyChainID) != 0 { - p.logger.Error("chain ID mismatch: ", tx.ChainId().String()) - return fmt.Errorf("chain ID mismatch: expected %s, got %s", policyChainID.String(), tx.ChainId().String()) - } - - // Validate gas parameters - if tx.Gas() == 0 { - p.logger.Error("invalid gas limit: must be greater than zero") - return fmt.Errorf("invalid gas limit: must be greater than zero") - } - if tx.GasPrice().Cmp(big.NewInt(0)) <= 0 { - p.logger.Error("invalid gas price: must be greater than zero") - return fmt.Errorf("invalid gas price: must be greater than zero") - } - - // Validate destination address - if tx.To() == nil { - return fmt.Errorf("transaction has no destination") - } - - // Validate transaction data exists - if len(tx.Data()) == 0 { - p.logger.Error("transaction contains empty payload") - return fmt.Errorf("transaction contains empty payload") - } - - txDestination := *tx.To() - - switch { - case txDestination.Cmp(*p.uniswapClient.GetRouterAddress()) == 0: - // Swap transaction - return p.validateSwapTransaction(tx, completedSwaps, policyTotalAmount, policyTotalOrders, sourceAddrPolicy, destAddrPolicy, signerAddress) - case txDestination.Cmp(*sourceAddrPolicy) == 0: - // Approve transaction - return p.validateApproveTransaction(tx, completedSwaps, policyTotalAmount, policyTotalOrders) - default: - // Unknown transaction type - p.logger.Error("invalid transaction destination: ", txDestination.String()) - return fmt.Errorf("unsupported transaction: %s", txDestination.String()) - } -} - -func (p *DCAPlugin) validateSwapTransaction(tx *gtypes.Transaction, completedSwaps int64, policyTotalAmount, policyTotalOrders *big.Int, sourceAddrPolicy *gcommon.Address, destAddrPolicy *gcommon.Address, signerAddress *gcommon.Address) error { - parsedSwapABI, err := p.getSwapABI() - if err != nil { - p.logger.Error("failed to parse swap ABI: ", err) - return fmt.Errorf("failed to parse swap ABI: %w", err) - } - - method, err := parsedSwapABI.MethodById(tx.Data()) - if err != nil { - p.logger.Error("failed to find method in swap ABI") - return fmt.Errorf("failed to find method in swap ABI: %w", err) - } - - if method != nil && method.Name != "swapExactTokensForTokens" { - return fmt.Errorf("unexpected transaction method: expected 'swapExactTokensForTokens', got %s'", method.Name) - } - - if err = p.validateSwapParameters(tx, method, completedSwaps, policyTotalAmount, policyTotalOrders, sourceAddrPolicy, destAddrPolicy, signerAddress); err != nil { - return fmt.Errorf("failed to validate swap parameters: %w", err) - } - - return nil -} - -func (p *DCAPlugin) validateApproveTransaction(tx *gtypes.Transaction, completedSwaps int64, policyTotalAmount, policyTotalOrders *big.Int) error { - parsedApproveABI, err := p.getApproveABI() - if err != nil { - p.logger.Error("failed to parse approve ABI: ", err) - return fmt.Errorf("failed to parse approve ABI: %w", err) - } - method, err := parsedApproveABI.MethodById(tx.Data()) - if err != nil { - p.logger.Error("failed to find method in approve ABI") - return fmt.Errorf("failed to find method in approve ABI: %w", err) - } - - if method != nil && method.Name != "approve" { - return fmt.Errorf("unexpected transaction method: expected 'approve', got %s'", method.Name) - } - - if err = p.validateApproveParameters(tx, method, completedSwaps, policyTotalAmount, policyTotalOrders); err != nil { - return fmt.Errorf("failed to validate approve parameters: %w", err) - } - - return nil -} - -func (p *DCAPlugin) validateApproveParameters(tx *gtypes.Transaction, method *abi.Method, completedSwaps int64, policyTotalAmount, policyTotalOrders *big.Int) error { - p.logger.Info("VALIDATING APPROVE PARAMETERS") - - // Decode the parameters - inputData := tx.Data()[4:] - decodedParams, err := method.Inputs.Unpack(inputData) - if err != nil { - return fmt.Errorf("failed to decode approve parameters: %w", err) - } - - // Validate spender address (should be the router) - spender, ok := decodedParams[0].(gcommon.Address) - if !ok { - return fmt.Errorf("failed to parse spender address: invalid format") - } - - if spender.Cmp(*p.uniswapClient.GetRouterAddress()) != 0 { - return fmt.Errorf("invalid spender address: expected=%s, got=%s", p.uniswapClient.GetRouterAddress().String(), spender.String()) - } - - // Validate approval amount (should be within policy limits) - amount, ok := decodedParams[1].(*big.Int) - if !ok { - return fmt.Errorf("failed to parse approval amount: invalid format") - } - p.logger.Info("VALIDATING AMOUNT: ", amount.String()) - - expectedSwapAmount := p.calculateSwapAmountPerOrder(policyTotalAmount, policyTotalOrders, completedSwaps) - p.logger.Info("EXPECTED APPROVE AMOUNT: ", expectedSwapAmount.String()) - if expectedSwapAmount.Cmp(amount) != 0 { - return fmt.Errorf("invalid approval amount: expected=%v, got=%v", expectedSwapAmount, amount) - } - - return nil -} - -func (p *DCAPlugin) validateSwapParameters(tx *gtypes.Transaction, method *abi.Method, completedSwaps int64, policyTotalAmount, policyTotalOrders *big.Int, sourceAddrPolicy, destAddrPolicy, signerAddress *gcommon.Address) error { - p.logger.Info("VALIDATING SWAP PARAMETERS") - - inputData := tx.Data()[4:] - decodedParams, err := method.Inputs.Unpack(inputData) - if err != nil { - return fmt.Errorf("failed to decode transaction swap parameters: %w", err) - } - path, ok := decodedParams[2].([]gcommon.Address) - if !ok || len(path) < 2 { - return fmt.Errorf("invalid swap path: must contain at least 2 tokens") - } - - if path[0] != *sourceAddrPolicy || path[len(path)-1] != *destAddrPolicy { - return fmt.Errorf("swap path tokens mismatch: expected source=%s, destination=%s", *sourceAddrPolicy, *destAddrPolicy) - } - - // Validate destination address matches signer - to, ok := decodedParams[3].(gcommon.Address) - if !ok || to != *signerAddress { - return fmt.Errorf("invalid swap destination: expected=%s, got=%s", *signerAddress, to.String()) - } - - amountIn, ok := decodedParams[0].(*big.Int) - if !ok { - return fmt.Errorf("failed to parse swap amount: invalid format") - } - - p.logger.Info("VALIDATING AMOUNT: ", amountIn.String()) - - expectedSwapAmountIn := p.calculateSwapAmountPerOrder(policyTotalAmount, policyTotalOrders, completedSwaps) - p.logger.Info("EXPECTED AMOUNT: ", expectedSwapAmountIn.String()) - if amountIn.Cmp(expectedSwapAmountIn) != 0 { - return fmt.Errorf("invalid swap amount: expected=%s, got=%s", expectedSwapAmountIn.String(), amountIn.String()) - } - - return nil -} - -func (p *DCAPlugin) getSwapABI() (abi.ABI, error) { - routerABI := `[ - { - "name": "swapExactTokensForTokens", - "type": "function", - "inputs": [ - { - "name": "amountIn", - "type": "uint256" - }, - { - "name": "amountOutMin", - "type": "uint256" - }, - { - "name": "path", - "type": "address[]" - }, - { - "name": "to", - "type": "address" - }, - { - "name": "deadline", - "type": "uint256" - } - ] - } - ]` - return abi.JSON(strings.NewReader(routerABI)) -} - -func (p *DCAPlugin) getApproveABI() (abi.ABI, error) { - approveABI := `[ - { - "name": "approve", - "type": "function", - "inputs": [ - { - "name": "spender", - "type": "address" - }, - { - "name": "value", - "type": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "bool" - } - ] - } - ]` - - return abi.JSON(strings.NewReader(approveABI)) -} - -type RawTxData struct { - TxHash []byte - RlpTxBytes []byte - Type string -} - -func (p *DCAPlugin) generateSwapTransactions(chainID *big.Int, signerAddress *gcommon.Address, srcToken, destToken string, swapAmount *big.Int) ([]RawTxData, error) { - srcTokenAddress := gcommon.HexToAddress(srcToken) - destTokenAddress := gcommon.HexToAddress(destToken) - - // TODO: validate the price range (if specified) - var rawTxsData []RawTxData - // from a UX perspective, it is better to do the "approve" tx as part of the DCA execution rather than having it be part of the policy creation/update - // approve Router to spend input token. - allowance, err := p.uniswapClient.GetAllowance(*signerAddress, srcTokenAddress) - if err != nil { - return []RawTxData{}, fmt.Errorf("failed to get allowance: %w", err) - } - p.logger.Info("DCA: ALLOWANCE: ", allowance.String()) - - // Propose APPROVE if allowance is insufficient - var swapNonce uint64 - if allowance.Cmp(swapAmount) < 0 { - txHash, rawTx, err := p.uniswapClient.ApproveERC20Token(chainID, signerAddress, srcTokenAddress, *p.uniswapClient.GetRouterAddress(), swapAmount, 0) - if err != nil { - return []RawTxData{}, fmt.Errorf("failed to make APPROVE transaction: %w", err) - } - rawTxsData = append(rawTxsData, RawTxData{txHash, rawTx, "APPROVE"}) - p.logger.Info("DCA: Proposed APPROVE transaction") - swapNonce = 1 - } - p.logger.Info("DCA: SWAP NONCE: ", swapNonce) - - // Propose SWAP transaction - tokensPair := []gcommon.Address{srcTokenAddress, destTokenAddress} - expectedAmountOut, err := p.uniswapClient.GetExpectedAmountOut(swapAmount, tokensPair) - if err != nil { - return []RawTxData{}, fmt.Errorf("failed to get expected amount out: %w", err) - } - p.logger.Info("DCA: EXPECTED AMOUNT OUT: ", expectedAmountOut.String()) - - slippagePercentage := 1.0 - amountOutMin := p.uniswapClient.CalculateAmountOutMin(expectedAmountOut, slippagePercentage) - - txHash, rawTx, err := p.uniswapClient.SwapTokens(chainID, signerAddress, swapAmount, amountOutMin, tokensPair, swapNonce) - if err != nil { - return []RawTxData{}, fmt.Errorf("failed to make SWAP transaction: %w", err) - } - rawTxsData = append(rawTxsData, RawTxData{txHash, rawTx, "SWAP"}) - p.logTokenBalances(p.uniswapClient, signerAddress, srcTokenAddress, destTokenAddress) - - return rawTxsData, nil -} - -func (p *DCAPlugin) logTokenBalances(client *uniswap.Client, signerAddress *gcommon.Address, tokenInAddress, tokenOutAddress gcommon.Address) { - tokenInBalance, err := client.GetTokenBalance(signerAddress, tokenInAddress) - if err != nil { - p.logger.Error("Input token balance: ", err) - return - } - p.logger.Info("Input token balance: ", tokenInBalance.String()) - - tokenOutBalance, err := client.GetTokenBalance(signerAddress, tokenOutAddress) - if err != nil { - p.logger.Error("Output token balance: ", err) - return - } - p.logger.Info("Output token balance: ", tokenOutBalance.String()) -} - -func (p *DCAPlugin) getCompletedSwapTransactionsCount(ctx context.Context, policyID uuid.UUID) (int64, error) { - // TODO implement in scope of DCA plugin implementation - return 0, nil -} - -func (p *DCAPlugin) calculateSwapAmountPerOrder(totalAmount, totalOrders *big.Int, completedSwaps int64) *big.Int { - baseAmount := new(big.Int).Div(totalAmount, totalOrders) - p.logger.Info("DCA: BASE AMOUNT: ", baseAmount.String()) - remainder := new(big.Int).Mod(totalAmount, totalOrders) - p.logger.Info("DCA: REMAINDER: ", remainder.String()) - - // Determine swap amount for the next order - swapAmount := new(big.Int).Set(baseAmount) - if big.NewInt(completedSwaps+1).Cmp(remainder) <= 0 { - p.logger.Info("DCA: REMAINDER ADDING") - swapAmount.Add(swapAmount, big.NewInt(1)) // Add 1 to distribute remainder - } - return swapAmount -} - -func (p *DCAPlugin) completePolicy(ctx context.Context, policy vtypes.PluginPolicy) error { - p.logger.WithFields(logrus.Fields{ - "policy_id": policy.ID, - }).Info("DCA: All orders completed, no transactions to propose") - - // TODO: Sync a COMPLETED state for the policy with the verifier database. - dbTx, err := p.db.Pool().Begin(ctx) - defer dbTx.Rollback(ctx) - if err != nil { - return fmt.Errorf("fail to begin transaction: %w", err) - } - policy.Active = false - _, err = p.db.UpdatePluginPolicyTx(ctx, dbTx, policy) - if err != nil { - return fmt.Errorf("fail to update policy: %w", err) - } - - if err := dbTx.Commit(ctx); err != nil { - return fmt.Errorf("failed to commit transaction: %w", err) - } - - return nil -} - -func (p *DCAPlugin) GetRecipeSpecification() (*rtypes.RecipeSchema, error) { - return &rtypes.RecipeSchema{}, nil -} diff --git a/plugin/dca/dcaPluginUiSchema.json b/plugin/dca/dcaPluginUiSchema.json deleted file mode 100644 index 4426fcdb..00000000 --- a/plugin/dca/dcaPluginUiSchema.json +++ /dev/null @@ -1,287 +0,0 @@ -{ - "form": { - "schema": { - "title": "DCA Plugin Policy", - "type": "object", - "required": [ - "total_amount", - "source_token_id", - "destination_token_id", - "total_orders" - ], - "properties": { - "chain_id": { - "type": "string", - "default": "1" - }, - "total_amount": { - "title": "I want to allocate", - "type": "string", - "pattern": "^(?!0$)(?!0+\\.0*$)[0-9]+(\\.[0-9]+)?$" - }, - "source_token_id": { - "type": "string", - "default": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" - }, - "destination_token_id": { - "title": "I want to buy", - "type": "string", - "default": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" - }, - "schedule": { - "type": "object", - "items": { - "type": "object" - }, - "required": [ - "interval", - "frequency" - ], - "properties": { - "interval": { - "title": "Every", - "type": "string" - }, - "frequency": { - "type": "string", - "title": "Time", - "enum": [ - "minutely", - "hourly", - "daily", - "weekly", - "monthly" - ], - "default": "minutely" - } - }, - "dependencies": { - "frequency": { - "oneOf": [ - { - "properties": { - "frequency": { - "enum": [ - "minutely" - ] - }, - "interval": { - "type": "string", - "pattern": "^(1[5-9]|[2-9][0-9]+)(\\.[0-9]+)?$" - } - } - }, - { - "properties": { - "frequency": { - "enum": [ - "hourly", - "daily", - "weekly", - "monthly" - ] - }, - "interval": { - "type": "string", - "pattern": "^(?!0$)(?!0+\\.0*$)[0-9]+(\\.[0-9]+)?$" - } - } - } - ] - } - } - }, - "total_orders": { - "title": "Over (orders)", - "type": "string", - "pattern": "^(?!0$)(?!0+\\.0*$)[0-9]+(\\.[0-9]+)?$" - }, - "price_range": { - "type": "object", - "items": { - "type": "object", - "required": [ - "title" - ] - }, - "properties": { - "min": { - "title": "Price Range (optional)", - "type": "string", - "pattern": "^(?!0$)(?!0+\\.0*$)[0-9]+(\\.[0-9]+)?$" - }, - "max": { - "type": "string", - "pattern": "^(?!0$)(?!0+\\.0*$)[0-9]+(\\.[0-9]+)?$" - } - } - } - } - }, - "uiSchema": { - "ui:order": [ - "total_amount", - "source_token_id", - "destination_token_id", - "schedule", - "total_orders", - "*" - ], - "ui:description": "Set up configuration settings for DCA Plugin Policy", - "ui:submitButtonOptions": { - "submitText": "Save policy" - }, - "chain_id": { - "ui:widget": "hidden" - }, - "total_amount": { - "ui:widget": "WeiConverter", - "ui:classNames": "input-background stacked-input", - "ui:style": { - "display": "inline-block", - "width": "48%", - "marginRight": "2%", - "boxSizing": "border-box", - "verticalAlign": "top" - } - }, - "source_token_id": { - "ui:widget": "TokenSelector", - "ui:options": { - "label": false, - "classNames": "input-background stacked-input" - }, - "ui:style": { - "display": "inline-block", - "width": "48%", - "boxSizing": "border-box", - "verticalAlign": "top", - "marginTop": "37px" - } - }, - "destination_token_id": { - "ui:widget": "TokenSelector", - "ui:options": { - "classNames": "input-background stacked-input" - } - }, - "schedule": { - "ui:hideError": true, - "ui:order": [ - "interval", - "frequency" - ], - "ui:options": { - "label": false - }, - "ui:classNames": "form-row", - "frequency": { - "ui:readonly": false, - "ui:hideError": true, - "ui:classNames": "input-background stacked-input", - "ui:style": { - "display": "flex", - "flexDirection": "column" - } - }, - "interval": { - "ui:readonly": false, - "ui:hideError": false, - "ui:classNames": "input-background stacked-input", - "ui:style": { - "display": "flex", - "flexDirection": "column" - } - } - }, - "total_orders": { - "ui:classNames": "input-background stacked-input" - }, - "price_range": { - "ui:order": [ - "min", - "max" - ], - "ui:options": { - "label": false, - "classNames": "form-row" - }, - "min": { - "ui:readonly": false, - "ui:options": { - "classNames": "input-background stacked-input", - "placeholder": "Min Price" - }, - "ui:style": { - "display": "flex", - "flexDirection": "column", - "justifyContent": "flex-end" - } - }, - "max": { - "ui:readonly": false, - "ui:options": { - "classNames": "input-background stacked-input", - "label": false, - "placeholder": "Max Price" - }, - "ui:style": { - "display": "flex", - "flexDirection": "column", - "justifyContent": "flex-end" - } - } - } - }, - "plugin_version": "0.0.1", - "policy_version": "0.0.1", - "plugin_type": "dca" - }, - "table": { - "columns": [ - { - "accessorKey": "pair", - "header": "Pair", - "cellComponent": "TokenPair" - }, - { - "accessorKey": "sell", - "header": "Sell Total", - "cellComponent": "TokenAmount" - }, - { - "accessorKey": "orders", - "header": "Total orders" - }, - { - "accessorKey": "toBuy", - "header": "To buy", - "cellComponent": "TokenName" - }, - { - "accessorKey": "orderInterval", - "header": "Order interval" - }, - { - "accessorKey": "status", - "header": "Active", - "cellComponent": "ActiveStatus" - } - ], - "mapping": { - "policyId": "id", - "pair": [ - "policy.source_token_id", - "policy.destination_token_id" - ], - "sell": [ - "policy.total_amount", - "policy.source_token_id" - ], - "orders": "policy.total_orders", - "toBuy": "policy.destination_token_id", - "orderInterval": "policy.schedule.interval, policy.schedule.frequency", - "status": "active" - } - } -} \ No newline at end of file diff --git a/plugin/dca/policy.go b/plugin/dca/policy.go deleted file mode 100644 index 674900fc..00000000 --- a/plugin/dca/policy.go +++ /dev/null @@ -1,25 +0,0 @@ -package dca - -type DCAPolicy struct { - ChainID string `json:"chain_id"` - SourceTokenID string `json:"source_token_id"` - DestinationTokenID string `json:"destination_token_id"` - TotalAmount string `json:"total_amount"` - TotalOrders string `json:"total_orders"` - Schedule Schedule `json:"schedule"` - PriceRange PriceRange `json:"price_range"` -} - -type PriceRange struct { - Min string `json:"min"` - Max string `json:"max"` -} - -// This is duplicated between DCA and Payroll to avoid a -// circular top-level dependency on the types package -type Schedule struct { - Frequency string `json:"frequency"` - Interval string `json:"interval"` - StartTime string `json:"start_time"` - EndTime string `json:"end_time,omitempty"` -} \ No newline at end of file diff --git a/scripts/dev/create_dca_policy/main.go b/scripts/dev/create_dca_policy/main.go deleted file mode 100644 index dc912c1e..00000000 --- a/scripts/dev/create_dca_policy/main.go +++ /dev/null @@ -1,117 +0,0 @@ -package main - -import ( - "bufio" - "bytes" - "encoding/json" - "flag" - "fmt" - "net/http" - "os" - "path/filepath" - "time" - - "github.com/google/uuid" - vtypes "github.com/vultisig/verifier/types" - - "github.com/vultisig/plugin/plugin/dca" -) - -var vaultName string -var stateDir string - -func main() { - flag.StringVar(&vaultName, "vault", "", "vault name") - flag.StringVar(&stateDir, "state-dir", "", "state directory") - flag.Parse() - - if vaultName == "" { - panic("vault name is required") - } - - if stateDir == "" { - homeDir, err := os.UserHomeDir() - if err != nil { - panic(err) - } - - stateDir = filepath.Join(homeDir, ".verifier", "vaults") - } - - keyPath := filepath.Join(stateDir, vaultName, "public_key") - rawKey, err := os.ReadFile(keyPath) - if err != nil { - panic(err) - } - - key := string(rawKey) - - fmt.Printf("Public key for vault %s:\n%s\n", vaultName, key) - - reader := bufio.NewReader(os.Stdin) - fmt.Print("Enter source token contract address: ") - sourceTokenContract, _ := reader.ReadString('\n') - sourceTokenContract = sourceTokenContract[:len(sourceTokenContract)-1] - - fmt.Print("Enter destination contract address: ") - destinationTokenContract, _ := reader.ReadString('\n') - destinationTokenContract = destinationTokenContract[:len(destinationTokenContract)-1] - - fmt.Print("Enter the input amount for swap: ") - swapAmountIn, _ := reader.ReadString('\n') - swapAmountIn = swapAmountIn[:len(swapAmountIn)-1] - - fmt.Printf("Source Token Contract: %s\n", sourceTokenContract) - fmt.Printf("Destination Token Contract: %s\n", destinationTokenContract) - fmt.Printf("Input amount for swap: %s\n\n", swapAmountIn) - - fmt.Print("Enter schedule frequency: ") - frequency, _ := reader.ReadString('\n') - frequency = frequency[:len(frequency)-1] - - policyId := uuid.New() - policy := vtypes.PluginPolicy{ - ID: policyId, - PublicKey: key, - PluginID: vtypes.PluginVultisigDCA_0000, - PluginVersion: "1.0.0", - PolicyVersion: 1, - Active: true, - Signature: "0x0000000000000000000000000000000000000000000000000000000000000000", - } - - dcaPolicy := dca.DCAPolicy{ - ChainID: "1", - SourceTokenID: sourceTokenContract, - DestinationTokenID: destinationTokenContract, - TotalAmount: swapAmountIn, - TotalOrders: "2", - Schedule: dca.Schedule{ - Frequency: frequency, - Interval: "", - StartTime: time.Now().UTC().Add(20 * time.Second).Format(time.RFC3339), - }, - } - - policyBytes, err := json.Marshal(dcaPolicy) - if err != nil { - panic(err) - } - - fmt.Println("DCA policy", string(policyBytes)) - - pluginHost := fmt.Sprintf("http://%s:%d", "localhost", 8080) - - reqBytes, err := json.Marshal(policy) - if err != nil { - panic(err) - } - - fmt.Printf("Creating policy on plugin server: %s\n", pluginHost) - - resp, err := http.Post(fmt.Sprintf("%s/plugin/policy", pluginHost), "application/json", bytes.NewBuffer(reqBytes)) - if err != nil { - panic(err) - } - fmt.Printf("Request sent: %d\n", resp.StatusCode) -} diff --git a/storage/postgres/schema/schema.sql b/storage/postgres/schema/schema.sql index c002e9cb..3d23fe80 100644 --- a/storage/postgres/schema/schema.sql +++ b/storage/postgres/schema/schema.sql @@ -1,6 +1,5 @@ CREATE TYPE "plugin_id" AS ENUM ( - 'vultisig-dca-0000', 'vultisig-payroll-0000', 'vultisig-fees-feee', 'vultisig-copytrader-0000' From 0b2268cf0a8160b537ef2e8c924da986e29dde2f Mon Sep 17 00:00:00 2001 From: webpiratt <213060745+webpiratt@users.noreply.github.com> Date: Fri, 15 Aug 2025 19:46:45 +0300 Subject: [PATCH 2/2] delete dca --- .github/workflows/migration-test.yml | 152 --------------------------- 1 file changed, 152 deletions(-) delete mode 100644 .github/workflows/migration-test.yml diff --git a/.github/workflows/migration-test.yml b/.github/workflows/migration-test.yml deleted file mode 100644 index 83ecc981..00000000 --- a/.github/workflows/migration-test.yml +++ /dev/null @@ -1,152 +0,0 @@ -name: Migration Test - -on: - push: - -jobs: - migration-test: - runs-on: ubuntu-latest - - services: - postgres: - image: postgres:15 - env: - POSTGRES_USER: myuser - POSTGRES_PASSWORD: mypassword - POSTGRES_DB: vultisig-plugin - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 5432:5432 - - redis: - image: redis:7 - options: >- - --health-cmd "redis-cli ping" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 6379:6379 - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.24.2' - - - name: Download go-wrappers - run: | - git clone https://github.com/vultisig/go-wrappers.git ../go-wrappers - - - name: Create test config - run: | - cat > payroll.json </dev/null; then - echo "Payroll process failed to start" - exit 1 - fi - - # Check if migrations ran successfully by verifying tables exist - PGPASSWORD=mypassword psql -h localhost -U myuser -d vultisig-plugin -c "\dt" | grep -E "(plugin_policies|scheduler)" | wc -l > /dev/null - if [ $? -ne 0 ]; then - echo "Migrations did not run successfully - expected tables not found" - kill $PAYROLL_PID - exit 1 - fi - - echo "All migrations ran successfully!" - - # Stop payroll - kill $PAYROLL_PID - - - name: Check migration integrity - run: | - # Verify goose migrations table exists and has entries - PGPASSWORD=mypassword psql -h localhost -U myuser -d vultisig-plugin -c "SELECT COUNT(*) FROM goose_db_version;" | grep -E "[0-9]+" > /dev/null - if [ $? -ne 0 ]; then - echo "Goose migrations table not found or empty" - exit 1 - fi - - echo "Migration integrity check passed!" - - - name: Check schema updates - run: | - # Dump current schema - PGPASSWORD=mypassword pg_dump -h localhost -U myuser -d vultisig-plugin \ - --schema-only \ - --no-comments \ - --no-owner \ - --quote-all-identifiers \ - -T public.goose_db_version \ - -T public.goose_db_version_id_seq | sed \ - -e '/^--.*/d' \ - -e '/^SET /d' \ - -e '/^SELECT pg_catalog./d' \ - -e 's/"public"\.//' | awk '/./ { e=0 } /^$/ { e += 1 } e <= 1' \ - > ./current_schema.sql - - # Compare with repository schema - if ! diff -u ./storage/postgres/schema/schema.sql ./current_schema.sql; then - echo "Schema has changed but schema.sql was not updated!" - echo "Please run 'make dump-schema CONFIG=config-plugin.yaml' to update the schema file." - exit 1 - fi - - echo "Schema is up to date!" \ No newline at end of file