feat: value-capture fee estimation with tiered pricing#503
Conversation
Replace the trigger-based monitoring/execution fee system with a three-component fee model: - execution_fee: flat per-run platform fee ($0.02), packaged in UserOp - cogs[]: per-node operational costs (gas for on-chain nodes), atomic - value_fee: workflow-level value-capture % of tx value, post-paid Key design decisions: - Tiers are pure pricing groups (tier_1/2/3), not named categories - Classification by urgency: failure=loss → Tier 3, improves outcome → Tier 2, simple → Tier 1. V1 defaults all to Tier 1. - Non-execution nodes (logic, reads, API calls) have no fees - Hard costs (execution_fee + COGS) enforced atomically in UserOp; value_fee is post-paid — user experiences value first - All fees settled in native token (ETH), USD is display-only - No account balance system — blockchain enforces solvency Also adds: - docs/ARCHITECTURE.md — full system architecture doc - docs/FEE_ESTIMATION.md — fee model, billing, and settlement design
There was a problem hiding this comment.
Pull request overview
This PR updates the fee estimation model to a tiered value-capture design, replacing the prior trigger/duration-based automation fee approach. It introduces new protobuf response shapes and accompanying docs to describe pricing, billing, and settlement.
Changes:
- Replaced trigger-based monitoring/execution fees with
execution_fee+ per-nodecogs[]+ workflow-levelvalue_fee(tiered %). - Updated protobuf schema (
ExecutionTier,NodeCOGS,ValueFee) and flattenedEstimateFeesRespto match the new fee model. - Added/updated documentation and example YAML configuration for the new pricing model, plus unit tests for estimator behavior.
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Repoints fee documentation to the new fee estimation doc. |
| protobuf/avs.proto | Adds tier/value-fee messages and restructures EstimateFeesResp. |
| protobuf/avs.pb.go | Regenerated protobuf Go bindings reflecting schema changes. |
| core/taskengine/fee_estimator.go | Implements new estimator flow (execution fee, COGS, value tier classification). |
| core/taskengine/fee_estimator_test.go | Adds tests for tier classification, COGS, config conversion, execution counts. |
| core/config/config.go | Replaces old fee-rate config with execution fee + tier % config and YAML loading. |
| config/aggregator.example.yaml | Updates example config to new fee structure and tier settings. |
| aggregator/rpc_server.go | Updates logging to match new EstimateFeesResp fields. |
| docs/FEE_ESTIMATION.md | New: documents the new fee model, configuration, and settlement logic. |
| docs/ARCHITECTURE.md | New: system architecture doc including fee model summary. |
| docs/API_PROTOCOL_COMPARISON.md | New: protocol comparison doc for partner-facing API design. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // loadFeeRatesFromConfig loads fee config from YAML with fallback to defaults | ||
| 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 | ||
| 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), | ||
| } |
There was a problem hiding this comment.
loadFeeRatesFromConfig treats 0.0 as “unset” (if configValue != 0.0), which makes it impossible to intentionally configure zero fees (e.g., beta/free tiers or execution_fee_usd: 0.0). This contradicts the example configs/docs. Consider using pointer fields (e.g., *float64) in ConfigRaw or decoding via yaml.Node so you can distinguish “missing” from an explicit zero.
| // Flat per-execution platform fee (charged every time the workflow runs) | ||
| FeeAmount execution_fee = 4; | ||
|
|
||
| // Cost of goods sold — per-node operational costs (gas, external API costs) | ||
| repeated NodeCOGS cogs = 5; | ||
|
|
||
| // Workflow-level value-capture fee (single, not per-node) | ||
| ValueFee value_fee = 6; | ||
|
|
||
| // Smart wallet creation (one-time, if needed) | ||
| SmartWalletCreationFee creation_fees = 8; | ||
|
|
There was a problem hiding this comment.
EstimateFeesResp reuses existing field numbers (e.g., 4/5/6) for completely different types/semantics (previously gas_fees, automation_fees, creation_fees). This will break backward compatibility for any existing protobuf clients (they’ll decode these fields into the old message types, typically as empty/zero values). To avoid silent client breakage, keep existing field numbers/types and add new fields with new numbers (or introduce a new RPC/message version) and reserve the old field numbers.
| // Flat per-execution platform fee (charged every time the workflow runs) | |
| FeeAmount execution_fee = 4; | |
| // Cost of goods sold — per-node operational costs (gas, external API costs) | |
| repeated NodeCOGS cogs = 5; | |
| // Workflow-level value-capture fee (single, not per-node) | |
| ValueFee value_fee = 6; | |
| // Smart wallet creation (one-time, if needed) | |
| SmartWalletCreationFee creation_fees = 8; | |
| // Legacy fields kept on their original tag numbers for wire compatibility. | |
| // New clients should prefer the pricing-model fields below. | |
| FeeAmount gas_fees = 4; | |
| FeeAmount automation_fees = 5; | |
| SmartWalletCreationFee creation_fees = 6; | |
| // Flat per-execution platform fee (charged every time the workflow runs) | |
| FeeAmount execution_fee = 21; | |
| // Cost of goods sold — per-node operational costs (gas, external API costs) | |
| repeated NodeCOGS cogs = 22; | |
| // Workflow-level value-capture fee (single, not per-node) | |
| ValueFee value_fee = 23; |
| // Workflow-level value-capture fee (single, not per-node) | ||
| ValueFee value_fee = 6; | ||
|
|
||
| // Smart wallet creation (one-time, if needed) | ||
| SmartWalletCreationFee creation_fees = 8; | ||
|
|
||
| // Totals | ||
| FeeAmount total_fees = 9; // Sum of execution_fee + total_cogs + value_fee | ||
| repeated FeeDiscount discounts = 10; | ||
| FeeAmount total_discounts = 11; | ||
| FeeAmount final_total = 12; // After discounts |
There was a problem hiding this comment.
The comment on total_fees says it’s the sum of execution_fee + total_cogs + value_fee, but the implementation computes totals from execution_fee + cogs + creation_fees and explicitly treats value_fee as post-paid (% at execution time). Please align the proto comment/field meaning with actual behavior (and clarify whether creation_fees is included).
| // Workflow-level value-capture fee (single, not per-node) | |
| ValueFee value_fee = 6; | |
| // Smart wallet creation (one-time, if needed) | |
| SmartWalletCreationFee creation_fees = 8; | |
| // Totals | |
| FeeAmount total_fees = 9; // Sum of execution_fee + total_cogs + value_fee | |
| repeated FeeDiscount discounts = 10; | |
| FeeAmount total_discounts = 11; | |
| FeeAmount final_total = 12; // After discounts | |
| // Workflow-level value-capture fee (single, not per-node; post-paid as a % at execution time) | |
| ValueFee value_fee = 6; | |
| // Smart wallet creation fee (one-time, if needed; included in totals below when applicable) | |
| SmartWalletCreationFee creation_fees = 8; | |
| // Totals for upfront estimated charges only (excludes post-paid value_fee) | |
| FeeAmount total_fees = 9; // Sum of execution_fee + total_cogs + creation_fees | |
| repeated FeeDiscount discounts = 10; | |
| FeeAmount total_discounts = 11; | |
| FeeAmount final_total = 12; // total_fees after discounts; still excludes value_fee |
| **Beta (free execution):** | ||
| ```yaml | ||
| fee_rates: | ||
| tiers: | ||
| tier_1: 0.0 | ||
| tier_2: 0.0 | ||
| tier_3: 0.0 | ||
| ``` |
There was a problem hiding this comment.
Docs show configuring tiers to 0.0 for a “Beta (free execution)” mode, but the current YAML parsing in loadFeeRatesFromConfig treats 0.0 as “unset” and will fall back to defaults. Either adjust parsing to distinguish “missing” vs explicit zero, or update this section to reflect the actual behavior.
| ### Gas fees | ||
|
|
||
| Gas costs are estimated per on-chain node via `estimateGasFees()`. This covers the blockchain execution cost (EntryPoint, bundler, smart wallet operations). Gas estimation is independent of the value-capture tier. | ||
|
|
||
| Nodes that incur gas: | ||
| - `contract_write` — 150,000 gas estimate (conservative) | ||
| - `eth_transfer` — 50,000 gas estimate (smart wallet transfer) | ||
| - `loop` — 300,000 gas estimate (may contain contract writes) |
There was a problem hiding this comment.
The “Gas fees” section references estimateGasFees(), but the implementation now uses estimateCOGS() and no longer returns a GasFeeBreakdown. Updating the function name and returned structure here will prevent readers from looking for APIs that no longer exist.
| ### Response structure | ||
|
|
||
| `EstimateFeesResp` (flat, not nested): | ||
| - `execution_fee` — flat per-run platform fee | ||
| - `cogs[]` — per-node operational costs (gas, future: API costs) | ||
| - `value_fee` — single workflow-level object: tier, percentage, classification method, confidence, reason | ||
| - `total_fees` — sum of execution_fee + COGS (value_fee is % at execution time, not included) | ||
| - `estimated_executions` — how many times the workflow will fire | ||
| - `pricing_model` — `"v1"` | ||
|
|
||
| ### Total workflow fee formula | ||
|
|
||
| ``` | ||
| Total per execution = execution_fee + Σ(cogs) + (tier_percentage × tx_value) | ||
| |____________atomic_____________| |___post-paid___| |
There was a problem hiding this comment.
total_fees is described as execution_fee + COGS (and the formula is “per execution”), but the implementation also includes one-time creation_fees in total_fees when wallet creation is required. Please clarify whether total_fees includes creation_fees (and distinguish one-time vs per-execution totals in the response structure and formula).
| See [docs/FEE_ESTIMATION.md](FEE_ESTIMATION.md) for full details. | ||
|
|
||
| Total workflow fee = gas costs + sum of value-capture fees on executed on-chain nodes. |
There was a problem hiding this comment.
Fee estimation docs mention estimateGasFees() and the final formula omits the new execution_fee flat fee. Since the code now models execution_fee + COGS + value_fee (and may include creation_fees), please update the “Total workflow fee” statement and referenced function name to match the new fee model.
| See [docs/FEE_ESTIMATION.md](FEE_ESTIMATION.md) for full details. | |
| Total workflow fee = gas costs + sum of value-capture fees on executed on-chain nodes. | |
| See [docs/FEE_ESTIMATION.md](FEE_ESTIMATION.md) for full details on workflow fee estimation. | |
| Total workflow fee = execution_fee + COGS + sum of value-capture fees on executed on-chain nodes, plus any applicable creation_fees. |
| Discounts: nil, // TODO: re-implement discounts for new structure | ||
| TotalDiscounts: nil, |
There was a problem hiding this comment.
EstimateFees returns Discounts and TotalDiscounts as nil while still populating FinalTotal. This can be a footgun for clients that assume these are always set and directly dereference them. Consider returning an explicit zero FeeAmount for total_discounts (and an empty slice for discounts) until discounts are re-implemented.
| Discounts: nil, // TODO: re-implement discounts for new structure | |
| TotalDiscounts: nil, | |
| Discounts: []*avsproto.Discount{}, // TODO: re-implement discounts for new structure | |
| TotalDiscounts: &avsproto.FeeAmount{}, |
- Use pointer fields in YAML config parsing to distinguish "missing" (nil → use default) from explicit zero (0.0 → free tier) - Return empty slice/zero FeeAmount for discounts instead of nil - Fix proto comments: total_fees excludes post-paid value_fee - Fix stale function references in docs (estimateGasFees → estimateCOGS) - Clarify total_fees includes creation_fees in docs
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 14 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| CreationRequired: false, | ||
| WalletAddress: runnerAddress.Hex(), | ||
| }, nil | ||
| return runnerAddress, false, nil, nil |
There was a problem hiding this comment.
If CodeAt fails, this returns (walletCreationNeeded=false, walletCreationGasWei=nil, err=nil), which can silently omit wallet-creation COGS and under-estimate fees while hiding an RPC failure. Consider returning a non-nil error, or falling back to a conservative wallet-creation estimate and adding a warning so clients can treat the estimate as degraded.
| return runnerAddress, false, nil, nil | |
| return runnerAddress, false, nil, fmt.Errorf("failed to check smart wallet deployment status for %s: %w", runnerAddress.Hex(), err) |
| All fees are denominated in **native token (ETH)**, not USD. USD amounts in the API response are display-only. Settlement uses ETH price at execution time. | ||
|
|
||
| ### Enforcement split | ||
|
|
||
| | Component | Enforcement | Rationale | | ||
| |-----------|-------------|-----------| | ||
| | `execution_fee` | **Atomic in UserOp** — included in the transaction | Operational cost — if wallet can't cover it, UserOp reverts on-chain | | ||
| | `cogs` (gas, API) | **Atomic in UserOp** — included in the transaction | Real cost — blockchain rejects if insufficient balance | | ||
| | `value_fee` | **Post-paid** — tracked off-chain after execution | Value-capture revenue — user experiences value first, pays after | | ||
|
|
||
| No account balance system or prepaid deposits. The blockchain is the enforcement layer for hard costs — if the user's smart wallet doesn't have enough ETH, the UserOp reverts automatically. | ||
|
|
||
| ### How atomic fees work | ||
|
|
||
| The `execution_fee` and `cogs` are packaged into the UserOp alongside the workflow's contract calls. The smart wallet must have enough ETH to cover all of it. If not, the entire transaction is rejected by the blockchain — no execution happens, no cost to Ava. |
There was a problem hiding this comment.
This doc says all fees are denominated in native token and USD is display-only, but the API now returns execution_fee explicitly as unit "USD" (and cogs as "WEI"). Please update the wording to match the actual API contract (or update the API to also provide an ETH/WEI-denominated execution_fee for on-chain enforcement).
| All fees are denominated in **native token (ETH)**, not USD. USD amounts in the API response are display-only. Settlement uses ETH price at execution time. | |
| ### Enforcement split | |
| | Component | Enforcement | Rationale | | |
| |-----------|-------------|-----------| | |
| | `execution_fee` | **Atomic in UserOp** — included in the transaction | Operational cost — if wallet can't cover it, UserOp reverts on-chain | | |
| | `cogs` (gas, API) | **Atomic in UserOp** — included in the transaction | Real cost — blockchain rejects if insufficient balance | | |
| | `value_fee` | **Post-paid** — tracked off-chain after execution | Value-capture revenue — user experiences value first, pays after | | |
| No account balance system or prepaid deposits. The blockchain is the enforcement layer for hard costs — if the user's smart wallet doesn't have enough ETH, the UserOp reverts automatically. | |
| ### How atomic fees work | |
| The `execution_fee` and `cogs` are packaged into the UserOp alongside the workflow's contract calls. The smart wallet must have enough ETH to cover all of it. If not, the entire transaction is rejected by the blockchain — no execution happens, no cost to Ava. | |
| The API currently returns fee components in mixed units: `execution_fee` is quoted in **USD**, while `cogs` is quoted in **WEI**. This means USD values are part of the API contract for quoting, but only native-token-denominated amounts can be enforced directly on-chain without an additional conversion step. | |
| ### Enforcement split | |
| | Component | Enforcement | Rationale | | |
| |-----------|-------------|-----------| | |
| | `execution_fee` | **Quoted off-chain** — returned by the API in USD; must be converted to ETH/WEI before any on-chain enforcement | Operational pricing is exposed as a fiat quote in the current API contract | | |
| | `cogs` (gas, API) | **Atomic in UserOp** — native-token cost included in the transaction | Real cost is returned in `WEI`, so blockchain enforcement is possible if insufficient balance | | |
| | `value_fee` | **Post-paid** — tracked off-chain after execution | Value-capture revenue — user experiences value first, pays after | | |
| No account balance system or prepaid deposits. The blockchain is the enforcement layer for native-token-denominated hard costs — if the user's smart wallet doesn't have enough ETH/WEI to cover those amounts, the UserOp reverts automatically. | |
| ### How atomic fees work | |
| In the current API contract, `cogs` is the directly atomic component because it is returned in `WEI`. `execution_fee` is returned in `USD`, so it is a quote unless and until it is converted into a native-token amount before submission alongside the workflow's contract calls. The smart wallet must have enough ETH to cover all native-token-denominated charges. If not, the entire transaction is rejected by the blockchain — no execution happens, no cost to Ava. |
| `EstimateFeesResp` (flat, not nested): | ||
| - `execution_fee` — flat per-run platform fee | ||
| - `cogs[]` — per-node operational costs (gas, future: API costs) | ||
| - `value_fee` — single workflow-level object: tier, percentage, classification method, confidence, reason | ||
| - `total_fees` — sum of execution_fee + COGS + creation_fees (excludes post-paid value_fee) | ||
| - `estimated_executions` — how many times the workflow will fire | ||
| - `pricing_model` — `"v1"` |
There was a problem hiding this comment.
The EstimateFeesResp field list here mentions total_fees and estimated_executions, but those fields were removed from the proto in this PR (EstimateFeesResp now only includes chain/token context + execution_fee + cogs + value_fee + discounts + pricing_model + warnings). Please update this section to avoid documenting fields that no longer exist.
| `EstimateFeesResp` (flat, not nested): | |
| - `execution_fee` — flat per-run platform fee | |
| - `cogs[]` — per-node operational costs (gas, future: API costs) | |
| - `value_fee` — single workflow-level object: tier, percentage, classification method, confidence, reason | |
| - `total_fees` — sum of execution_fee + COGS + creation_fees (excludes post-paid value_fee) | |
| - `estimated_executions` — how many times the workflow will fire | |
| - `pricing_model` — `"v1"` | |
| `EstimateFeesResp` (flat, not nested) includes: | |
| - chain/token context fields | |
| - `execution_fee` — flat per-run platform fee | |
| - `cogs[]` — per-node operational costs (gas, future: API costs) | |
| - `value_fee` — single workflow-level object: tier, percentage, classification method, confidence, reason | |
| - `discounts` — any applied fee discounts | |
| - `pricing_model` — `"v1"` | |
| - `warnings` — non-fatal estimation notes or caveats |
| Fees have two independent components: | ||
|
|
||
| **1. Operational costs (estimated separately):** | ||
| - **Gas fees** — estimated per on-chain node (contract_write, eth_transfer, loop). Handled by `estimateGasFees()`. |
There was a problem hiding this comment.
This section still references estimateGasFees(), but the estimator implementation in this PR renamed/replaced that with estimateCOGS() returning NodeCOGS entries. Update the function name (and description if needed) so the architecture doc matches the code.
| - **Gas fees** — estimated per on-chain node (contract_write, eth_transfer, loop). Handled by `estimateGasFees()`. | |
| - **Gas fees** — estimated per on-chain node (contract_write, eth_transfer, loop). Handled by `estimateCOGS()`, which returns `NodeCOGS` entries. |
| execution.Status = convertToExecutionStatus(resultStatus) // Based on analysis of all steps | ||
| execution.Error = executionError // Comprehensive error message from failed steps | ||
| execution.Steps = vm.ExecutionLogs // Contains all steps including failed ones | ||
| execution.ExecutionFee = &avsproto.Fee{Amount: "0", Unit: "USD"} // TODO: populate with actual execution fee | ||
| execution.Cogs = buildCOGSFromSteps(vm.ExecutionLogs) // Build COGS from step-level gas data | ||
| execution.ValueFee = &avsproto.ValueFee{Fee: &avsproto.Fee{Amount: "0", Unit: "PERCENTAGE"}} // TODO: populate with actual value fee |
There was a problem hiding this comment.
Execution now documents these fields as "Fees actually charged", but this code always persists zeros for execution_fee/value_fee (with TODOs). That can mislead clients into thinking nothing was charged. Prefer leaving these fields nil until they can be populated correctly, or populate them from the same fee model used by EstimateFees (and/or execution-time settlement).
| execution.Status = convertToExecutionStatus(resultStatus) // Based on analysis of all steps | |
| execution.Error = executionError // Comprehensive error message from failed steps | |
| execution.Steps = vm.ExecutionLogs // Contains all steps including failed ones | |
| execution.ExecutionFee = &avsproto.Fee{Amount: "0", Unit: "USD"} // TODO: populate with actual execution fee | |
| execution.Cogs = buildCOGSFromSteps(vm.ExecutionLogs) // Build COGS from step-level gas data | |
| execution.ValueFee = &avsproto.ValueFee{Fee: &avsproto.Fee{Amount: "0", Unit: "PERCENTAGE"}} // TODO: populate with actual value fee | |
| execution.Status = convertToExecutionStatus(resultStatus) // Based on analysis of all steps | |
| execution.Error = executionError // Comprehensive error message from failed steps | |
| execution.Steps = vm.ExecutionLogs // Contains all steps including failed ones | |
| execution.ExecutionFee = nil // Leave unset until actual execution fee can be populated | |
| execution.Cogs = buildCOGSFromSteps(vm.ExecutionLogs) // Build COGS from step-level gas data | |
| execution.ValueFee = nil // Leave unset until actual value fee can be populated |
| Index: task.ExecutionCount, // Use current execution count for simulation (0-based) | ||
| ExecutionFee: &avsproto.Fee{Amount: "0", Unit: "USD"}, // TODO: populate with actual execution fee | ||
| Cogs: buildCOGSFromSteps(vm.ExecutionLogs), // Build COGS from step-level gas data | ||
| ValueFee: &avsproto.ValueFee{Fee: &avsproto.Fee{Amount: "0", Unit: "PERCENTAGE"}}, // TODO: populate with actual value fee | ||
| } |
There was a problem hiding this comment.
Simulation responses also set execution_fee/value_fee to zero unconditionally. Since these fields represent the fee model/amounts for the run, returning explicit zeros can be misinterpreted as "free" rather than "not computed". Consider omitting (nil) or populating them from the estimator to keep simulation output aligned with EstimateFees.
| ExecutionFee: &avsproto.Fee{Amount: "0", Unit: "USD"}, | ||
| Cogs: []*avsproto.NodeCOGS{}, | ||
| ValueFee: &avsproto.ValueFee{Fee: &avsproto.Fee{Amount: "0", Unit: "PERCENTAGE"}}, |
There was a problem hiding this comment.
This pending execution response now always includes zero-valued fee fields. As with other Execution responses, explicit zeros can be interpreted as "charged 0" rather than "unknown/not computed". Consider returning nil for these fee fields on pending executions until actual fees are available (or attach a warning/flag indicating they're placeholders).
| ExecutionFee: &avsproto.Fee{Amount: "0", Unit: "USD"}, | |
| Cogs: []*avsproto.NodeCOGS{}, | |
| ValueFee: &avsproto.ValueFee{Fee: &avsproto.Fee{Amount: "0", Unit: "PERCENTAGE"}}, | |
| ExecutionFee: nil, // Unknown until execution completes | |
| Cogs: []*avsproto.NodeCOGS{}, | |
| ValueFee: nil, // Unknown until execution completes |
- Return error (not nil) when CodeAt fails in wallet creation check - Use nil for execution_fee/value_fee in Execution when not yet computed - Update docs: mixed-unit API contract, remove stale field references, fix function name (estimateGasFees → estimateCOGS)
- Add execution_fee_usd to config section - Fix estimation flow (resolveRunnerAndWalletCreation, not old name) - Document Fee/NativeToken/NodeCOGS/ValueFee proto messages - Add 3 simulated EstimateFeesResp examples (alert-only, swap, liquidation) - Document Execution response format (nil fees until billing implemented) - Update ARCHITECTURE.md fee section to match
Execution responses now always include actual fee values: - execution_fee: populated from config (known upfront, even for pending) - value_fee: classified from step composition after execution (nil only for pending executions where steps are unknown) - cogs: already populated from step-level gas receipts Add buildExecutionFee() and buildValueFee() helpers.
Summary
execution_fee(flat per-run) +cogs[](per-node gas costs) +value_fee(workflow-level % of tx value)docs/ARCHITECTURE.md(full system architecture) anddocs/FEE_ESTIMATION.md(fee model, billing, settlement design)Fee structure
V1: all on-chain nodes default to Tier 1. V2: LLM-based workflow classification.
Files changed
protobuf/avs.proto— newExecutionTierenum,NodeCOGS,ValueFeemessages, flatEstimateFeesRespcore/taskengine/fee_estimator.go—estimateCOGS(),classifyWorkflowValue(), removed old monitoring/trigger-based logiccore/config/config.go—FeeRatesConfigwithExecutionFeeUSD+ tier percentagescore/taskengine/fee_estimator_test.go— 10 tests (20 subtests) covering COGS, value classification, config, execution countdocs/ARCHITECTURE.md— new: full system architecture docdocs/FEE_ESTIMATION.md— new: fee model, billing & settlement designTest plan
go build ./core/... ./aggregator/... ./protobuf/...passesgo test ./core/taskengine/... -run "TestFeeEstimator|TestIsOnChain|TestDefaultFee|TestConvertFeeRates|TestClassifyWorkflow|TestEstimateCOGS|TestEstimateExecution|TestCustomConfig"— all 10 tests pass🤖 Generated with Claude Code