Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 2 additions & 114 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,121 +32,9 @@ ap-avs aggregator

Note: The Ava Protocol team currently manages the aggregator, and the communication IP address between the operator and the aggregator is hardcoded in the operator.

### Configurable Fee Rates

The aggregator supports configurable fee rates for workflow execution and monitoring. This allows for dynamic pricing adjustments without code deployments, environment-specific pricing, and A/B testing of pricing models.

#### Configuration Overview

Fee rates are completely **optional** and **backward compatible**. The aggregator will work exactly as before without any configuration changes. When fee rates are configured, they override the hardcoded defaults.

#### Adding Fee Configuration

To configure custom fee rates, add a `fee_rates:` section to your existing aggregator YAML configuration file:

```yaml
# Your existing aggregator config (unchanged)
environment: development
db_path: /tmp/ap-avs/db
ecdsa_private_key: your_private_key_here
# ... other existing config fields ...

# 🆕 Optional fee rates configuration
fee_rates:
# Base fees (one-time per workflow)
base_fee_usd: 0.0

# Monitoring fees (per minute) - cost to monitor triggers
manual_monitoring_fee_usd_per_minute: 0.0 # Manual triggers free
fixed_time_monitoring_fee_usd_per_minute: 0.000017 # ~$0.01/day
cron_monitoring_fee_usd_per_minute: 0.000033 # ~$0.02/day
block_monitoring_fee_usd_per_minute: 0.000033 # ~$0.02/day
event_monitoring_fee_usd_per_minute: 0.000083 # ~$0.05/day base

# Per-execution fees - cost for each trigger activation
manual_execution_fee_usd: 0.0 # Manual executions free
scheduled_execution_fee_usd: 0.005 # $0.005 per scheduled execution
block_execution_fee_usd: 0.01 # $0.01 per block trigger
event_execution_fee_usd: 0.01 # $0.01 per event trigger

# Event monitoring scaling factors
event_address_fee_usd_per_minute: 0.000008 # ~$0.005/day per address
event_topic_fee_usd_per_minute: 0.000003 # ~$0.002/day per topic
```

#### Configuration Files

The aggregator uses configuration files from the `config/` directory:

- **Default**: `./config/aggregator.yaml`
- **Custom**: Specify with `--config` flag

**Available config files:**
- `config/aggregator.yaml` - Default configuration
- `config/aggregator-sepolia.yaml` - Sepolia testnet
- `config/aggregator-base.yaml` - Base network
- `config/aggregator-ethereum.yaml` - Ethereum mainnet

#### Starting with Custom Config

```bash
# Use default config (./config/aggregator.yaml)
./ap-avs aggregator

# Use specific config file
./ap-avs aggregator --config ./config/aggregator-sepolia.yaml
./ap-avs aggregator -c ./path/to/your/config.yaml
```

#### Pricing Strategies

**All fields are optional** - only specify rates you want to override:

**Beta Testing (Free Everything):**
```yaml
fee_rates:
scheduled_execution_fee_usd: 0.0
block_execution_fee_usd: 0.0
event_execution_fee_usd: 0.0
# All monitoring fees default to existing values
```

**Premium Pricing (Selective Overrides):**
```yaml
fee_rates:
# Only override specific rates
block_execution_fee_usd: 0.02 # 2x default rate
event_execution_fee_usd: 0.015 # 1.5x default rate
# All other fees use hardcoded defaults
```

**Environment-Specific Pricing:**
```yaml
# Production - Higher rates
fee_rates:
scheduled_execution_fee_usd: 0.01 # 2x default

# Development - Free rates
fee_rates:
scheduled_execution_fee_usd: 0.0 # Free for development
```
### Fee Estimation

#### Default Values

When no `fee_rates` section is provided, these hardcoded defaults are used:

- **Base Fee**: $0.00 (one-time per workflow)
- **Manual Monitoring**: $0.00/minute (free)
- **Fixed Time Monitoring**: $0.000017/minute (~$0.01/day)
- **Cron Monitoring**: $0.000033/minute (~$0.02/day)
- **Block Monitoring**: $0.000033/minute (~$0.02/day)
- **Event Monitoring**: $0.000083/minute (~$0.05/day base)
- **Manual Execution**: $0.00 (free)
- **Scheduled Execution**: $0.005 per execution
- **Block Execution**: $0.01 per execution
- **Event Execution**: $0.01 per execution
- **Event Address Fee**: $0.000008/minute per monitored address
- **Event Topic Fee**: $0.000003/minute per topic filter
See [docs/FEE_ESTIMATION.md](docs/FEE_ESTIMATION.md) for the fee pricing model, configuration, and implementation details.

#### Benefits

Expand Down
3 changes: 1 addition & 2 deletions aggregator/rpc_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1255,8 +1255,7 @@ func (r *RpcServer) EstimateFees(ctx context.Context, req *avsproto.EstimateFees

r.config.Logger.Info("✅ fee estimation completed successfully",
"user", user.Address.String(),
"final_total_usd", resp.FinalTotal.UsdAmount,
"estimation_method", resp.GasFees.EstimationMethod)
"pricing_model", resp.PricingModel)

return resp, nil
}
Expand Down
31 changes: 10 additions & 21 deletions config/aggregator.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,28 +76,17 @@ approved_operators:
- "0xc6b87cc9e85b07365b6abefff061f237f7cf7dc3" # Operator 2
- "0xa026265a0f01a6e1a19b04655519429df0a57c4e" # Operator 3 (newly added)

# 🆕 CONFIGURABLE FEE RATES
# All fields are optional - only specify ones you want to override
# FEE STRUCTURE: execution_fee + COGS + value_fee
# - execution_fee: flat per-run platform fee
# - COGS: per-node operational costs (gas, external APIs) — estimated automatically
# - value_fee: workflow-level % of tx value, classified by urgency/importance
# All fields optional — unset values use defaults shown below.
fee_rates:
# Base fees (one-time per workflow) - defaults to 0.0
base_fee_usd: 0.0

# Monitoring fees (per minute) - cost to monitor triggers
manual_monitoring_fee_usd_per_minute: 0.0 # Manual triggers free (default)
fixed_time_monitoring_fee_usd_per_minute: 0.000017 # ~$0.01/day (default)
cron_monitoring_fee_usd_per_minute: 0.000033 # ~$0.02/day (default)
block_monitoring_fee_usd_per_minute: 0.000033 # ~$0.02/day (default)
event_monitoring_fee_usd_per_minute: 0.000083 # ~$0.05/day (default)

# Per-execution fees - cost for each trigger activation
manual_execution_fee_usd: 0.0 # Manual executions free (default)
scheduled_execution_fee_usd: 0.005 # $0.005 per scheduled execution (default)
block_execution_fee_usd: 0.01 # $0.01 per block trigger (default)
event_execution_fee_usd: 0.01 # $0.01 per event trigger (default)

# Event monitoring scaling factors - additional costs for complex event monitoring
event_address_fee_usd_per_minute: 0.000008 # ~$0.005/day per address (default)
event_topic_fee_usd_per_minute: 0.000003 # ~$0.002/day per topic (default)
execution_fee_usd: 0.02 # Flat per-execution platform fee ($0.02 default)
tiers:
tier_1: 0.03 # Value-capture group 1 (0.03% of tx value)
tier_2: 0.09 # Value-capture group 2 (0.09% of tx value)
tier_3: 0.18 # Value-capture group 3 (0.18% of tx value)

# Macro variables and secrets - Available globally to ALL workflows at runtime
# Access in workflows via template syntax: {{apContext.configVars.SECRET_NAME}}
Expand Down
142 changes: 38 additions & 104 deletions core/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,27 +115,16 @@ type Config struct {
NotificationsSummary NotificationsSummaryConfig
}

// FeeRatesConfig defines configurable pricing for different trigger types and operations
// FeeRatesConfig defines the fee structure for workflow execution.
// Three components: execution_fee (flat) + COGS (per-node) + value_fee (workflow-level tier %).
type FeeRatesConfig struct {
// Base fees (one-time per workflow)
BaseFeeUSD float64

// Monitoring fees (per minute)
ManualMonitoringFeeUSDPerMinute float64
FixedTimeMonitoringFeeUSDPerMinute float64
CronMonitoringFeeUSDPerMinute float64
BlockMonitoringFeeUSDPerMinute float64
EventMonitoringFeeUSDPerMinute float64

// Per-execution fees
ManualExecutionFeeUSD float64
ScheduledExecutionFeeUSD float64
BlockExecutionFeeUSD float64
EventExecutionFeeUSD float64

// Event monitoring scaling factors
EventAddressFeeUSDPerMinute float64
EventTopicFeeUSDPerMinute float64
// Flat per-execution platform fee (charged every run)
ExecutionFeeUSD float64 // Default: $0.02

// Value-capture tier percentages (% of tx value, workflow-level)
Tier1FeePercentage float64 // Default: 0.03%
Tier2FeePercentage float64 // Default: 0.09%
Tier3FeePercentage float64 // Default: 0.18%
}

// NotificationsSummaryConfig defines optional AI summarization settings for notifications.
Expand Down Expand Up @@ -231,27 +220,15 @@ type ConfigRaw struct {
// Moralis Web3 Data API key for token price lookup (optional)
MoralisApiKey string `yaml:"moralis_api_key"`

// Fee rates configuration for task execution pricing
// Fee structure: execution_fee + COGS + value tiers
// Pointer fields: nil = use default, explicit 0.0 = free tier
FeeRates struct {
// Base fees (one-time per workflow)
BaseFeeUSD float64 `yaml:"base_fee_usd"`

// Monitoring fees (per minute)
ManualMonitoringFeeUSDPerMinute float64 `yaml:"manual_monitoring_fee_usd_per_minute"`
FixedTimeMonitoringFeeUSDPerMinute float64 `yaml:"fixed_time_monitoring_fee_usd_per_minute"`
CronMonitoringFeeUSDPerMinute float64 `yaml:"cron_monitoring_fee_usd_per_minute"`
BlockMonitoringFeeUSDPerMinute float64 `yaml:"block_monitoring_fee_usd_per_minute"`
EventMonitoringFeeUSDPerMinute float64 `yaml:"event_monitoring_fee_usd_per_minute"`

// Per-execution fees
ManualExecutionFeeUSD float64 `yaml:"manual_execution_fee_usd"`
ScheduledExecutionFeeUSD float64 `yaml:"scheduled_execution_fee_usd"`
BlockExecutionFeeUSD float64 `yaml:"block_execution_fee_usd"`
EventExecutionFeeUSD float64 `yaml:"event_execution_fee_usd"`

// Event monitoring scaling factors
EventAddressFeeUSDPerMinute float64 `yaml:"event_address_fee_usd_per_minute"`
EventTopicFeeUSDPerMinute float64 `yaml:"event_topic_fee_usd_per_minute"`
ExecutionFeeUSD *float64 `yaml:"execution_fee_usd"` // $0.02 default
Tiers struct {
Tier1 *float64 `yaml:"tier_1"` // 0.03% default
Tier2 *float64 `yaml:"tier_2"` // 0.09% default
Tier3 *float64 `yaml:"tier_3"` // 0.18% default
} `yaml:"tiers"`
} `yaml:"fee_rates"`

// Notifications configuration block
Expand Down Expand Up @@ -559,81 +536,38 @@ func ReadYamlConfig(path string, o interface{}) error {
return nil
}

// loadFeeRatesFromConfig loads fee rates from configuration with fallback to hardcoded defaults
// This function works completely without YAML configuration - all fields are optional
// The hardcoded defaults match exactly what was previously in getDefaultAutomationRates()
// loadFeeRatesFromConfig loads fee config from YAML with fallback to defaults.
// Pointer fields distinguish "missing" (nil → use default) from explicit zero (0.0 → free tier).
func loadFeeRatesFromConfig(configRates struct {
// Base fees (one-time per workflow)
BaseFeeUSD float64 `yaml:"base_fee_usd"`

// Monitoring fees (per minute)
ManualMonitoringFeeUSDPerMinute float64 `yaml:"manual_monitoring_fee_usd_per_minute"`
FixedTimeMonitoringFeeUSDPerMinute float64 `yaml:"fixed_time_monitoring_fee_usd_per_minute"`
CronMonitoringFeeUSDPerMinute float64 `yaml:"cron_monitoring_fee_usd_per_minute"`
BlockMonitoringFeeUSDPerMinute float64 `yaml:"block_monitoring_fee_usd_per_minute"`
EventMonitoringFeeUSDPerMinute float64 `yaml:"event_monitoring_fee_usd_per_minute"`

// Per-execution fees
ManualExecutionFeeUSD float64 `yaml:"manual_execution_fee_usd"`
ScheduledExecutionFeeUSD float64 `yaml:"scheduled_execution_fee_usd"`
BlockExecutionFeeUSD float64 `yaml:"block_execution_fee_usd"`
EventExecutionFeeUSD float64 `yaml:"event_execution_fee_usd"`

// Event monitoring scaling factors
EventAddressFeeUSDPerMinute float64 `yaml:"event_address_fee_usd_per_minute"`
EventTopicFeeUSDPerMinute float64 `yaml:"event_topic_fee_usd_per_minute"`
ExecutionFeeUSD *float64 `yaml:"execution_fee_usd"`
Tiers struct {
Tier1 *float64 `yaml:"tier_1"`
Tier2 *float64 `yaml:"tier_2"`
Tier3 *float64 `yaml:"tier_3"`
} `yaml:"tiers"`
}) *FeeRatesConfig {
// Helper function to get float64 from config with hardcoded default
// Uses the exact same defaults that were hardcoded in getDefaultAutomationRates()
getFloat64 := func(configValue, hardcodedDefault float64) float64 {
if configValue != 0.0 {
return configValue // Use YAML value if provided
getFloat64 := func(configValue *float64, hardcodedDefault float64) float64 {
if configValue != nil {
return *configValue
}
return hardcodedDefault // Use hardcoded default (same as before)
return hardcodedDefault
}

return &FeeRatesConfig{
// Base fees - exactly as before
BaseFeeUSD: getFloat64(configRates.BaseFeeUSD, 0.0),

// Monitoring fees (per minute) - exactly as before
ManualMonitoringFeeUSDPerMinute: getFloat64(configRates.ManualMonitoringFeeUSDPerMinute, 0.0),
FixedTimeMonitoringFeeUSDPerMinute: getFloat64(configRates.FixedTimeMonitoringFeeUSDPerMinute, 0.000017), // ~$0.01/day
CronMonitoringFeeUSDPerMinute: getFloat64(configRates.CronMonitoringFeeUSDPerMinute, 0.000033), // ~$0.02/day
BlockMonitoringFeeUSDPerMinute: getFloat64(configRates.BlockMonitoringFeeUSDPerMinute, 0.000033), // ~$0.02/day
EventMonitoringFeeUSDPerMinute: getFloat64(configRates.EventMonitoringFeeUSDPerMinute, 0.000083), // ~$0.05/day base

// Per-execution fees - exactly as before
ManualExecutionFeeUSD: getFloat64(configRates.ManualExecutionFeeUSD, 0.0),
ScheduledExecutionFeeUSD: getFloat64(configRates.ScheduledExecutionFeeUSD, 0.005),
BlockExecutionFeeUSD: getFloat64(configRates.BlockExecutionFeeUSD, 0.01),
EventExecutionFeeUSD: getFloat64(configRates.EventExecutionFeeUSD, 0.01),

// Event monitoring scaling factors - exactly as before
EventAddressFeeUSDPerMinute: getFloat64(configRates.EventAddressFeeUSDPerMinute, 0.000008), // ~$0.005/day per address
EventTopicFeeUSDPerMinute: getFloat64(configRates.EventTopicFeeUSDPerMinute, 0.000003), // ~$0.002/day per topic group
ExecutionFeeUSD: getFloat64(configRates.ExecutionFeeUSD, 0.02),
Tier1FeePercentage: getFloat64(configRates.Tiers.Tier1, 0.03),
Tier2FeePercentage: getFloat64(configRates.Tiers.Tier2, 0.09),
Tier3FeePercentage: getFloat64(configRates.Tiers.Tier3, 0.18),
}
}

// GetDefaultFeeRatesConfig returns the default fee rates configuration
// This is useful for tests and as a reference for expected values
// GetDefaultFeeRatesConfig returns the default fee rates
func GetDefaultFeeRatesConfig() *FeeRatesConfig {
return &FeeRatesConfig{
BaseFeeUSD: 0.0,

ManualMonitoringFeeUSDPerMinute: 0.0,
FixedTimeMonitoringFeeUSDPerMinute: 0.000017, // ~$0.01/day
CronMonitoringFeeUSDPerMinute: 0.000033, // ~$0.02/day
BlockMonitoringFeeUSDPerMinute: 0.000033, // ~$0.02/day
EventMonitoringFeeUSDPerMinute: 0.000083, // ~$0.05/day base

ManualExecutionFeeUSD: 0.0,
ScheduledExecutionFeeUSD: 0.005,
BlockExecutionFeeUSD: 0.01,
EventExecutionFeeUSD: 0.01,

EventAddressFeeUSDPerMinute: 0.000008, // ~$0.005/day per address
EventTopicFeeUSDPerMinute: 0.000003, // ~$0.002/day per topic group
ExecutionFeeUSD: 0.02,
Tier1FeePercentage: 0.03,
Tier2FeePercentage: 0.09,
Tier3FeePercentage: 0.18,
}
}

Expand Down
28 changes: 11 additions & 17 deletions core/taskengine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -2911,7 +2911,6 @@ func (n *Engine) SimulateTask(user *model.User, trigger *avsproto.TaskTrigger, n
_, executionError, failedStepCount, resultStatus := vm.AnalyzeExecutionResult()

// Step 11: Calculate total gas cost for the workflow
totalGasCost := vm.CalculateTotalGasCost()

// Create execution result with proper status/error analysis
execution := &avsproto.Execution{
Expand All @@ -2922,12 +2921,9 @@ func (n *Engine) SimulateTask(user *model.User, trigger *avsproto.TaskTrigger, n
Error: executionError, // Comprehensive error message from failed steps
Steps: vm.ExecutionLogs, // Now contains both trigger and node steps (including failed ones)
Index: task.ExecutionCount, // Use current execution count for simulation (0-based)
TotalGasCost: totalGasCost, // Total gas cost for the entire workflow
AutomationFee: &avsproto.FeeAmount{
NativeTokenAmount: "0",
NativeTokenSymbol: "", // Zero fee; symbol not applicable until automation fees are enabled
UsdAmount: "0",
},
ExecutionFee: buildExecutionFee(n.config.FeeRates),
Cogs: buildCOGSFromSteps(vm.ExecutionLogs),
ValueFee: buildValueFee(vm.ExecutionLogs, n.config.FeeRates),
}

// Log execution status based on result type
Expand Down Expand Up @@ -3267,16 +3263,14 @@ func (n *Engine) GetExecution(user *model.User, payload *avsproto.ExecutionReq)

n.logger.Debug("🗂️ Returning pending execution", "task_id", payload.TaskId, "execution_id", payload.ExecutionId, "status", *execStatus, "index", pendingIndex)
return &avsproto.Execution{
Id: payload.ExecutionId,
Status: *execStatus,
StartAt: time.Now().UnixMilli(), // Approximate start time
Steps: []*avsproto.Execution_Step{}, // Empty steps for pending
Index: pendingIndex, // Use pre-assigned or newly assigned index
AutomationFee: &avsproto.FeeAmount{
NativeTokenAmount: "0",
NativeTokenSymbol: "", // Zero fee; symbol not applicable until automation fees are enabled
UsdAmount: "0",
},
Id: payload.ExecutionId,
Status: *execStatus,
StartAt: time.Now().UnixMilli(), // Approximate start time
Steps: []*avsproto.Execution_Step{}, // Empty steps for pending
Index: pendingIndex, // Use pre-assigned or newly assigned index
ExecutionFee: buildExecutionFee(n.config.FeeRates), // Known upfront
Cogs: []*avsproto.NodeCOGS{}, // Unknown until execution completes
ValueFee: nil, // Unknown until execution completes
}, nil
}

Expand Down
Loading
Loading