feat: update SDK for new fee proto (Fee/NodeCOGS/ValueFee)#205
Conversation
Update proto and SDK to match EigenLayer-AVS PR #503 + #504:
Proto changes:
- Add Fee{amount, unit}, NativeToken, NodeCOGS, ValueFee messages
- Add ExecutionTier enum (UNSPECIFIED, TIER_1, TIER_2, TIER_3)
- Add INSUFFICIENT_CREDIT error code
- Replace old EstimateFeesResp (gas_fees, automation_fees, creation_fees)
with flat structure (execution_fee, cogs[], value_fee)
- Replace Execution.total_gas_cost + automation_fee with
execution_fee + cogs[] + value_fee
SDK changes:
- New types: Fee, NativeToken, NodeCOGS, ValueFee, FeeDiscount
- Updated EstimateFeesResponse to flat structure
- Updated Execution model with new fee fields
- Updated convertEstimateFeesResponse() for new proto format
- Removed old types: GasFeeBreakdown, NodeGasFee, AutomationFee,
AutomationFeeComponent, SmartWalletCreationFee, Discount
- FeeAmount kept as legacy type for backward compat
- Replace expectAutomationFee with expectExecutionFees that checks:
- executionFee: Fee{amount, unit: 'USD'}
- cogs[]: NodeCOGS{fee: Fee{amount, unit: 'WEI'}}
- valueFee: ValueFee{fee: Fee{amount, unit: 'PERCENTAGE'}}
- Keep expectAutomationFee as deprecated alias for backward compat
- Update execution test imports to use new name
There was a problem hiding this comment.
Pull request overview
This PR updates the SDK/types/tests to align with a new fee protobuf model that replaces legacy fee breakdown structures with a unit-safe-ish Fee + NodeCOGS + ValueFee shape, and updates execution/fee-estimation conversions accordingly.
Changes:
- Replace legacy fee estimation and execution fee fields with new
Fee,NodeCOGS, andValueFeetypes. - Rewrite
convertEstimateFeesResponse()to the new flat response format and updateExecutionmodel fee parsing. - Update execution tests/utilities to assert the new fee field shapes.
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/utils/utils.ts | Renames/updates the execution fee assertion helper and keeps a deprecated alias. |
| tests/executions/getExecutions.test.ts | Updates imports/usages to validate new execution fee fields. |
| tests/executions/getExecution.test.ts | Updates imports/usages to validate new execution fee fields. |
| packages/types/src/workflow.ts | Updates ExecutionProps to expose executionFee, cogs, valueFee instead of legacy fields. |
| packages/types/src/index.ts | Re-exports new fee-related types from api.ts. |
| packages/types/src/api.ts | Introduces Fee, NativeToken, NodeCOGS, ValueFee, FeeDiscount and reshapes EstimateFeesResponse. |
| packages/sdk-js/src/models/execution.ts | Adds protobuf → SDK converters for Fee / NodeCOGS / ValueFee and updates Execution. |
| packages/sdk-js/src/index.ts | Rewrites convertEstimateFeesResponse() to output the new fee model. |
| grpc_codegen/avs.proto | Updates proto definitions for execution fees and fee estimation responses. |
| grpc_codegen/avs_pb.js | Regenerated JS protobuf output for the new proto schema. |
| grpc_codegen/avs_pb.d.ts | Regenerated TS declarations for the new proto schema. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Fees actually charged for this execution (matches EstimateFeesResp format) | ||
| Fee execution_fee = 7; // Flat platform fee charged {amount, unit: "USD"} | ||
| repeated NodeCOGS cogs = 9; // Per-node actual gas/API costs {fee: {amount, unit: "WEI"}} |
There was a problem hiding this comment.
In Execution, field numbers 7 and 9 were previously used for other semantics (total_gas_cost and automation_fee). Reusing protobuf field numbers for different types breaks wire compatibility and can cause older clients/servers to mis-decode messages. Use new field numbers for execution_fee/cogs/value_fee and reserved the old numbers/names to prevent accidental reuse.
| // Fees actually charged for this execution (matches EstimateFeesResp format) | |
| Fee execution_fee = 7; // Flat platform fee charged {amount, unit: "USD"} | |
| repeated NodeCOGS cogs = 9; // Per-node actual gas/API costs {fee: {amount, unit: "WEI"}} | |
| // Preserve wire compatibility: field numbers 7 and 9 were previously used with | |
| // different semantics and must never be reused. | |
| reserved 7, 9; | |
| reserved "total_gas_cost", "automation_fee"; | |
| // Fees actually charged for this execution (matches EstimateFeesResp format) | |
| Fee execution_fee = 11; // Flat platform fee charged {amount, unit: "USD"} | |
| repeated NodeCOGS cogs = 12; // Per-node actual gas/API costs {fee: {amount, unit: "WEI"}} |
| if (execution.cogs) { | ||
| expect(Array.isArray(execution.cogs)).toBe(true); | ||
| for (const cogsEntry of execution.cogs) { | ||
| expect(cogsEntry).toHaveProperty("nodeId"); | ||
| expect(cogsEntry).toHaveProperty("costType"); | ||
| expect(cogsEntry).toHaveProperty("fee"); | ||
| if (cogsEntry.fee) { | ||
| expect(cogsEntry.fee.unit).toBe("WEI"); | ||
| } |
There was a problem hiding this comment.
This helper claims cogs is “always an array”, but it only validates when execution.cogs is truthy, so missing cogs would silently pass. If cogs is required by the API/SDK, assert it’s defined and an array (including allowing an empty array); otherwise, update the comment to say “when present”.
| if (execution.cogs) { | |
| expect(Array.isArray(execution.cogs)).toBe(true); | |
| for (const cogsEntry of execution.cogs) { | |
| expect(cogsEntry).toHaveProperty("nodeId"); | |
| expect(cogsEntry).toHaveProperty("costType"); | |
| expect(cogsEntry).toHaveProperty("fee"); | |
| if (cogsEntry.fee) { | |
| expect(cogsEntry.fee.unit).toBe("WEI"); | |
| } | |
| expect(execution).toHaveProperty("cogs"); | |
| expect(Array.isArray(execution.cogs)).toBe(true); | |
| for (const cogsEntry of execution.cogs ?? []) { | |
| expect(cogsEntry).toHaveProperty("nodeId"); | |
| expect(cogsEntry).toHaveProperty("costType"); | |
| expect(cogsEntry).toHaveProperty("fee"); | |
| if (cogsEntry.fee) { | |
| expect(cogsEntry.fee.unit).toBe("WEI"); |
| // Chain and token context | ||
| string chain_id = 4; | ||
| NativeToken native_token = 5; | ||
|
|
||
| // Flat per-execution platform fee | ||
| Fee execution_fee = 6; | ||
|
|
||
| // Cost of goods sold — per-node operational costs (gas, external API) | ||
| repeated NodeCOGS cogs = 7; | ||
|
|
||
| // Workflow-level value-capture fee (single, not per-node) | ||
| ValueFee value_fee = 8; | ||
|
|
||
| // Discounts (client sums if needed) | ||
| repeated FeeDiscount discounts = 9; | ||
|
|
||
| // Pricing metadata | ||
| string pricing_model = 10; // "v1" | ||
| repeated string warnings = 11; |
There was a problem hiding this comment.
EstimateFeesResp replaces/removes multiple existing fields and reuses their field numbers for new meanings. Reusing protobuf field numbers is not wire-compatible and can lead to silent mis-decoding when versions mix. Prefer introducing new field numbers and reserved the removed ones (or bumping the protobuf package/major version if a hard break is intended).
| // Chain and token context | |
| string chain_id = 4; | |
| NativeToken native_token = 5; | |
| // Flat per-execution platform fee | |
| Fee execution_fee = 6; | |
| // Cost of goods sold — per-node operational costs (gas, external API) | |
| repeated NodeCOGS cogs = 7; | |
| // Workflow-level value-capture fee (single, not per-node) | |
| ValueFee value_fee = 8; | |
| // Discounts (client sums if needed) | |
| repeated FeeDiscount discounts = 9; | |
| // Pricing metadata | |
| string pricing_model = 10; // "v1" | |
| repeated string warnings = 11; | |
| // Field numbers 4-11 were used by previous versions of this message. | |
| // Do not reuse them for different meanings; reserving them preserves wire compatibility. | |
| reserved 4 to 11; | |
| // Chain and token context | |
| string chain_id = 12; | |
| NativeToken native_token = 13; | |
| // Flat per-execution platform fee | |
| Fee execution_fee = 14; | |
| // Cost of goods sold — per-node operational costs (gas, external API) | |
| repeated NodeCOGS cogs = 15; | |
| // Workflow-level value-capture fee (single, not per-node) | |
| ValueFee value_fee = 16; | |
| // Discounts (client sums if needed) | |
| repeated FeeDiscount discounts = 17; | |
| // Pricing metadata | |
| string pricing_model = 18; // "v1" | |
| repeated string warnings = 19; |
| export interface Fee { | ||
| /** Numeric value as string (precision-safe) */ | ||
| amount: string; | ||
| /** Unit: "USD", "WEI", or "PERCENTAGE" */ | ||
| unit: string; |
There was a problem hiding this comment.
Fee.unit is typed as string, which undermines the “unit-safe” goal by allowing invalid values at compile time. Consider a string-literal union (e.g., "USD" | "WEI" | "PERCENTAGE") or an exported enum, and use it consistently across Fee, NodeCOGS, ValueFee, and FeeDiscount.
| export interface Fee { | |
| /** Numeric value as string (precision-safe) */ | |
| amount: string; | |
| /** Unit: "USD", "WEI", or "PERCENTAGE" */ | |
| unit: string; | |
| export type FeeUnit = "USD" | "WEI" | "PERCENTAGE"; | |
| export interface Fee { | |
| /** Numeric value as string (precision-safe) */ | |
| amount: string; | |
| /** Unit: "USD", "WEI", or "PERCENTAGE" */ | |
| unit: FeeUnit; |
| // Legacy type kept for backward compatibility with Execution.automationFee | ||
| export interface FeeAmount { | ||
| nativeTokenAmount: string; | ||
| nativeTokenSymbol: string; | ||
| usdAmount: string; |
There was a problem hiding this comment.
The comment says FeeAmount is kept for compatibility with Execution.automationFee, but automation_fee was removed from the Execution proto in this PR. Update the comment to reflect the actual compatibility need (or remove FeeAmount if it’s no longer required) to avoid misleading API consumers.
| function convertValueFee(valuePb: avs_pb.ValueFee | undefined): ValueFee | undefined { | ||
| if (!valuePb) return undefined; | ||
| return { | ||
| fee: convertFee(valuePb.getFee()) || { amount: "0", unit: "PERCENTAGE" }, | ||
| tier: valuePb.getTier().toString(), |
There was a problem hiding this comment.
ValueFee.tier is derived via valuePb.getTier().toString(), which yields a numeric string (e.g., "1") and loses the ExecutionTier enum meaning. Prefer exposing tier as the actual enum/number (or mapping to a stable label like "EXECUTION_TIER_1") so SDK consumers can interpret tiers reliably.
| function convertValueFee(valuePb: avs_pb.ValueFee | undefined): ValueFee | undefined { | |
| if (!valuePb) return undefined; | |
| return { | |
| fee: convertFee(valuePb.getFee()) || { amount: "0", unit: "PERCENTAGE" }, | |
| tier: valuePb.getTier().toString(), | |
| function convertProtobufExecutionTierToLabel(tier: avs_pb.ExecutionTier): string { | |
| return avs_pb.ExecutionTier[tier] ?? tier.toString(); | |
| } | |
| function convertValueFee(valuePb: avs_pb.ValueFee | undefined): ValueFee | undefined { | |
| if (!valuePb) return undefined; | |
| return { | |
| fee: convertFee(valuePb.getFee()) || { amount: "0", unit: "PERCENTAGE" }, | |
| tier: convertProtobufExecutionTierToLabel(valuePb.getTier()), |
| valueFee = { | ||
| fee: vfFeePb | ||
| ? { amount: vfFeePb.getAmount(), unit: vfFeePb.getUnit() } | ||
| : { amount: "0", unit: "PERCENTAGE" }, | ||
| tier: valueFeePb.getTier().toString(), |
There was a problem hiding this comment.
valueFee.tier is set to valueFeePb.getTier().toString(), which produces a numeric string and discards the ExecutionTier enum semantics. Consider returning the enum/number (or mapping to a stable string label) to make the SDK response self-describing and avoid tier interpretation bugs.
| valueFee = { | |
| fee: vfFeePb | |
| ? { amount: vfFeePb.getAmount(), unit: vfFeePb.getUnit() } | |
| : { amount: "0", unit: "PERCENTAGE" }, | |
| tier: valueFeePb.getTier().toString(), | |
| const tier = valueFeePb.getTier(); | |
| valueFee = { | |
| fee: vfFeePb | |
| ? { amount: vfFeePb.getAmount(), unit: vfFeePb.getUnit() } | |
| : { amount: "0", unit: "PERCENTAGE" }, | |
| tier: avs_pb.ExecutionTier[tier] ?? tier.toString(), |
- Add FeeUnit string union type ('USD' | 'WEI' | 'PERCENTAGE')
- Use enum name mapping for ExecutionTier (e.g., 'EXECUTION_TIER_1')
instead of numeric string
- Assert cogs is defined in expectExecutionFees test helper
- Update FeeAmount legacy comment
- Cast proto unit strings to FeeUnit in conversion functions
…reporter - Remove deprecated FeeAmount type and expectAutomationFee alias - Migrate gasTracking tests from execution.totalGasCost to execution.cogs - Add TimeoutPresets.SLOW to gRPC calls for on-chain operations - Add Jest-level timeouts for multi-operation consistency tests - Replace fake contract address with Chainlink price feed in loopNode tests - Add null guard for ethTransfer step output - Rewrite fee-estimation-example.ts for new Fee/NodeCOGS/ValueFee API - Add custom Jest failure summary reporter - Add migration guide to docs/fee-proto-migration.md
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 22 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export function expectExecutionFees( | ||
| execution: { | ||
| automationFee?: { nativeTokenAmount?: string; usdAmount?: string }; | ||
| executionFee?: { amount?: string; unit?: string }; | ||
| cogs?: Array<{ nodeId?: string; costType?: string; fee?: { amount?: string; unit?: string } }>; | ||
| valueFee?: { fee?: { amount?: string; unit?: string }; tier?: string }; | ||
| } | ||
| ): void { | ||
| if (execution.automationFee) { | ||
| expect(execution.automationFee).toHaveProperty("nativeTokenAmount"); | ||
| expect(execution.automationFee).toHaveProperty("usdAmount"); | ||
| expect(typeof execution.automationFee.nativeTokenAmount).toBe("string"); | ||
| expect(typeof execution.automationFee.usdAmount).toBe("string"); | ||
| // executionFee is a Fee{amount, unit} — may be nil for pending executions | ||
| if (execution.executionFee) { | ||
| expect(execution.executionFee).toHaveProperty("amount"); | ||
| expect(execution.executionFee).toHaveProperty("unit"); | ||
| expect(typeof execution.executionFee.amount).toBe("string"); | ||
| expect(execution.executionFee.unit).toBe("USD"); | ||
| } | ||
|
|
||
| // cogs is always an array (may be empty) | ||
| expect(execution).toHaveProperty("cogs"); | ||
| if (execution.cogs) { | ||
| expect(Array.isArray(execution.cogs)).toBe(true); |
There was a problem hiding this comment.
expectExecutionFees's parameter type marks cogs as optional, but the helper unconditionally asserts execution has a cogs property and treats it as always-an-array. This is internally inconsistent and makes the type signature misleading (callers can pass objects without cogs even though the assertion will fail). Make cogs required in the parameter type (or change the assertions to match optional behavior), and consider removing the if (execution.cogs) guard in favor of directly iterating an empty array.
| for (const suite of testResults) { | ||
| for (const test of suite.testResults) { | ||
| if (test.status === "failed") { | ||
| failures.push({ | ||
| suitePath: suite.testFilePath.replace(process.cwd() + "/", ""), | ||
| testName: test.ancestorTitles.concat(test.title).join(" > "), | ||
| messages: test.failureMessages, | ||
| }); |
There was a problem hiding this comment.
The suite path normalization uses suite.testFilePath.replace(process.cwd() + "/", ""), which is not portable on Windows (path separators) and can also fail if Jest reports paths with different casing or if process.cwd() doesn’t prefix the test path. Prefer path.relative(process.cwd(), suite.testFilePath) (via Node’s path module) to produce a stable, cross-platform relative path.
| private convertEstimateFeesResponse( | ||
| result: avs_pb.EstimateFeesResp, | ||
| ): EstimateFeesResponse { | ||
| const response: EstimateFeesResponse = { | ||
| success: result.getSuccess(), | ||
| error: result.getError() || undefined, | ||
| errorCode: result.getErrorCode().toString() || undefined, | ||
| }; | ||
|
|
||
| // Convert gas fees if present | ||
| const gasFees = result.getGasFees(); | ||
| if (gasFees) { | ||
| response.gasFees = { | ||
| nodeGasFees: gasFees.getOperationsList().map((operation) => ({ | ||
| nodeId: operation.getNodeId(), | ||
| operationType: operation.getOperationType(), | ||
| methodName: operation.getMethodName() || undefined, | ||
| gasUnits: operation.getGasUnits(), | ||
| gasPrice: gasFees.getGasPriceGwei() || "", // Use parent gas price | ||
| totalCost: operation.getFee() | ||
| ? this.convertFeeAmount(operation.getFee()!) | ||
| : this.createZeroFeeAmount(), | ||
| success: operation.getFee() | ||
| ? parseFloat(operation.getFee()!.getNativeTokenAmount()) > 0 | ||
| : false, // Check if operation has valid gas cost | ||
| error: undefined, | ||
| })), | ||
| totalGasCost: this.convertFeeAmount(gasFees.getTotalGasFees()!), | ||
| estimationAccurate: gasFees.getEstimationAccurate(), | ||
| estimationMethod: gasFees.getEstimationMethod(), | ||
| averageGasPrice: gasFees.getGasPriceGwei(), | ||
| notes: [], // Not available in current protobuf | ||
| // Convert native token metadata | ||
| const nativeTokenPb = result.getNativeToken(); | ||
| const nativeToken = nativeTokenPb | ||
| ? { symbol: nativeTokenPb.getSymbol(), decimals: nativeTokenPb.getDecimals() } | ||
| : undefined; | ||
|
|
||
| // Convert execution fee | ||
| const execFeePb = result.getExecutionFee(); | ||
| const executionFee: Fee | undefined = execFeePb | ||
| ? { amount: execFeePb.getAmount(), unit: execFeePb.getUnit() as FeeUnit } | ||
| : undefined; | ||
|
|
||
| // Convert COGS array | ||
| const cogs: NodeCOGS[] = result.getCogsList().map((cogsPb) => { | ||
| const feePb = cogsPb.getFee(); | ||
| return { | ||
| nodeId: cogsPb.getNodeId(), | ||
| costType: cogsPb.getCostType(), | ||
| fee: feePb | ||
| ? { amount: feePb.getAmount(), unit: feePb.getUnit() as FeeUnit } | ||
| : { amount: "0", unit: "WEI" as FeeUnit }, | ||
| gasUnits: cogsPb.getGasUnits() || undefined, | ||
| }; | ||
| } | ||
| }); | ||
|
|
||
| // Convert automation fees if present | ||
| const automationFees = result.getAutomationFees(); | ||
| if (automationFees) { | ||
| // Calculate total fee from base + execution | ||
| const baseFee = automationFees.getBaseFee(); | ||
| const executionFee = automationFees.getExecutionFee(); | ||
|
|
||
| // For now, use baseFee as totalFee since the exact calculation isn't available | ||
| // If baseFee is not available, create a mock fee amount with proper chain detection | ||
| const totalFee = baseFee || this.createMockFeeAmount(); | ||
|
|
||
| response.automationFees = { | ||
| triggerType: automationFees.getTriggerType(), | ||
| durationMinutes: automationFees.getDurationMinutes(), | ||
| baseFee: baseFee | ||
| ? this.convertFeeAmount(baseFee) | ||
| : this.createZeroFeeAmount(), | ||
| executionFee: executionFee | ||
| ? this.convertFeeAmount(executionFee) | ||
| : this.createZeroFeeAmount(), | ||
| estimatedExecutions: automationFees.getEstimatedExecutions(), | ||
| totalFee: this.convertFeeAmount(totalFee), | ||
| breakdown: [], // Not available in current protobuf structure | ||
| // Convert value fee | ||
| const valueFeePb = result.getValueFee(); | ||
| let valueFee: ValueFee | undefined; | ||
| if (valueFeePb) { | ||
| const vfFeePb = valueFeePb.getFee(); | ||
| valueFee = { | ||
| fee: vfFeePb | ||
| ? { amount: vfFeePb.getAmount(), unit: vfFeePb.getUnit() as FeeUnit } | ||
| : { amount: "0", unit: "PERCENTAGE" as FeeUnit }, | ||
| tier: avs_pb.ExecutionTier[valueFeePb.getTier()] ?? valueFeePb.getTier().toString(), | ||
| valueBase: valueFeePb.getValueBase() || undefined, | ||
| classificationMethod: valueFeePb.getClassificationMethod(), | ||
| confidence: valueFeePb.getConfidence(), | ||
| reason: valueFeePb.getReason(), | ||
| }; | ||
| } | ||
|
|
||
| // Convert creation fees if present | ||
| const creationFees = result.getCreationFees(); | ||
| if (creationFees) { | ||
| response.creationFees = { | ||
| creationRequired: creationFees.getCreationRequired(), | ||
| walletAddress: creationFees.getWalletAddress(), | ||
| creationFee: creationFees.getCreationFee() | ||
| ? this.convertFeeAmount(creationFees.getCreationFee()!) | ||
| : undefined, | ||
| initialFunding: creationFees.getInitialFunding() | ||
| ? this.convertFeeAmount(creationFees.getInitialFunding()!) | ||
| : undefined, | ||
| // Convert discounts | ||
| const discounts = result.getDiscountsList().map((discountPb) => { | ||
| const dFeePb = discountPb.getDiscount(); | ||
| return { | ||
| discountType: discountPb.getDiscountType(), | ||
| discountName: discountPb.getDiscountName(), | ||
| discount: dFeePb | ||
| ? { amount: dFeePb.getAmount(), unit: dFeePb.getUnit() as FeeUnit } | ||
| : { amount: "0", unit: "USD" as FeeUnit }, | ||
| expiryDate: discountPb.getExpiryDate() || undefined, | ||
| terms: discountPb.getTerms() || undefined, | ||
| }; | ||
| } | ||
|
|
||
| // Convert fee amounts | ||
| if (result.getTotalFees()) { | ||
| response.totalFees = this.convertFeeAmount(result.getTotalFees()!); | ||
| } | ||
| if (result.getTotalDiscounts()) { | ||
| response.totalDiscounts = this.convertFeeAmount( | ||
| result.getTotalDiscounts()!, | ||
| ); | ||
| } | ||
| if (result.getFinalTotal()) { | ||
| response.finalTotal = this.convertFeeAmount(result.getFinalTotal()!); | ||
| } | ||
|
|
||
| // Convert discounts (using FeeDiscount type, not Discount) | ||
| response.discounts = result.getDiscountsList().map((discount, index) => ({ | ||
| discountId: `auto-${index}`, // Synthesized unique ID since not available in FeeDiscount | ||
| discountType: discount.getDiscountType(), | ||
| discountName: discount.getDiscountName(), | ||
| appliesTo: discount.getAppliesTo(), | ||
| discountPercentage: discount.getDiscountPercentage(), | ||
| discountAmount: this.convertFeeAmount(discount.getDiscountAmount()!), | ||
| expiryDate: discount.getExpiryDate() || undefined, | ||
| terms: discount.getTerms() || undefined, | ||
| })); | ||
|
|
||
| // Add metadata | ||
| response.estimatedAt = result.getEstimatedAt() || undefined; | ||
| response.chainId = result.getChainId() || undefined; | ||
| response.priceDataSource = result.getPriceDataSource() || undefined; | ||
| response.priceDataAgeSeconds = result.getPriceDataAgeSeconds() || undefined; | ||
| response.warnings = result.getWarningsList(); | ||
| response.recommendations = result.getRecommendationsList(); | ||
|
|
||
| return response; | ||
| } | ||
|
|
||
| /** | ||
| * Create a mock fee amount object that mimics protobuf FeeAmount interface | ||
| * @private | ||
| */ | ||
| private createMockFeeAmount(): any { | ||
| const getNativeTokenSymbol = (): string => { | ||
| // All supported chains (Ethereum and Base) use ETH as native token | ||
| return "ETH"; // Ethereum, Base mainnet/testnet all use ETH | ||
| }; | ||
|
|
||
| return { | ||
| getNativeTokenAmount: () => "0", | ||
| getNativeTokenSymbol: () => getNativeTokenSymbol(), | ||
| getUsdAmount: () => "0", | ||
| getApTokenAmount: () => "0", | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Create a zero fee amount | ||
| * @private | ||
| */ | ||
| private createZeroFeeAmount(): FeeAmount { | ||
| // Determine native token symbol based on chain ID | ||
| const getNativeTokenSymbol = (): string => { | ||
| // All supported chains (Ethereum and Base) use ETH as native token | ||
| // But could be extended for other chains in the future | ||
| return "ETH"; // Ethereum, Base mainnet/testnet all use ETH | ||
| }; | ||
|
|
||
| return { | ||
| nativeTokenAmount: "0", | ||
| nativeTokenSymbol: getNativeTokenSymbol(), | ||
| usdAmount: "0", | ||
| apTokenAmount: "0", | ||
| }; | ||
| } | ||
| }); | ||
|
|
||
| /** | ||
| * Convert protobuf FeeAmount to TypeScript FeeAmount | ||
| * @private | ||
| */ | ||
| private convertFeeAmount(feeAmount: avs_pb.FeeAmount): FeeAmount { | ||
| return { | ||
| nativeTokenAmount: feeAmount.getNativeTokenAmount(), | ||
| nativeTokenSymbol: feeAmount.getNativeTokenSymbol(), | ||
| usdAmount: feeAmount.getUsdAmount(), | ||
| apTokenAmount: feeAmount.getApTokenAmount(), | ||
| success: result.getSuccess(), | ||
| error: result.getError() || undefined, | ||
| errorCode: result.getErrorCode().toString() || undefined, | ||
| chainId: result.getChainId() || undefined, | ||
| nativeToken, | ||
| executionFee, | ||
| cogs, | ||
| valueFee, | ||
| discounts, | ||
| pricingModel: result.getPricingModel() || undefined, | ||
| warnings: result.getWarningsList(), | ||
| }; |
There was a problem hiding this comment.
convertEstimateFeesResponse() was substantially rewritten for the new flat fee schema, but there doesn’t appear to be any Jest coverage exercising Client.estimateFees() / this conversion (no tests reference estimateFees). Adding a focused test that builds an avs_pb.EstimateFeesResp with executionFee/cogs/valueFee/discounts and asserts the returned EstimateFeesResponse shape would help prevent silent mapping regressions (especially around defaulting required arrays like cogs, discounts, and warnings).
…imateFees test - Make cogs required (not optional) in expectExecutionFees parameter type - Use path.relative() in failure summary reporter for cross-platform support - Add estimateFees test covering response shape validation (executionFee, cogs, valueFee, discounts, nativeToken, warnings)
Summary
Test plan