diff --git a/cmd/dca/server/config.go b/cmd/dca/server/config.go index 81ea650..a0ca4b8 100644 --- a/cmd/dca/server/config.go +++ b/cmd/dca/server/config.go @@ -16,9 +16,9 @@ type DCAServerConfig struct { 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.BlockStorageConfig `mapstructure:"block_storage" json:"block_storage,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"` diff --git a/cmd/dca/worker/config.go b/cmd/dca/worker/config.go index d504e28..86b2f9d 100644 --- a/cmd/dca/worker/config.go +++ b/cmd/dca/worker/config.go @@ -14,9 +14,9 @@ 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.BlockStorageConfig `mapstructure:"block_storage" json:"block_storage,omitempty"` - VaultServiceConfig vault_config.Config `mapstructure:"vault_service" json:"vault_service,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"` diff --git a/cmd/fees/server/config.go b/cmd/fees/server/config.go index f7ed0c3..7cd9fe5 100644 --- a/cmd/fees/server/config.go +++ b/cmd/fees/server/config.go @@ -16,9 +16,9 @@ type CoreConfig struct { 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.BlockStorageConfig `mapstructure:"block_storage" json:"block_storage,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"` diff --git a/cmd/fees/worker/config.go b/cmd/fees/worker/config.go index 6103d8d..7cbd945 100644 --- a/cmd/fees/worker/config.go +++ b/cmd/fees/worker/config.go @@ -16,10 +16,10 @@ type CoreConfig struct { 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.BlockStorageConfig `mapstructure:"block_storage" json:"block_storage,omitempty"` - VaultServiceConfig vault_config.Config `mapstructure:"vault_service" json:"vault_service,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"` + 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"` diff --git a/cmd/payroll/server/config.go b/cmd/payroll/server/config.go index 9bdc864..7cef1e4 100644 --- a/cmd/payroll/server/config.go +++ b/cmd/payroll/server/config.go @@ -16,9 +16,9 @@ type PayrollServerConfig struct { 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.BlockStorageConfig `mapstructure:"block_storage" json:"block_storage,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"` diff --git a/cmd/payroll/worker/config.go b/cmd/payroll/worker/config.go index ace1af4..f6b9afe 100644 --- a/cmd/payroll/worker/config.go +++ b/cmd/payroll/worker/config.go @@ -11,9 +11,9 @@ import ( ) type PayrollWorkerConfig struct { - Redis storage.RedisConfig `mapstructure:"redis" json:"redis,omitempty"` - BlockStorage vault_config.BlockStorageConfig `mapstructure:"block_storage" json:"block_storage,omitempty"` - VaultServiceConfig vault_config.Config `mapstructure:"vault_service" json:"vault_service,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"` diff --git a/go.mod b/go.mod index 1226e8d..2857dbe 100644 --- a/go.mod +++ b/go.mod @@ -19,8 +19,8 @@ require ( github.com/spf13/viper v1.20.1 github.com/vultisig/commondata v0.0.0-20250430024109-a2492623ef05 github.com/vultisig/mobile-tss-lib v0.0.0-20250316003201-2e7e570a4a74 - github.com/vultisig/recipes v0.0.0-20250609134859-0655e0445c1b - github.com/vultisig/verifier v0.0.0-20250620085341-b935ecc82e40 + github.com/vultisig/recipes v0.0.0-20250627044941-5f6e07d5d22f + github.com/vultisig/verifier v0.0.0-20250627125920-a555ee8da0cb github.com/vultisig/vultiserver v0.0.0-20250515110921-82d56d3d9cc9 ) diff --git a/go.sum b/go.sum index 5aaaffa..689add6 100644 --- a/go.sum +++ b/go.sum @@ -747,6 +747,8 @@ github.com/vultisig/mobile-tss-lib v0.0.0-20250316003201-2e7e570a4a74 h1:goqwk4n github.com/vultisig/mobile-tss-lib v0.0.0-20250316003201-2e7e570a4a74/go.mod h1:nOykk4nOy1L3yXtLSlYvVsgizBnCQ3tR2N5uwGPdvaM= github.com/vultisig/recipes v0.0.0-20250609134859-0655e0445c1b h1:rXhRaf40re1FdUYlYbCTEIO6JqIx8FfLlksksu8LiGE= github.com/vultisig/recipes v0.0.0-20250609134859-0655e0445c1b/go.mod h1:JT1FTsiJ8tY5W065vSsrxvrCSl1amp5o6SsBc1VVcCQ= +github.com/vultisig/recipes v0.0.0-20250627044941-5f6e07d5d22f h1:1OaeEJ1zER9pJ8IFh674C1dpZwWhIiTV5nd729dHMzU= +github.com/vultisig/recipes v0.0.0-20250627044941-5f6e07d5d22f/go.mod h1:9ucuiGHbjFTekXNwUc9M+OQSm38P56Gr0yGNdMdE+i0= github.com/vultisig/verifier v0.0.0-20250612165949-9db8a6828606 h1:DGia8ZQM3izAm+Oo/LM+5yWzENc9MIWtEfFCsCZOgO0= github.com/vultisig/verifier v0.0.0-20250612165949-9db8a6828606/go.mod h1:35VQIKhChb6/H+3J/XkbGhPkVF6lyLuxsUG/WXHS4+A= github.com/vultisig/verifier v0.0.0-20250614014346-e5348c16754b h1:8DFobGSwdD01Zzr8lCWW1UWGHqbd/EpwyrfhEy81xTI= @@ -763,6 +765,10 @@ github.com/vultisig/verifier v0.0.0-20250617205725-01cd5bd5b68b h1:y02mtgV2eNRrx github.com/vultisig/verifier v0.0.0-20250617205725-01cd5bd5b68b/go.mod h1:35VQIKhChb6/H+3J/XkbGhPkVF6lyLuxsUG/WXHS4+A= github.com/vultisig/verifier v0.0.0-20250620085341-b935ecc82e40 h1:SXFcpcq7HGCmveEg2fV+lXSrfVnyc/qxGrE1dwaEInU= github.com/vultisig/verifier v0.0.0-20250620085341-b935ecc82e40/go.mod h1:jAepDQtls7VT2r4lb6J6Asl5xCSwt6mg/b8XBO3uzsA= +github.com/vultisig/verifier v0.0.0-20250626093402-fa1ecf8bd816 h1:hCTbtaqIKBsnRarJxccgcoi+DrpuNFyHspWb3PSWp40= +github.com/vultisig/verifier v0.0.0-20250626093402-fa1ecf8bd816/go.mod h1:dLmQBnF5F0auFkGtCMHOCqkUwdTRL6fWm3H4yfHfsVU= +github.com/vultisig/verifier v0.0.0-20250627125920-a555ee8da0cb h1:x2h4ud358pS5jQ5ZEVAu6Jazc2OzDrLa1E8rtdFUEFg= +github.com/vultisig/verifier v0.0.0-20250627125920-a555ee8da0cb/go.mod h1:dLmQBnF5F0auFkGtCMHOCqkUwdTRL6fWm3H4yfHfsVU= github.com/vultisig/vultiserver v0.0.0-20250515110921-82d56d3d9cc9 h1:pZhGN8q8+gPB1JJjVDC1hDg8qn6Tbj0XBJymgTQ8qQg= github.com/vultisig/vultiserver v0.0.0-20250515110921-82d56d3d9cc9/go.mod h1:HwP2IgW6Mcu/gX8paFuKvfibrGE9UmPgkOFTub6dskM= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= diff --git a/plugin/fees/fees.go b/plugin/fees/fees.go index e8c0981..6c3f192 100644 --- a/plugin/fees/fees.go +++ b/plugin/fees/fees.go @@ -32,9 +32,9 @@ type BaseConfig struct { 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.BlockStorageConfig `mapstructure:"block_storage" json:"block_storage,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"` diff --git a/plugin/payroll/monitoring.go b/plugin/payroll/monitoring.go deleted file mode 100644 index 4b87a24..0000000 --- a/plugin/payroll/monitoring.go +++ /dev/null @@ -1,139 +0,0 @@ -package payroll - -import ( - "context" - "fmt" - "math/big" - "strings" - "time" - - "github.com/ethereum/go-ethereum" - gcommon "github.com/ethereum/go-ethereum/common" - gtypes "github.com/ethereum/go-ethereum/core/types" - - "github.com/vultisig/plugin/internal/types" -) - -func (p *PayrollPlugin) handleBroadcastError(err error, sender gcommon.Address) error { - errMsg := err.Error() - - switch { - case strings.Contains(errMsg, "insufficient funds"): - // this is for ETH balance for gas - immediate failure, what to do? - // goal : retry only when we dectect user send funds - // for now : we can skip this trigger and wait for next one - return &types.TransactionError{ - Code: types.ErrInsufficientFunds, - Message: fmt.Sprintf("Account %s has insufficient gas", sender.Hex()), - Err: err, - } - - case strings.Contains(errMsg, "nonce too low"): - case strings.Contains(errMsg, "nonce too high"): - case strings.Contains(errMsg, "gas price too low"): - case strings.Contains(errMsg, "gas limit reached"): - // these are retriable errors - the caller should retry with updated parameters - // we should not skip this trigger and retry immediately - return &types.TransactionError{ - Code: types.ErrRetriable, - Message: err.Error(), - Err: err, - } - - default: - return &types.TransactionError{ - Code: types.ErrRPCConnectionFailed, - Message: "Unknown RPC error", - Err: err, - } - } - return nil -} - -func (p *PayrollPlugin) monitorTransaction(tx *gtypes.Transaction) error { - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(p.config.Monitoring.TimeoutMinutes)*time.Minute) - defer cancel() - - ticker := time.NewTicker(time.Duration(p.config.Monitoring.CheckIntervalSeconds) * time.Second) - defer ticker.Stop() - - txHash := tx.Hash() - for { - select { - case <-ctx.Done(): - return &types.TransactionError{ - Code: types.ErrTxTimeout, - Message: fmt.Sprintf("Transaction monitoring timed out for tx: %s", txHash.Hex()), - } - - case <-ticker.C: - // check tx status - _, isPending, err := p.rpcClient.TransactionByHash(ctx, txHash) - if err != nil { - if err == ethereum.NotFound { - return &types.TransactionError{ - Code: types.ErrTxDropped, - Message: fmt.Sprintf("Transaction dropped from mempool: %s", txHash.Hex()), - } - } - continue // keep trying on other RPC errors - } - - if !isPending { - receipt, err := p.rpcClient.TransactionReceipt(ctx, txHash) - if err != nil { - continue - } - - if receipt.Status == 0 { - reason := p.getRevertReason(ctx, tx, receipt.BlockNumber) - - // Check if it's a permanent failure (like insufficient token balance) - if !p.isRetriableError(reason) { - return &types.TransactionError{ - Code: types.ErrPermanentFailure, - Message: fmt.Sprintf("Transaction permanently failed: %s", reason), - } - } - - // It's a retriable error - return &types.TransactionError{ - Code: types.ErrRetriable, - Message: fmt.Sprintf("Transaction failed with retriable error: %s", reason), - } - } - - // Transaction successful - return nil - } - } - } -} - -func (p *PayrollPlugin) isRetriableError(reason string) bool { - // implement logic to determine if the error is retriable based on the reason - return strings.Contains(reason, "insufficient funds") || strings.Contains(reason, "nonce too low") || strings.Contains(reason, "nonce too high") || strings.Contains(reason, "gas price too low") || strings.Contains(reason, "gas limit reached") -} - -func (p *PayrollPlugin) getRevertReason(ctx context.Context, tx *gtypes.Transaction, blockNum *big.Int) string { - callMsg := ethereum.CallMsg{ - To: tx.To(), - Data: tx.Data(), - Gas: tx.Gas(), - GasPrice: tx.GasPrice(), - Value: tx.Value(), - } - - _, err := p.rpcClient.CallContract(ctx, callMsg, blockNum) - if err != nil { - // try to parse standard revert reason - if strings.Contains(err.Error(), "execution reverted:") { - parts := strings.Split(err.Error(), "execution reverted:") - if len(parts) > 1 { - return strings.TrimSpace(parts[1]) - } - } - return err.Error() - } - return "Unknown revert reason" -} diff --git a/plugin/payroll/payroll.go b/plugin/payroll/payroll.go index b4039cc..81868ec 100644 --- a/plugin/payroll/payroll.go +++ b/plugin/payroll/payroll.go @@ -7,6 +7,8 @@ import ( "github.com/hibiken/asynq" "github.com/sirupsen/logrus" "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" @@ -16,8 +18,7 @@ var _ plugin.Plugin = (*PayrollPlugin)(nil) type PayrollPlugin struct { db storage.DatabaseStorage - nonceManager *NonceManager - rpcClient *ethclient.Client + eth *evm.SDK logger logrus.FieldLogger config *PluginConfig txIndexerService *tx_indexer.Service @@ -49,10 +50,14 @@ func NewPayrollPlugin( return nil, err } + ethEvmChainID, err := common.Ethereum.EvmID() + if err != nil { + return nil, fmt.Errorf("common.Ethereum.EvmID: %w", err) + } + return &PayrollPlugin{ db: db, - rpcClient: rpcClient, - nonceManager: NewNonceManager(rpcClient), + eth: evm.NewSDK(ethEvmChainID, rpcClient, rpcClient.Client()), logger: logrus.WithField("plugin", "payroll"), config: cfg, txIndexerService: txIndexerService, @@ -62,7 +67,3 @@ func NewPayrollPlugin( encryptionSecret: encryptionSecret, }, nil } - -func (p *PayrollPlugin) GetNextNonce(address string) (uint64, error) { - return p.nonceManager.GetNextNonce(address) -} diff --git a/plugin/payroll/transaction.go b/plugin/payroll/transaction.go index 6ee6f97..1c0882d 100644 --- a/plugin/payroll/transaction.go +++ b/plugin/payroll/transaction.go @@ -2,12 +2,10 @@ package payroll import ( "context" - "encoding/hex" "encoding/json" "errors" "fmt" "math/big" - "strconv" "strings" "sync" "time" @@ -18,16 +16,13 @@ import ( "github.com/vultisig/plugin/internal/tasks" "github.com/vultisig/recipes/chain" reth "github.com/vultisig/recipes/ethereum" + "github.com/vultisig/recipes/sdk/evm" rutil "github.com/vultisig/recipes/util" "github.com/vultisig/verifier/tx_indexer/pkg/storage" "github.com/vultisig/vultiserver/contexthelper" "golang.org/x/sync/errgroup" - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" gcommon "github.com/ethereum/go-ethereum/common" - gtypes "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/rlp" "github.com/hibiken/asynq" "github.com/sirupsen/logrus" "github.com/vultisig/mobile-tss-lib/tss" @@ -44,11 +39,6 @@ const ( hexEncryptionKey = "hexencryptionkey" ) -var ethereumEvmChainID = big.NewInt(1) - -// consider as native evm chain asset — ETH,BNB,ARB etc -var evmZeroAddress = gcommon.HexToAddress("0x0000000000000000000000000000000000000000") - func (p *PayrollPlugin) HandleSchedulerTrigger(c context.Context, t *asynq.Task) error { ctx, cancel := context.WithTimeout(c, 5*time.Minute) defer cancel() @@ -242,9 +232,15 @@ func (p *PayrollPlugin) ProposeTransactions(policy vtypes.PluginPolicy) ([]vtype return nil, fmt.Errorf("failed to parse resource: %v", err) } - token, found := ethchain.GetToken(resourcePath.ProtocolId) - if !found { - return nil, fmt.Errorf("failed to get token: %v", resourcePath.ProtocolId) + var tokenID string + if strings.ToLower(resourcePath.ProtocolId) == "eth" { + tokenID = evm.ZeroAddress.Hex() + } else { + token, found := ethchain.GetToken(resourcePath.ProtocolId) + if !found { + return nil, fmt.Errorf("failed to get token: %v", resourcePath.ProtocolId) + } + tokenID = token.Address } recipient, amountStr, err := RuleToRecipientAndAmount(rule) @@ -261,7 +257,7 @@ func (p *PayrollPlugin) ProposeTransactions(policy vtypes.PluginPolicy) ([]vtype chain, policy.PluginID, policy.ID, - token.Address, + tokenID, recipient, ) if e != nil { @@ -272,7 +268,7 @@ func (p *PayrollPlugin) ProposeTransactions(policy vtypes.PluginPolicy) ([]vtype "recipient": recipient, "amount": amountStr, "chain_id": chain, - "token_id": token.Address, + "token_id": tokenID, }).Info("transaction already proposed, skipping") return nil } @@ -281,7 +277,7 @@ func (p *PayrollPlugin) ProposeTransactions(policy vtypes.PluginPolicy) ([]vtype ctx, chain, ethAddress, - token.Address, + tokenID, amountStr, recipient, ) @@ -289,13 +285,13 @@ func (p *PayrollPlugin) ProposeTransactions(policy vtypes.PluginPolicy) ([]vtype return fmt.Errorf("p.genUnsignedTx: %w", e) } - txHex := hex.EncodeToString(tx) + txHex := gcommon.Bytes2Hex(tx) txToTrack, e := p.txIndexerService.CreateTx(ctx, storage.CreateTxDto{ PluginID: policy.PluginID, PolicyID: policy.ID, ChainID: chain, - TokenID: token.Address, + TokenID: tokenID, FromPublicKey: policy.PublicKey, ToPublicKey: recipient, ProposedTxHex: txHex, @@ -348,22 +344,16 @@ func (p *PayrollPlugin) SigningComplete( signRequest vtypes.PluginKeysignRequest, _ vtypes.PluginPolicy, ) error { - tx, err := evmAppendSignature(ethereumEvmChainID, gcommon.FromHex(signRequest.Transaction), signature) - if err != nil { - p.logger.WithError(err).Error("evmAppendSignature") - return fmt.Errorf("evmAppendSignature: %w", err) - } - - sender, err := address.GetEVMAddress(signRequest.PublicKey) - if err != nil { - p.logger.WithError(err).Error("address.GetEVMAddress") - return fmt.Errorf("address.GetEVMAddress: %w", err) - } - - err = p.rpcClient.SendTransaction(ctx, tx) + 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).Error("Failed to broadcast transaction") - return p.handleBroadcastError(err, gcommon.HexToAddress(sender)) + 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.WithField("hash", tx.Hash().Hex()).Info("transaction successfully broadcasted") @@ -435,16 +425,20 @@ func (p *PayrollPlugin) genUnsignedTx( ) ([]byte, error) { switch chain { case vcommon.Ethereum: - tx, err := p.evmMakeUnsignedTransfer( + amt, ok := new(big.Int).SetString(amount, 10) + if !ok { + return nil, fmt.Errorf("failed to parse amount: %s", amount) + } + + tx, err := p.eth.MakeAnyTransfer( ctx, - ethereumEvmChainID, - senderAddress, - tokenID, - amount, - to, + gcommon.HexToAddress(senderAddress), + gcommon.HexToAddress(to), + gcommon.HexToAddress(tokenID), + amt, ) if err != nil { - return nil, fmt.Errorf("p.evmMakeUnsignedTransfer: %v", err) + return nil, fmt.Errorf("p.eth.MakeAnyTransfer: %v", err) } return tx, nil default: @@ -452,220 +446,6 @@ func (p *PayrollPlugin) genUnsignedTx( return nil, fmt.Errorf("unsupported chain: %s", chain) } -func evmEncodeUnsignedDynamicFeeTx( - evmChainID *big.Int, - nonce uint64, - to gcommon.Address, - maxPriorityFeePerGas, maxFeePerGas *big.Int, - gas uint64, - value *big.Int, - data []byte, - accessList gtypes.AccessList, -) ([]byte, error) { - bytes, err := rlp.EncodeToBytes(reth.DynamicFeeTxWithoutSignature{ - ChainID: evmChainID, - Nonce: nonce, - GasTipCap: maxPriorityFeePerGas, - GasFeeCap: maxFeePerGas, - Gas: gas, - To: &to, - Value: value, - Data: data, - AccessList: accessList, - }) - if err != nil { - return nil, fmt.Errorf("rlp.EncodeToBytes: %v", err) - } - - res := append([]byte{gtypes.DynamicFeeTxType}, bytes...) - return res, nil -} - -func evmAppendSignature( - evmChainID *big.Int, - unsignedTx []byte, - tssSig tss.KeysignResponse, -) (*gtypes.Transaction, error) { - txData, err := reth.DecodeUnsignedPayload(unsignedTx) - if err != nil { - return nil, fmt.Errorf("reth.DecodeUnsignedPayload: %v", err) - } - - var sig []byte - sig = append(sig, gcommon.FromHex(tssSig.R)...) - sig = append(sig, gcommon.FromHex(tssSig.S)...) - sig = append(sig, gcommon.FromHex(tssSig.RecoveryID)...) - - tx, err := gtypes.NewTx(txData).WithSignature(gtypes.NewPragueSigner(evmChainID), sig) - if err != nil { - return nil, fmt.Errorf("gtypes.NewTx(txData).WithSignature: %v", err) - } - return tx, nil -} - -func (p *PayrollPlugin) evmEstimateTx( - ctx context.Context, - from, to gcommon.Address, - value *big.Int, - data []byte, -) (uint64, uint64, *big.Int, *big.Int, gtypes.AccessList, error) { - var eg errgroup.Group - var gasLimit uint64 - eg.Go(func() error { - r, e := p.rpcClient.EstimateGas(ctx, ethereum.CallMsg{ - From: from, - To: &to, - Data: data, - Value: value, - }) - if e != nil { - return fmt.Errorf("p.rpcClient.EstimateGas: %v", e) - } - gasLimit = r - return nil - }) - - var gasTipCap *big.Int - eg.Go(func() error { - r, e := p.rpcClient.SuggestGasTipCap(ctx) - if e != nil { - return fmt.Errorf("p.rpcClient.SuggestGasTipCap: %v", e) - } - gasTipCap = r - return nil - }) - - var baseFee *big.Int - eg.Go(func() error { - feeHistory, e := p.rpcClient.FeeHistory(ctx, 1, nil, nil) - if e != nil { - return fmt.Errorf("p.rpcClient.FeeHistory: %v", e) - } - if len(feeHistory.BaseFee) == 0 { - return fmt.Errorf("feeHistory.BaseFee is empty") - } - baseFee = feeHistory.BaseFee[0] - return nil - }) - - var nonce uint64 - eg.Go(func() error { - r, e := p.nonceManager.GetNextNonce(from.Hex()) - if e != nil { - return fmt.Errorf("p.nonceManager.GetNextNonce: %v", e) - } - nonce = r - return nil - }) - err := eg.Wait() - if err != nil { - return 0, 0, nil, nil, nil, fmt.Errorf("eg.Wait: %v", err) - } - - maxFeePerGas := new(big.Int).Add(gasTipCap, baseFee) - - type createAccessListArgs struct { - From string `json:"from,omitempty"` - To string `json:"to,omitempty"` - Gas string `json:"gas,omitempty"` - GasPrice string `json:"gasPrice,omitempty"` - MaxPriorityFeePerGas string `json:"maxPriorityFeePerGas,omitempty"` - MaxFeePerGas string `json:"maxFeePerGas,omitempty"` - Value string `json:"value,omitempty"` - Data string `json:"data,omitempty"` - } - createAccessListRes := struct { - AccessList gtypes.AccessList `json:"accessList"` - GasUsed string `json:"gasUsed"` - }{} - err = p.rpcClient.Client().CallContext( - ctx, - &createAccessListRes, - "eth_createAccessList", - []interface{}{ - createAccessListArgs{ - From: from.Hex(), - To: to.Hex(), - Gas: "0x" + strconv.FormatUint(gasLimit, 16), - MaxPriorityFeePerGas: "0x" + gcommon.Bytes2Hex(gasTipCap.Bytes()), - MaxFeePerGas: "0x" + gcommon.Bytes2Hex(maxFeePerGas.Bytes()), - Value: "0x" + gcommon.Bytes2Hex(value.Bytes()), - Data: "0x" + gcommon.Bytes2Hex(data), - }, - "latest", - }, - ) - if err != nil { - return 0, 0, nil, nil, nil, fmt.Errorf("p.rpcClient.Client().CallContext: %v", err) - } - - return nonce, gasLimit, gasTipCap, maxFeePerGas, createAccessListRes.AccessList, nil -} - -func (p *PayrollPlugin) evmMakeUnsignedTransfer( - ctx context.Context, - evmChainID *big.Int, - senderAddress, tokenIDStr, amountStr, toStr string, -) ([]byte, error) { - amount, ok := new(big.Int).SetString(amountStr, 10) - if !ok { - return nil, fmt.Errorf("new(big.Int).SetString: %s", amountStr) - } - - to := gcommon.HexToAddress(toStr) - tokenID := gcommon.HexToAddress(tokenIDStr) - - var ( - value *big.Int - data []byte - ) - if tokenID == evmZeroAddress { - value = amount - data = nil - } else { - parsedABI, err := abi.JSON(strings.NewReader(erc20ABI)) - if err != nil { - return nil, fmt.Errorf("abi.JSON(strings.NewReader(erc20ABI)): %v", err) - } - - d, err := parsedABI.Pack("transfer", to, amount) - if err != nil { - return nil, fmt.Errorf("parsedABI.Pack: %v", err) - } - value = big.NewInt(0) - data = d - } - - senderAddressHex := gcommon.HexToAddress(senderAddress) - - nonce, gasLimit, gasTipCap, maxFeePerGas, accessList, err := p.evmEstimateTx( - ctx, - senderAddressHex, - tokenID, - value, - data, - ) - if err != nil { - return nil, fmt.Errorf("p.evmEstimateTx: %v", err) - } - - bytes, err := evmEncodeUnsignedDynamicFeeTx( - evmChainID, - nonce, - tokenID, - gasTipCap, - maxFeePerGas, - gasLimit, - value, - data, - accessList, - ) - if err != nil { - return nil, fmt.Errorf("evmEncodeUnsignedDynamicFeeTx: %v", err) - } - return bytes, nil -} - func RuleToRecipientAndAmount(rule *rtypes.Rule) (string, string, error) { var recipient string var amountStr string diff --git a/scripts/dev/create_fee_policy/dummy_server.go b/scripts/dev/create_fee_policy/dummy_server.go index 80b5646..c2ad21a 100644 --- a/scripts/dev/create_fee_policy/dummy_server.go +++ b/scripts/dev/create_fee_policy/dummy_server.go @@ -24,9 +24,9 @@ type FeesServerConfig struct { 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.BlockStorageConfig `mapstructure:"block_storage" json:"block_storage,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"`