Skip to content

feat: update SDK for new fee proto (Fee/NodeCOGS/ValueFee)#205

Merged
chrisli30 merged 8 commits into
stagingfrom
feat/update-fee-proto
Apr 6, 2026
Merged

feat: update SDK for new fee proto (Fee/NodeCOGS/ValueFee)#205
chrisli30 merged 8 commits into
stagingfrom
feat/update-fee-proto

Conversation

@chrisli30

Copy link
Copy Markdown
Member

Summary

  • Update proto from EigenLayer-AVS staging (PRs #503 + #504)
  • Replace old fee types (FeeAmount, GasFeeBreakdown, AutomationFee) with new unit-safe structure (Fee, NodeCOGS, ValueFee)
  • Update Execution model with new fee fields
  • Rewrite convertEstimateFeesResponse() for flat response format
  • Update test assertions: expectExecutionFees() validates new shapes

Test plan

  • yarn build passes
  • Execution tests: 48/50 pass (2 pre-existing failures unrelated to fee changes)
  • expectExecutionFees() validates executionFee (USD), cogs (WEI), valueFee (PERCENTAGE)

will-dz added 2 commits April 6, 2026 00:33
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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, and ValueFee types.
  • Rewrite convertEstimateFeesResponse() to the new flat response format and update Execution model 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.

Comment thread grpc_codegen/avs.proto
Comment on lines +702 to +704
// 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"}}

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// 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"}}

Copilot uses AI. Check for mistakes.
Comment thread tests/utils/utils.ts Outdated
Comment on lines +745 to +753
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");
}

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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”.

Suggested change
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");

Copilot uses AI. Check for mistakes.
Comment thread grpc_codegen/avs.proto
Comment on lines +1441 to +1459
// 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;

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
// 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;

Copilot uses AI. Check for mistakes.
Comment thread packages/types/src/api.ts Outdated
Comment on lines +298 to +302
export interface Fee {
/** Numeric value as string (precision-safe) */
amount: string;
/** Unit: "USD", "WEI", or "PERCENTAGE" */
unit: string;

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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;

Copilot uses AI. Check for mistakes.
Comment thread packages/types/src/api.ts Outdated
Comment on lines +414 to +418
// Legacy type kept for backward compatibility with Execution.automationFee
export interface FeeAmount {
nativeTokenAmount: string;
nativeTokenSymbol: string;
usdAmount: string;

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread packages/sdk-js/src/models/execution.ts Outdated
Comment on lines +50 to +54
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(),

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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()),

Copilot uses AI. Check for mistakes.
Comment thread packages/sdk-js/src/index.ts Outdated
Comment on lines +1600 to +1604
valueFee = {
fee: vfFeePb
? { amount: vfFeePb.getAmount(), unit: vfFeePb.getUnit() }
: { amount: "0", unit: "PERCENTAGE" },
tier: valueFeePb.getTier().toString(),

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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(),

Copilot uses AI. Check for mistakes.
- 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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread tests/utils/utils.ts Outdated
Comment on lines +729 to +747
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);

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +21
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,
});

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines 1568 to 1639
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(),
};

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
…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)
@chrisli30 chrisli30 merged commit 1d39e3c into staging Apr 6, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants