diff --git a/cmd/payroll/server/config.go b/cmd/payroll/server/config.go index f98d43d..9bdc864 100644 --- a/cmd/payroll/server/config.go +++ b/cmd/payroll/server/config.go @@ -23,6 +23,7 @@ type PayrollServerConfig struct { Host string `mapstructure:"host" json:"host,omitempty"` Port string `mapstructure:"port" json:"port,omitempty"` } `mapstructure:"datadog" json:"datadog"` + EncryptionSecret string `mapstructure:"encryption_secret" json:"encryption_secret,omitempty"` } func GetConfigure() (*PayrollServerConfig, error) { diff --git a/cmd/payroll/server/main.go b/cmd/payroll/server/main.go index b301615..7d5037f 100644 --- a/cmd/payroll/server/main.go +++ b/cmd/payroll/server/main.go @@ -71,7 +71,7 @@ func main() { if err != nil { logger.Fatalf("Failed to connect to database: %v", err) } - p, err := payroll.NewPayrollPlugin(db, cfg.BaseConfigPath, txIndexerService, client, inspector) + p, err := payroll.NewPayrollPlugin(db, vaultStorage, cfg.BaseConfigPath, txIndexerService, client, inspector, cfg.EncryptionSecret) if err != nil { logger.Fatalf("failed to create payroll plugin,err: %s", err) } diff --git a/cmd/payroll/worker/config.go b/cmd/payroll/worker/config.go index d42346b..ace1af4 100644 --- a/cmd/payroll/worker/config.go +++ b/cmd/payroll/worker/config.go @@ -21,7 +21,8 @@ type PayrollWorkerConfig struct { Database struct { DSN string `mapstructure:"dsn" json:"dsn,omitempty"` } `mapstructure:"database" json:"database,omitempty"` - BaseConfigPath string `mapstructure:"base_file_path" json:"base_file_path,omitempty"` + BaseConfigPath string `mapstructure:"base_file_path" json:"base_file_path,omitempty"` + EncryptionSecret string `mapstructure:"encryption_secret" json:"encryption_secret,omitempty"` } func GetConfigure() (*PayrollWorkerConfig, error) { diff --git a/cmd/payroll/worker/main.go b/cmd/payroll/worker/main.go index 9ce2189..e381603 100644 --- a/cmd/payroll/worker/main.go +++ b/cmd/payroll/worker/main.go @@ -81,7 +81,7 @@ func main() { if err != nil { panic(fmt.Errorf("failed to create postgres backend: %w", err)) } - p, err := payroll.NewPayrollPlugin(postgressDB, cfg.BaseConfigPath, txIndexerService, client, inspector) + p, err := payroll.NewPayrollPlugin(postgressDB, vaultStorage, cfg.BaseConfigPath, txIndexerService, client, inspector, cfg.EncryptionSecret) if err != nil { panic(fmt.Errorf("failed to create payroll plugin: %w", err)) } diff --git a/common/vault.go b/common/vault.go new file mode 100644 index 0000000..cf4c8aa --- /dev/null +++ b/common/vault.go @@ -0,0 +1,24 @@ +package common + +import ( + "fmt" + + v1 "github.com/vultisig/commondata/go/vultisig/vault/v1" + vcommon "github.com/vultisig/verifier/common" + vtypes "github.com/vultisig/verifier/types" + vault "github.com/vultisig/verifier/vault" +) + +func GetVaultFromPolicy(s vault.Storage, policy vtypes.PluginPolicy, encryptionSecret string) (*v1.Vault, error) { + vaultFileName := vcommon.GetVaultBackupFilename(policy.PublicKey, policy.PluginID.String()) + vaultContent, err := s.GetVault(vaultFileName) + if err != nil { + return nil, fmt.Errorf("failed to get vault") + } + + if vaultContent == nil { + return nil, fmt.Errorf("vault not found") + } + + return vcommon.DecryptVaultFromBackup(encryptionSecret, vaultContent) +} diff --git a/go.mod b/go.mod index 4860204..1226e8d 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( 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-20250617205725-01cd5bd5b68b + github.com/vultisig/verifier v0.0.0-20250620085341-b935ecc82e40 github.com/vultisig/vultiserver v0.0.0-20250515110921-82d56d3d9cc9 ) diff --git a/go.sum b/go.sum index 22d8a22..5aaaffa 100644 --- a/go.sum +++ b/go.sum @@ -761,6 +761,8 @@ github.com/vultisig/verifier v0.0.0-20250617153835-9a8d53c93da1 h1:iTB/XLdeidd5v github.com/vultisig/verifier v0.0.0-20250617153835-9a8d53c93da1/go.mod h1:35VQIKhChb6/H+3J/XkbGhPkVF6lyLuxsUG/WXHS4+A= github.com/vultisig/verifier v0.0.0-20250617205725-01cd5bd5b68b h1:y02mtgV2eNRrxFBXj/8yzaLK1/j8FNNoTXxbjC4Dec4= 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/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/payroll/payroll.go b/plugin/payroll/payroll.go index 6e00bb8..b4039cc 100644 --- a/plugin/payroll/payroll.go +++ b/plugin/payroll/payroll.go @@ -9,6 +9,7 @@ import ( "github.com/vultisig/plugin/storage" "github.com/vultisig/verifier/plugin" "github.com/vultisig/verifier/tx_indexer" + "github.com/vultisig/verifier/vault" ) var _ plugin.Plugin = (*PayrollPlugin)(nil) @@ -22,14 +23,18 @@ type PayrollPlugin struct { txIndexerService *tx_indexer.Service client *asynq.Client inspector *asynq.Inspector + vaultStorage vault.Storage + encryptionSecret string } func NewPayrollPlugin( db storage.DatabaseStorage, + vaultStorage vault.Storage, baseConfigPath string, txIndexerService *tx_indexer.Service, client *asynq.Client, inspector *asynq.Inspector, + encryptionSecret string, ) (*PayrollPlugin, error) { if db == nil { return nil, fmt.Errorf("database storage cannot be nil") @@ -53,6 +58,8 @@ func NewPayrollPlugin( txIndexerService: txIndexerService, client: client, inspector: inspector, + vaultStorage: vaultStorage, + encryptionSecret: encryptionSecret, }, nil } diff --git a/plugin/payroll/policy.go b/plugin/payroll/policy.go index fdbfe26..9700df0 100644 --- a/plugin/payroll/policy.go +++ b/plugin/payroll/policy.go @@ -2,135 +2,51 @@ package payroll import ( "encoding/base64" - "encoding/hex" "fmt" "math/big" - "strconv" "strings" - "time" - "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/golang/protobuf/proto" + "github.com/vultisig/recipes/chain" + "github.com/vultisig/recipes/engine" rtypes "github.com/vultisig/recipes/types" vtypes "github.com/vultisig/verifier/types" ) -type PayrollPolicy struct { - ChainID []string `json:"chain_id"` - TokenID []string `json:"token_id"` - Recipients []PayrollRecipient `json:"recipients"` - Schedule Schedule `json:"schedule"` -} - -type PayrollRecipient struct { - Address string `json:"address"` - Amount string `json:"amount"` -} - -// 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"` -} - -type ScheduleTyped struct { - Frequency rtypes.ScheduleFrequency - Interval int - StartTime time.Time -} - -func (s Schedule) Typed() (ScheduleTyped, error) { - frequency, ok := rtypes.ScheduleFrequency_value[s.Frequency] - if !ok { - return ScheduleTyped{}, fmt.Errorf("unknown schedule frequency: %s", s.Frequency) - } - - interval, err := strconv.Atoi(s.Interval) - if err != nil { - return ScheduleTyped{}, fmt.Errorf("strconv.Atoi(%s): %w", s.Interval, err) - } - - startTime, err := time.Parse(time.RFC3339, s.StartTime) - if err != nil { - return ScheduleTyped{}, fmt.Errorf("time.Parse(%s): %w", s.StartTime, err) - } - - return ScheduleTyped{ - Frequency: rtypes.ScheduleFrequency(frequency), - Interval: interval, - StartTime: startTime, - }, nil -} - func (p *PayrollPlugin) 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) } - parsedABI, err := abi.JSON(strings.NewReader(erc20ABI)) + recipe, err := policy.GetRecipe() if err != nil { - return fmt.Errorf("failed to parse ABI: %v", err) + return fmt.Errorf("failed to get recipe from policy: %v", err) } - var payrollPolicy PayrollPolicy - // TODO: convert the recipes to PayrollPolicy - for i, tx := range txs { - var parsedTx *gtypes.Transaction - txBytes, err := hex.DecodeString(tx.Transaction) - if err != nil { - return fmt.Errorf("failed to decode transaction: %v", err) - } + eng := engine.NewEngine() - err = rlp.DecodeBytes(txBytes, &parsedTx) - if err != nil { - return fmt.Errorf("failed to parse transaction: %v", err) - } - - txDestination := parsedTx.To() - if txDestination == nil { - return fmt.Errorf("transaction destination is nil") - } - - if strings.ToLower(txDestination.Hex()) != strings.ToLower(payrollPolicy.TokenID[i]) { - // ERC20 transfer tx 'to' is contract address, and 'recipient' encoded in calldata - return fmt.Errorf("transaction destination does not match token ID") - } - - txData := parsedTx.Data() - m, err := parsedABI.MethodById(txData[:4]) - if err != nil { - return fmt.Errorf("failed to get method by ID: %v", err) - } - - v := make(map[string]interface{}) - if err := m.Inputs.UnpackIntoMap(v, txData[4:]); err != nil { - return fmt.Errorf("failed to unpack transaction data: %v", err) - } - - fmt.Printf("Decoded: %+v\n", v) + 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) + } - recipientAddress, ok := v["recipient"].(gcommon.Address) - if !ok { - return fmt.Errorf("failed to get recipient address") - } + decodedTx, err := messageChain.ParseTransaction(keysignMessage.Message) + if err != nil { + return fmt.Errorf("failed to parse transaction: %w", err) + } - var recipientFound bool - for _, recipient := range payrollPolicy.Recipients { - if strings.EqualFold(recipientAddress.Hex(), recipient.Address) { - recipientFound = true - break + transactionAllowed, _, err := eng.Evaluate(recipe, messageChain, decodedTx) + if err != nil { + return fmt.Errorf("failed to evaluate transaction: %w", err) } - } - if !recipientFound { - return fmt.Errorf("recipient not found in policy") + if !transactionAllowed { + return fmt.Errorf("transaction %s on %s not allowed by policy", keysignMessage.Hash, keysignMessage.Chain) + } } } @@ -184,6 +100,7 @@ func (p *PayrollPlugin) validateAmount(pc *rtypes.ParameterConstraint) error { } return nil } + func (p *PayrollPlugin) validateSchedule(schedule *rtypes.Schedule) error { if schedule == nil { return fmt.Errorf("schedule is nil") @@ -202,6 +119,7 @@ func (p *PayrollPlugin) validateSchedule(schedule *rtypes.Schedule) error { return nil } + func (p *PayrollPlugin) checkRule(rule *rtypes.Rule) error { if rule.Effect != rtypes.Effect_EFFECT_ALLOW { return fmt.Errorf("rule effect must be ALLOW, got: %s", rule.Effect) @@ -228,6 +146,7 @@ func (p *PayrollPlugin) checkRule(rule *rtypes.Rule) error { } return nil } + func (p *PayrollPlugin) ValidatePluginPolicy(policyDoc vtypes.PluginPolicy) error { if policyDoc.PluginID != vtypes.PluginVultisigPayroll_0000 { return fmt.Errorf("policy does not match plugin type, expected: %s, got: %s", vtypes.PluginVultisigPayroll_0000, policyDoc.PluginID) diff --git a/plugin/payroll/transaction.go b/plugin/payroll/transaction.go index 77c3645..6ee6f97 100644 --- a/plugin/payroll/transaction.go +++ b/plugin/payroll/transaction.go @@ -13,9 +13,12 @@ import ( "time" "github.com/google/uuid" + "github.com/vultisig/plugin/common" "github.com/vultisig/plugin/internal/scheduler" "github.com/vultisig/plugin/internal/tasks" + "github.com/vultisig/recipes/chain" reth "github.com/vultisig/recipes/ethereum" + rutil "github.com/vultisig/recipes/util" "github.com/vultisig/verifier/tx_indexer/pkg/storage" "github.com/vultisig/vultiserver/contexthelper" "golang.org/x/sync/errgroup" @@ -201,47 +204,75 @@ func (p *PayrollPlugin) ProposeTransactions(policy vtypes.PluginPolicy) ([]vtype return nil, fmt.Errorf("failed to validate plugin policy: %v", err) } - var payrollPolicy PayrollPolicy - // TODO: convert the recipes to PayrollPolicy + vault, err := common.GetVaultFromPolicy(p.vaultStorage, policy, p.encryptionSecret) + if err != nil { + return nil, fmt.Errorf("failed to get vault from policy: %v", err) + } - chain := vcommon.Ethereum + ethAddress, _, _, err := address.GetAddress(vault.PublicKeyEcdsa, vault.HexChainCode, vcommon.Ethereum) + if err != nil { + return nil, fmt.Errorf("failed to get eth address: %v", err) + } - schedule, err := payrollPolicy.Schedule.Typed() + recipe, err := policy.GetRecipe() if err != nil { - p.logger.WithError(err).Error("payrollPolicy.Schedule.Typed") - return nil, fmt.Errorf("payrollPolicy.Schedule.Typed: %w", err) + return nil, fmt.Errorf("failed to get recipe from policy: %v", err) } + echain, err := chain.GetChain("ethereum") + if err != nil { + return nil, fmt.Errorf("failed to get ethereum chain: %v", err) + } + + ethchain := echain.(*reth.Ethereum) + + chain := vcommon.Ethereum + + schedule := recipe.Schedule + var ( mu = &sync.Mutex{} txs = make([]vtypes.PluginKeysignRequest, 0) ) var eg errgroup.Group - for _i, _recipient := range payrollPolicy.Recipients { - i := _i - recipient := _recipient + + for _, rule := range recipe.Rules { + resourcePath, err := rutil.ParseResource(rule.Resource) + if err != nil { + 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) + } + + recipient, amountStr, err := RuleToRecipientAndAmount(rule) + if err != nil { + return nil, fmt.Errorf("failed to get recipient and amount: %v", err) + } eg.Go(func() error { isAlreadyProposed, e := p.IsAlreadyProposed( ctx, schedule.Frequency, - schedule.StartTime, - schedule.Interval, + schedule.StartTime.AsTime(), + int(schedule.Interval), chain, policy.PluginID, policy.ID, - payrollPolicy.TokenID[i], - recipient.Address, + token.Address, + recipient, ) if e != nil { return fmt.Errorf("p.IsAlreadyProposed: %w", e) } if isAlreadyProposed { p.logger.WithFields(logrus.Fields{ - "recipient": recipient.Address, - "amount": recipient.Amount, - "chain_id": payrollPolicy.ChainID[i], - "token_id": payrollPolicy.TokenID[i], + "recipient": recipient, + "amount": amountStr, + "chain_id": chain, + "token_id": token.Address, }).Info("transaction already proposed, skipping") return nil } @@ -249,10 +280,10 @@ func (p *PayrollPlugin) ProposeTransactions(policy vtypes.PluginPolicy) ([]vtype tx, e := p.genUnsignedTx( ctx, chain, - policy.PublicKey, - payrollPolicy.TokenID[i], - recipient.Amount, - recipient.Address, + ethAddress, + token.Address, + amountStr, + recipient, ) if e != nil { return fmt.Errorf("p.genUnsignedTx: %w", e) @@ -264,9 +295,9 @@ func (p *PayrollPlugin) ProposeTransactions(policy vtypes.PluginPolicy) ([]vtype PluginID: policy.PluginID, PolicyID: policy.ID, ChainID: chain, - TokenID: payrollPolicy.TokenID[i], + TokenID: token.Address, FromPublicKey: policy.PublicKey, - ToPublicKey: recipient.Address, + ToPublicKey: recipient, ProposedTxHex: txHex, }) if e != nil { @@ -367,8 +398,6 @@ func (p *PayrollPlugin) GetRecipeSpecification() rtypes.RecipeSchema { ParameterName: "amount", SupportedTypes: []rtypes.ConstraintType{ rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED, - rtypes.ConstraintType_CONSTRAINT_TYPE_MAX, - rtypes.ConstraintType_CONSTRAINT_TYPE_MAX_PER_PERIOD, }, Required: true, }, @@ -376,7 +405,6 @@ func (p *PayrollPlugin) GetRecipeSpecification() rtypes.RecipeSchema { ParameterName: "token", SupportedTypes: []rtypes.ConstraintType{ rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED, - rtypes.ConstraintType_CONSTRAINT_TYPE_WHITELIST, }, Required: true, }, @@ -403,14 +431,14 @@ func (p *PayrollPlugin) GetRecipeSpecification() rtypes.RecipeSchema { func (p *PayrollPlugin) genUnsignedTx( ctx context.Context, chain vcommon.Chain, - senderPublicKey, tokenID, amount, to string, + senderAddress, tokenID, amount, to string, ) ([]byte, error) { switch chain { case vcommon.Ethereum: tx, err := p.evmMakeUnsignedTransfer( ctx, ethereumEvmChainID, - senderPublicKey, + senderAddress, tokenID, amount, to, @@ -577,7 +605,7 @@ func (p *PayrollPlugin) evmEstimateTx( func (p *PayrollPlugin) evmMakeUnsignedTransfer( ctx context.Context, evmChainID *big.Int, - senderPublicKey, tokenIDStr, amountStr, toStr string, + senderAddress, tokenIDStr, amountStr, toStr string, ) ([]byte, error) { amount, ok := new(big.Int).SetString(amountStr, 10) if !ok { @@ -608,14 +636,11 @@ func (p *PayrollPlugin) evmMakeUnsignedTransfer( data = d } - senderAddressHex, err := address.GetEVMAddress(senderPublicKey) - if err != nil { - return nil, fmt.Errorf("address.GetEVMAddress: %v", err) - } + senderAddressHex := gcommon.HexToAddress(senderAddress) nonce, gasLimit, gasTipCap, maxFeePerGas, accessList, err := p.evmEstimateTx( ctx, - gcommon.HexToAddress(senderAddressHex), + senderAddressHex, tokenID, value, data, @@ -640,3 +665,38 @@ func (p *PayrollPlugin) evmMakeUnsignedTransfer( } return bytes, nil } + +func RuleToRecipientAndAmount(rule *rtypes.Rule) (string, string, error) { + var recipient string + var amountStr string + + if len(rule.ParameterConstraints) == 0 { + return "", "", fmt.Errorf("no parameter constraints found") + } + + if len(rule.ParameterConstraints) > 2 { + return "", "", fmt.Errorf("too many parameter constraints found") + } + + for _, constraint := range rule.ParameterConstraints { + if constraint.ParameterName == "recipient" { + if constraint.Constraint.Type != rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED { + return "", "", fmt.Errorf("recipient constraint is not a fixed value") + } + + fixedValue := constraint.Constraint.GetValue().(*rtypes.Constraint_FixedValue) + recipient = fixedValue.FixedValue + } + + if constraint.ParameterName == "amount" { + if constraint.Constraint.Type != rtypes.ConstraintType_CONSTRAINT_TYPE_FIXED { + return "", "", fmt.Errorf("amount constraint is not a fixed value") + } + + fixedValue := constraint.Constraint.GetValue().(*rtypes.Constraint_FixedValue) + amountStr = fixedValue.FixedValue + } + } + + return recipient, amountStr, nil +} diff --git a/scripts/dev/create_payroll_policy/main.go b/scripts/dev/create_payroll_policy/main.go deleted file mode 100644 index d5af2a5..0000000 --- a/scripts/dev/create_payroll_policy/main.go +++ /dev/null @@ -1,154 +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/payroll" -) - -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.Println("Enter tokenAddress, recipientAddress, amount, and chain ID one by one - enter 'done' when finished") - var done bool - var recipientAddresses []string - var recipientAmounts []string - var chainIDs []string - var tokenContracts []string - for !done { - - fmt.Print("Enter token contract: ") - tokenContract, _ := reader.ReadString('\n') - tokenContract = tokenContract[:len(tokenContract)-1] - - if tokenContract == "done" { - done = true - break - } - - fmt.Print("Enter a recipient address: ") - recipientAddress, _ := reader.ReadString('\n') - recipientAddress = recipientAddress[:len(recipientAddress)-1] - - fmt.Print("Enter the amount for this recipient: ") - recipientAmount, _ := reader.ReadString('\n') - recipientAmount = recipientAmount[:len(recipientAmount)-1] - - fmt.Print("Enter the chain ID for this recipient: ") - chainID, _ := reader.ReadString('\n') - chainID = chainID[:len(chainID)-1] - - recipientAddresses = append(recipientAddresses, recipientAddress) - recipientAmounts = append(recipientAmounts, recipientAmount) - chainIDs = append(chainIDs, chainID) - tokenContracts = append(tokenContracts, tokenContract) - } - - for i, recipient := range recipientAddresses { - fmt.Printf("Recipient %d: %s, Amount: %s\n", i+1, recipient, recipientAmounts[i]) - } - - 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.PluginVultisigPayroll_0000, - PluginVersion: "1.0.0", - PolicyVersion: 1, - Active: true, - Signature: "0x0000000000000000000000000000000000000000000000000000000000000000", - } - - payrollPolicy := payroll.PayrollPolicy{ - ChainID: chainIDs, // Todo : move this elsewhere, or the frontend deals with this? - TokenID: tokenContracts, - Recipients: []payroll.PayrollRecipient{}, - Schedule: payroll.Schedule{ - Frequency: frequency, - StartTime: time.Now().UTC().Add(20 * time.Second).Format(time.RFC3339), - }, - } - - for i, recipient := range recipientAddresses { - payrollPolicy.Recipients = append(payrollPolicy.Recipients, payroll.PayrollRecipient{ - Address: recipient, - Amount: recipientAmounts[i], - }) - } - - policyBytes, err := json.Marshal(payrollPolicy) - if err != nil { - panic(err) - } - - fmt.Println("Payroll policy", string(policyBytes)) - // policy.Policy = policyBytes - - serverHost := fmt.Sprintf("http://%s:%d", "localhost", 8080) - pluginHost := fmt.Sprintf("http://%s:%d", "localhost", 8081) - - fmt.Printf("Creating policy on verifier server: %s\n", serverHost) - reqBytes, err := json.Marshal(policy) - if err != nil { - panic(err) - } - - resp, err := http.Post(fmt.Sprintf("%s/plugin/policy", serverHost), "application/json", bytes.NewBuffer(reqBytes)) - if err != nil { - panic(err) - } - fmt.Printf("Request sent: %d\n", resp.StatusCode) - - 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) -}