diff --git a/cmd/copytrader/server/config.go b/cmd/copytrader/server/config.go new file mode 100644 index 0000000..bb705ca --- /dev/null +++ b/cmd/copytrader/server/config.go @@ -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"` +} + +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 +} diff --git a/cmd/copytrader/server/main.go b/cmd/copytrader/server/main.go new file mode 100644 index 0000000..ebd56fe --- /dev/null +++ b/cmd/copytrader/server/main.go @@ -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) + } + + server := api.NewServer( + cfg.Server, + db, + redisStorage, + vaultStorage, + redisOptions, + client, + inspector, + sdClient, + ct, + ) + if err := server.StartServer(); err != nil { + panic(err) + } +} diff --git a/cmd/copytrader/worker/config.go b/cmd/copytrader/worker/config.go new file mode 100644 index 0000000..7f6a6b7 --- /dev/null +++ b/cmd/copytrader/worker/config.go @@ -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 +} diff --git a/cmd/copytrader/worker/main.go b/cmd/copytrader/worker/main.go new file mode 100644 index 0000000..00482a0 --- /dev/null +++ b/cmd/copytrader/worker/main.go @@ -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 +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)) + } +} diff --git a/go.mod b/go.mod index 56fe04d..35f720b 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/plugin/copytrader/const.go b/plugin/copytrader/const.go new file mode 100644 index 0000000..093aa86 --- /dev/null +++ b/plugin/copytrader/const.go @@ -0,0 +1,6 @@ +package copytrader + +const ( + UniswapV2RouterAddress = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D" + SwapExactTokensForTokens = "38ed1739" +) diff --git a/plugin/copytrader/copytrader.go b/plugin/copytrader/copytrader.go new file mode 100644 index 0000000..c767cc2 --- /dev/null +++ b/plugin/copytrader/copytrader.go @@ -0,0 +1,77 @@ +package copytrader + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/ethclient" + "github.com/hibiken/asynq" + "github.com/sirupsen/logrus" + "github.com/vultisig/recipes/sdk/evm" + "github.com/vultisig/verifier/common" + "github.com/vultisig/verifier/plugin" + "github.com/vultisig/verifier/tx_indexer" + "github.com/vultisig/verifier/vault" + + "github.com/vultisig/plugin/internal/keysign" + "github.com/vultisig/plugin/storage" +) + +var _ plugin.Plugin = (*Plugin)(nil) + +type Plugin struct { + db storage.DatabaseStorage + signer *keysign.Signer + eth *evm.SDK + ethRpc *ethclient.Client + logger logrus.FieldLogger + txIndexerService *tx_indexer.Service + client *asynq.Client + vaultStorage vault.Storage + vaultEncryptionSecret string + blockID uint64 +} + +func NewPlugin( + db storage.DatabaseStorage, + signer *keysign.Signer, + vaultStorage vault.Storage, + ethRpc *ethclient.Client, + txIndexerService *tx_indexer.Service, + client *asynq.Client, + vaultEncryptionSecret string, +) (*Plugin, error) { + if db == nil { + return nil, fmt.Errorf("database storage cannot be nil") + } + + var ( + eth *evm.SDK + currentBlock uint64 + ) + if ethRpc != nil { + ethEvmChainID, err := common.Ethereum.EvmID() + if err != nil { + return nil, fmt.Errorf("failed to get Ethereum EVM ID: %w", err) + } + eth = evm.NewSDK(ethEvmChainID, ethRpc, ethRpc.Client()) + + currentBlock, err = ethRpc.BlockNumber(context.Background()) + if err != nil { + return nil, fmt.Errorf("failed to get block: %w", err) + } + } + + return &Plugin{ + db: db, + signer: signer, + eth: eth, + ethRpc: ethRpc, + logger: logrus.WithField("plugin", "copytrader"), + txIndexerService: txIndexerService, + client: client, + vaultStorage: vaultStorage, + vaultEncryptionSecret: vaultEncryptionSecret, + blockID: currentBlock, + }, nil +} diff --git a/plugin/copytrader/policy.go b/plugin/copytrader/policy.go new file mode 100644 index 0000000..3158da7 --- /dev/null +++ b/plugin/copytrader/policy.go @@ -0,0 +1,117 @@ +package copytrader + +import ( + "fmt" + "strings" + + "github.com/vultisig/recipes/chain" + "github.com/vultisig/recipes/engine" + rtypes "github.com/vultisig/recipes/types" + vtypes "github.com/vultisig/verifier/types" + + "github.com/vultisig/plugin/internal/plugin" +) + +func (p *Plugin) ValidateProposedTransactions(policy vtypes.PluginPolicy, txs []vtypes.PluginKeysignRequest) error { + err := p.ValidatePluginPolicy(policy) + if err != nil { + return fmt.Errorf("failed to validate plugin policy: %v", err) + } + + recipe, err := policy.GetRecipe() + if err != nil { + return fmt.Errorf("failed to get recipe from policy: %v", err) + } + + eng := engine.NewEngine() + + for _, tx := range txs { + for _, keysignMessage := range tx.Messages { + messageChain, err := chain.GetChain(strings.ToLower(keysignMessage.Chain.String())) + if err != nil { + return fmt.Errorf("failed to get chain: %w", err) + } + + decodedTx, err := messageChain.ParseTransaction(keysignMessage.Message) + if err != nil { + return fmt.Errorf("failed to parse transaction: %w", err) + } + + transactionAllowed, _, err := eng.Evaluate(recipe, messageChain, decodedTx) + if err != nil { + return fmt.Errorf("failed to evaluate transaction: %w", err) + } + + if !transactionAllowed { + return fmt.Errorf("transaction %s on %s not allowed by policy", keysignMessage.Hash, keysignMessage.Chain) + } + } + } + + return nil +} + +func (p *Plugin) ValidatePluginPolicy(policyDoc vtypes.PluginPolicy) error { + return plugin.ValidatePluginPolicy(policyDoc, p.GetRecipeSpecification()) +} + +func (p *Plugin) GetRecipeSpecification() *rtypes.RecipeSchema { + return &rtypes.RecipeSchema{ + Version: 1, // Schema version + ScheduleVersion: 1, // Schedule specification version + // TODO: configure + PluginId: string(vtypes.PluginVultisigCopytrader_0000), + PluginName: "Copy trading plugin", + PluginVersion: 1, // Convert from "0.1.0" to int32 + SupportedResources: []*rtypes.ResourcePattern{ + { + ResourcePath: &rtypes.ResourcePath{ + ChainId: "ethereum", + ProtocolId: "uniswapv2_router", + FunctionId: "swapExactTokensForTokens", + Full: "ethereum.uniswapv2_router.swapExactTokensForTokens", + }, + ParameterCapabilities: []*rtypes.ParameterConstraintCapability{ + { + ParameterName: "aim", + SupportedTypes: []rtypes.ConstraintType{ + rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED, + rtypes.ConstraintType_CONSTRAINT_TYPE_WHITELIST, + }, + Required: true, + }, + { + ParameterName: "source_token", + SupportedTypes: []rtypes.ConstraintType{ + rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED, + rtypes.ConstraintType_CONSTRAINT_TYPE_WHITELIST, + }, + Required: true, + }, + { + ParameterName: "destination_token", + SupportedTypes: []rtypes.ConstraintType{ + rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED, + rtypes.ConstraintType_CONSTRAINT_TYPE_WHITELIST, + }, + Required: true, + }, + { + ParameterName: "amount", + SupportedTypes: []rtypes.ConstraintType{ + rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED, + rtypes.ConstraintType_CONSTRAINT_TYPE_MAX, + rtypes.ConstraintType_CONSTRAINT_TYPE_RANGE, + }, + Required: true, + }, + }, + Required: true, + }, + }, + Requirements: &rtypes.PluginRequirements{ + MinVultisigVersion: 1, + SupportedChains: []string{"ethereum"}, + }, + } +} diff --git a/plugin/copytrader/transaction.go b/plugin/copytrader/transaction.go new file mode 100644 index 0000000..94c8802 --- /dev/null +++ b/plugin/copytrader/transaction.go @@ -0,0 +1,97 @@ +package copytrader + +import ( + "context" + "encoding/json" + "fmt" + "time" + + gcommon "github.com/ethereum/go-ethereum/common" + "github.com/hibiken/asynq" + "github.com/sirupsen/logrus" + "github.com/vultisig/mobile-tss-lib/tss" + vcommon "github.com/vultisig/verifier/common" + vtypes "github.com/vultisig/verifier/types" + "github.com/vultisig/vultiserver/contexthelper" +) + +func (p *Plugin) HandleSwapTask(c context.Context, t *asynq.Task) error { + ctx, cancel := context.WithTimeout(c, 5*time.Minute) + defer cancel() + + if err := contexthelper.CheckCancellation(ctx); err != nil { + p.logger.WithError(err).Warn("Context cancelled, skipping trigger") + return err + } + var swapTask SwapTask + if err := json.Unmarshal(t.Payload(), &swapTask); err != nil { + p.logger.WithError(err).Error("Failed to unmarshal swapTask payload") + return fmt.Errorf("failed to unmarshal swapTask payload: %s, %w", err, asynq.SkipRetry) + } + + //TODO: implement aim <-> policy db + //TODO: trigger swaps + return nil +} + +func (p *Plugin) ProposeTransactions(policy vtypes.PluginPolicy) ([]vtypes.PluginKeysignRequest, error) { + //TODO implement me + panic("implement me") +} + +func (p *Plugin) initSign( + ctx context.Context, + req vtypes.PluginKeysignRequest, + pluginPolicy vtypes.PluginPolicy, +) error { + sigs, err := p.signer.Sign(ctx, req) + if err != nil { + p.logger.WithError(err).Error("Keysign failed") + return fmt.Errorf("failed to sign transaction: %w", err) + } + + if len(sigs) != 1 { + p.logger. + WithField("sigs_count", len(sigs)). + Error("expected only 1 message+sig per request for evm") + return fmt.Errorf("failed to sign transaction: invalid signature count: %d", len(sigs)) + } + var sig tss.KeysignResponse + for _, s := range sigs { + sig = s + } + + err = p.SigningComplete(ctx, sig, req, pluginPolicy) + if err != nil { + p.logger.WithError(err).Error("failed to complete signing process (broadcast tx)") + return fmt.Errorf("failed to complete signing process: %w", err) + } + return nil +} + +func (p *Plugin) SigningComplete( + ctx context.Context, + signature tss.KeysignResponse, + signRequest vtypes.PluginKeysignRequest, + _ vtypes.PluginPolicy, +) error { + tx, err := p.eth.Send( + ctx, + gcommon.FromHex(signRequest.Transaction), + gcommon.Hex2Bytes(signature.R), + gcommon.Hex2Bytes(signature.S), + gcommon.Hex2Bytes(signature.RecoveryID), + ) + if err != nil { + p.logger.WithError(err).WithField("tx_hex", signRequest.Transaction).Error("p.eth.Send") + return fmt.Errorf("p.eth.Send(tx_hex=%s): %w", signRequest.Transaction, err) + } + + p.logger.WithFields(logrus.Fields{ + "from_public_key": signRequest.PublicKey, + "to_address": tx.To().Hex(), + "hash": tx.Hash().Hex(), + "chain": vcommon.Ethereum.String(), + }).Info("tx successfully signed and broadcasted") + return nil +} diff --git a/plugin/copytrader/types.go b/plugin/copytrader/types.go new file mode 100644 index 0000000..a97e5fd --- /dev/null +++ b/plugin/copytrader/types.go @@ -0,0 +1,13 @@ +package copytrader + +import ( + "math/big" + + common "github.com/ethereum/go-ethereum/common" +) + +type SwapTask struct { + Sender common.Address + Path []common.Address + Amount *big.Int +} diff --git a/plugin/copytrader/watcher.go b/plugin/copytrader/watcher.go new file mode 100644 index 0000000..4c1dcd7 --- /dev/null +++ b/plugin/copytrader/watcher.go @@ -0,0 +1,95 @@ +package copytrader + +import ( + "context" + "encoding/hex" + "encoding/json" + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/vultisig/recipes/sdk/evm/codegen/uniswapv2_router" +) + +func (p *Plugin) WatchSwap(ctx context.Context) { + var uniswapABI abi.ABI + err := json.Unmarshal([]byte(uniswapv2_router.Uniswapv2RouterMetaData.ABI), &uniswapABI) + if err != nil { + panic(err) + } + + for { + select { + case <-ctx.Done(): + return + case <-time.After(5 * time.Second): + currentBlock, err := p.ethRpc.BlockNumber(ctx) + if err != nil { + p.logger.WithError(err).Error("failed to get block") + continue + } + + for p.blockID < currentBlock { + p.blockID++ + p.logger.Info("Processing block: ", p.blockID) + + block, err := p.ethRpc.BlockByNumber(ctx, big.NewInt(0).SetUint64(p.blockID)) + if err != nil { + p.logger.WithError(err).Error("failed to get block") + continue + } + + // Process txs to find UniswapV2Router interactions + for _, tx := range block.Transactions() { + if tx.To() == nil { + continue + } + // is Uniswap tx check + if tx.To().String() == UniswapV2RouterAddress { + inputBytes := tx.Data() + signature, data := inputBytes[:4], inputBytes[4:] + if hex.EncodeToString(signature) != SwapExactTokensForTokens { + continue + } + + method, err := uniswapABI.MethodById(signature) + if err != nil { + p.logger.WithError(err).Error("unknown method") + continue + } + + // Getting args from tx to find necessary info + var args = make(map[string]interface{}) + err = method.Inputs.UnpackIntoMap(args, data) + if err != nil { + p.logger.WithError(err).Error("failed to unpack data") + continue + } + + path := args["path"] + tokens, valid := path.([]common.Address) + if !valid { + p.logger.Error("invalid path", path) + continue + } + + amountIn, _ := new(big.Int).SetString(fmt.Sprint(args["amountIn"]), 10) + to := args["to"] + sender, valid := to.(common.Address) + if !valid { + p.logger.Error("invalid sender", to) + continue + } + + //Triggering swaps + fmt.Println("sender", sender.String()) + fmt.Println("amount", amountIn.String()) + fmt.Println("path: ", tokens) + } + } + } + } + } +} diff --git a/plugin/payroll/payroll.go b/plugin/payroll/payroll.go index d04cfaf..ba3f894 100644 --- a/plugin/payroll/payroll.go +++ b/plugin/payroll/payroll.go @@ -6,13 +6,14 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/hibiken/asynq" "github.com/sirupsen/logrus" - "github.com/vultisig/plugin/internal/keysign" - "github.com/vultisig/plugin/storage" "github.com/vultisig/recipes/sdk/evm" "github.com/vultisig/verifier/common" "github.com/vultisig/verifier/plugin" "github.com/vultisig/verifier/tx_indexer" "github.com/vultisig/verifier/vault" + + "github.com/vultisig/plugin/internal/keysign" + "github.com/vultisig/plugin/storage" ) var _ plugin.Plugin = (*Plugin)(nil)