Conversation
The operator's shared event enrichment was updated in EigenLayer-AVS PR #509 so that on ERC-20 Transfer event triggers: data.value → raw uint256 base units (string, for math) data.valueFormatted → decimal-applied display string (for UI/messages) Previously data.value held the formatted value when token metadata was available. SDK consumers that read data.value for display will now get raw base units and break. Changes: - packages/types/src/shared.ts — add @deprecated JSDoc to MethodCallType.applyToFields explaining the field is now redundant for Transfer.value (the operator publishes valueFormatted directly), while remaining valid for non-Transfer events such as Chainlink AnswerUpdated current/answer. - packages/sdk-js/README.md — new "Event trigger output: value vs valueFormatted" section with field table, breaking change note, and applyToFields deprecation rationale. - examples/example.ts — comment near scheduleMonitorTransfer's ContractWrite step explaining when to use data.value (raw, for ERC-20 calldata encoding) vs data.valueFormatted (display). - tests/triggers/eventTrigger.test.ts — assert valueFormatted is defined and string-typed alongside value; update stale "Formatted value" comments. - tests/templates/telegram-alert-on-transfer.test.ts — update mock fixture from value: "100.5" (formatted) to value: "100500000" / valueFormatted: "100.5" / tokenDecimals: 6; assert valueFormatted in the runTrigger output check. - tests/executions/stepInput.test.ts — prefer eventData.valueFormatted in the CustomCode snippet, fall back to decoding rawData only when the new field is unavailable (simulation paths). - .changeset/transfer-value-semantics.md — minor bump for @avaprotocol/sdk-js and @avaprotocol/types with the migration note. No production SDK source reads transferData.value, so there is no compile-time API break. This is a documentation and consumer-guidance release.
…gger tests Following the deprecation of MethodCallType.applyToFields for Transfer events (the operator's shared event enrichment now publishes valueFormatted automatically), remove the redundant methodCall blocks from the SDK's transfer-related tests: - tests/triggers/eventTrigger.test.ts:835 — drop the methodCalls block from the runTrigger Transfer enrichment test query. Add semantic regex assertions to pin the PR #509 contract: `value` must be digits-only (raw uint256 base units), `valueFormatted` must look like a decimal-formatted string. These catch any future operator regression that swaps the two fields. - tests/templates/telegram-alert-on-transfer.test.ts:130, 177 — drop the methodCalls blocks from both outgoing and incoming transfer queries. Update the function-level docstring to remove the stale "applyDecimalsTo" reference and explain that shared enrichment publishes both value and valueFormatted automatically. No functional change to the trigger setup — the operator emits the same enriched data with or without the deprecated methodCall, just without the now-redundant decimal lookup.
…precation The previous commit added @deprecated to MethodCallType.applyToFields, but applyToFields has 30+ legitimate non-Transfer usages across the codebase: Chainlink AnswerUpdated current/answer, ERC-20 totalSupply, latestRoundData.answer, and other contract read fields where shared event enrichment does not pre-compute a formatted value. A blanket field-level @deprecated would incorrectly strikethrough every one of those uses in IDE intellisense and lint reports. The actual deprecation is value-specific (the literal string "Transfer.value"), which TypeScript cannot express at the type level. Replace @deprecated with an @remarks block that documents the Transfer-specific guidance without polluting non-Transfer usages. Updates: - packages/types/src/shared.ts — replace @deprecated with @remarks. The Transfer-specific guidance is preserved verbatim; the field-level type tag is gone so non-Transfer usages render normally. - packages/sdk-js/README.md — soften "deprecated" framing to "do not use for Transfer events", emphasizing the field is still the correct mechanism for everything else. - .changeset/transfer-value-semantics.md — clarify that this is a guidance change, not a type-level deprecation.
PR Review: align SDK with EigenLayer-AVS PR #509 Transfer field semanticsOverall this is a well-reasoned docs + test-fixture update. The commit history is clean, the Bug / Violation of Project Convention
if (eventData.valueFormatted) {
valueFormatted = parseFloat(eventData.valueFormatted);
} else {
const rawValue = eventData.rawData || "0x0";
const valueWei = parseInt(rawValue, 16); // ⚠️ see precision note below
valueFormatted = valueWei / Math.pow(10, 18);
}CLAUDE.md states: "All test assertions must be deterministic — never use conditional checks that allow tests to pass on errors." Even though this conditional lives inside the CustomCode template string (not directly in an Since this is a simulation test, it will always hit the
Precision Issue in Fallback Code
// Before:
const valueWei = parseInt(rawValue, 16);
const valueFormatted = valueWei / Math.pow(10, 18);
// After:
const valueWei = BigInt(rawValue);
const valueFormatted = Number(valueWei) / Math.pow(10, 18); // acceptable for display
// or more precisely:
const valueFormatted = Number(valueWei * 10000n / BigInt(10 ** 18)) / 10000;Same note applies to the Comment Inconsistency ("deprecated" language)The third commit (
These contradict the deliberate Minor: Changeset SemverThis is marked Suggestion: Add a
|
There was a problem hiding this comment.
Pull request overview
Aligns the SDK’s documentation, type guidance, and test fixtures with EigenLayer-AVS PR #509, which changed ERC-20 Transfer event trigger output semantics so data.value is always raw base units and data.valueFormatted is the human-readable decimal string.
Changes:
- Updated trigger/template tests and fixtures to expect
valueas raw uint256 base-units string and to include/validatevalueFormatted. - Clarified
MethodCallType.applyToFieldsJSDoc/README guidance to discourage["Transfer.value"]usage for ERC-20 Transfer events (shared enrichment now providesvalueFormatted). - Added a changeset entry documenting the semantics/migration guidance.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/triggers/eventTrigger.test.ts | Removes Transfer decimals methodCalls and adds semantic assertions for value vs valueFormatted. |
| tests/templates/telegram-alert-on-transfer.test.ts | Removes applyToFields: ["Transfer.value"] and updates mocked Transfer output to include valueFormatted. |
| tests/executions/stepInput.test.ts | Prefers valueFormatted when present, preserving simulation fallback behavior. |
| packages/types/src/shared.ts | Adds @remarks guidance clarifying applyToFields usage and Transfer-specific caveat. |
| packages/sdk-js/README.md | Documents value vs valueFormatted semantics and migration guidance. |
| examples/example.ts | Adds comments describing new semantics near ERC-20 calldata encoding example. |
| .changeset/transfer-value-semantics.md | Adds release notes describing the semantics change and guidance. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| | `data.tokenSymbol` | `string` | Token symbol from contract metadata | Display | | ||
| | `data.tokenDecimals` | `number` | Token decimals from contract metadata | Reference | | ||
|
|
||
| **Breaking change in v1.x**: Prior versions populated `data.value` with the decimal-formatted amount when token metadata was available. As of EigenLayer-AVS PR #509, `data.value` is **always raw uint256 base units**, and `data.valueFormatted` is the new field for human-readable amounts. SDK consumers that read `data.value` for display **must migrate to `data.valueFormatted`**. |
There was a problem hiding this comment.
The README calls this a "Breaking change in v1.x", but @avaprotocol/sdk-js is currently v2.x (package.json shows 2.15.0). This version reference is inaccurate/misleading; consider changing it to "Breaking change relative to previous operator output" (or update it to the correct major) so readers don’t assume this only affects v1 consumers.
| **Breaking change in v1.x**: Prior versions populated `data.value` with the decimal-formatted amount when token metadata was available. As of EigenLayer-AVS PR #509, `data.value` is **always raw uint256 base units**, and `data.valueFormatted` is the new field for human-readable amounts. SDK consumers that read `data.value` for display **must migrate to `data.valueFormatted`**. | |
| **Breaking change relative to previous operator output**: Prior versions populated `data.value` with the decimal-formatted amount when token metadata was available. As of EigenLayer-AVS PR #509, `data.value` is **always raw uint256 base units**, and `data.valueFormatted` is the new field for human-readable amounts. SDK consumers that read `data.value` for display **must migrate to `data.valueFormatted`**. |
| callData: | ||
| "0xa9059cbb000000000000000000000000e0f7d11fd714674722d325cd86062a5f1882e13a{{ Number(demoTriggerName.data.value).toString(16).padStart(64, '0') }}", |
There was a problem hiding this comment.
The new comment states data.value is correct for ERC-20 transfer calldata, but the example still encodes it via Number(demoTriggerName.data.value). ERC-20 amounts are uint256 and can exceed JS safe-integer range, so Number(...) will silently lose precision and generate incorrect calldata. Use an integer-safe approach (e.g., BigInt-based) for hex encoding, and consider noting this constraint in the surrounding guidance.
| callData: | |
| "0xa9059cbb000000000000000000000000e0f7d11fd714674722d325cd86062a5f1882e13a{{ Number(demoTriggerName.data.value).toString(16).padStart(64, '0') }}", | |
| // Encode it with an integer-safe path (`BigInt`), because ERC-20 amounts can exceed | |
| // JavaScript's safe integer range and `Number(...)` would silently lose precision. | |
| callData: | |
| "0xa9059cbb000000000000000000000000e0f7d11fd714674722d325cd86062a5f1882e13a{{ BigInt(demoTriggerName.data.value).toString(16).padStart(64, '0') }}", |
Copilot + Claude review on PR #207: 1. tests/executions/stepInput.test.ts — replace the if/else fallback that violated the test determinism rule (CLAUDE.md). The CustomCode snippet now reads eventData.valueFormatted directly. Per EigenLayer-AVS PR #509 the operator always publishes valueFormatted for ERC-20 Transfer events when token decimals are known, so the fallback was dead code in practice and would have hidden a future regression that drops the field. Bonus: this also removes the parseInt(rawValue, 16) precision-loss path Claude flagged. 2. examples/example.ts — switch the ContractWrite calldata template from Number(...) to BigInt(...) for hex encoding the transfer amount. ERC-20 amounts can exceed JS safe-integer range (~9e15), and Number(...) silently truncates, producing incorrect calldata for any token amount above that. Add a comment explaining the constraint. 3. packages/sdk-js/README.md — replace "Breaking change in v1.x" (incorrect — SDK is currently v2.x) with version-agnostic wording "Breaking change in operator output (EigenLayer-AVS PR #509)". 4. tests/triggers/eventTrigger.test.ts and tests/templates/telegram-alert-on-transfer.test.ts — soften inline "is deprecated" comments to "is no longer needed for Transfer events". This aligns the inline guidance with the @remarks (not @deprecated) framing committed in 668002b, and explicitly notes the field is still valid for non-Transfer cases. Skipped: Claude's suggestion of a typed TransferEventData interface is a natural follow-up but out of scope for this docs/test PR. The minor-vs-major changeset semver debate is deferred to the release lead — the changeset body already calls out the consumer-visible behavior change prominently.
Patch bump for @avaprotocol/sdk-js covering the four review-fix changes in commit 8a29747: 1. examples/example.ts BigInt fix (real precision-loss bug for ERC-20 amounts > ~9e15 base units) 2. stepInput.test.ts determinism fix 3. README version-string fix (v1.x → operator-output framing) 4. Test comment language alignment with the @remarks decision @avaprotocol/types is not bumped since this commit doesn't touch the types package.
…511 fix
Three loopNode tests were silently passing because the operator returned
`success: false` whenever any iteration errored, which caused the tests'
`if (result.success && result.data)` blocks to be skipped entirely. The
broken assertions inside were never reached.
After EigenLayer-AVS issue #511 was fixed, the loop runner correctly
reports `success: true` with the per-iteration results array preserved
(including null entries for failed iterations). This exposed three
pre-existing test bugs that now fail loudly:
1. "should support sequential execution mode with timing verification"
and "should default to sequential execution mode when not specified"
- Used the wrong response shape: `result.data.data` instead of
`result.data` directly. The same file documents the correct
contract on line ~2000: "runNodeWithInputs returns data directly
as array, not wrapped in RunNodeResponseData".
- Used `item` and `index` in CustomCode source instead of `value`
(matching iterVal: "value"). `item` was always undefined in goja,
so each iteration silently produced `{item: undefined, ...}` which
pre-fix was discarded entirely (leading to `success: false`).
- Fix: read `result.data` directly as the iteration array; use the
correct variable names matching the loop config; remove the
synthetic `await new Promise(setTimeout(...))` line which goja
does not reliably support.
3. "should handle invalid runner configuration gracefully"
- Expected `success: false` for an "invalid runner configuration",
but the runner config (CustomCode that throws) is structurally
valid — the inner source just throws on every iteration.
- With #511 fix, the operator now correctly reports the loop as
having run to completion with all-null results. The semantic
question of whether all-iterations-failed should be `success: false`
or `success: true` with an all-null array is open and tracked in
a follow-up on EigenLayer-AVS.
- Fix: accept either outcome — the test verifies that the failure
is observable either via the top-level success flag OR via all-null
entries in the data array.
No SDK source changes. Pure test cleanup.
The withdraw tests perform real Sepolia UserOp round-trips (bundler → EntryPoint → on-chain confirmation) and use TimeoutPresets.SLOW (3 minutes) on the gRPC client. The default TIMEOUT_DURATION (60s) races the gRPC timeout and aborts the test before the chain call can complete, even when the underlying operation succeeds. Bump to 4 minutes to give the gRPC SLOW preset its full budget plus some headroom. This is structural mismatch in the test file, not related to any specific bug — the file's own comment "60 seconds to reduce flaky timeouts" was actively producing the flakes it tried to prevent.
Releases: @avaprotocol/sdk-js@2.16.0 @avaprotocol/types@2.12.0 [skip ci]
Summary
Documentation, type-comment, test-fixture, and changeset alignment with EigenLayer-AVS PR #509, which changed the semantics of
TransferEventResponse.valuefor ERC-20 Transfer event triggers.No production SDK source code reads
transferData.valuedirectly, so there is no compile-time API break. This is a documentation + consumer-guidance + test-fixture release.What changed in EigenLayer-AVS
For ERC-20 Transfer event triggers, the operator's shared event enrichment now publishes:
data.valuestring"1500000"for 1.5 USDC)data.valueFormattedstring"1.5")Previously
data.valueheld the formatted value when token metadata was available. SDK consumers that readdata.valuefor display will now get raw base units and break.Commits
44f1764a2afd43applyToFields ["Transfer.value"]from event trigger tests + add semantic regex assertions668002b@remarks, not a field-level@deprecated(avoids strikethrough on 30+ legitimate non-Transfer usages)Highlights
MethodCallType.applyToFieldsguidance, not deprecation (668002b)The first attempt put
@deprecatedonMethodCallType.applyToFields. Audit found 30+ legitimate non-Transfer uses acrosscontractRead.test.ts,loopNode.test.ts,eventTrigger.test.ts,uniswapv3_stoploss.test.ts, and the production wiring inmodels/trigger/event.ts/node/contractRead.ts/node/loop.ts(ChainlinkAnswerUpdated.current/latestRoundData.answer, ERC-20totalSupply, etc.). A blanket@deprecatedwould strikethrough all of them in IDE intellisense. The actual deprecation is value-specific (["Transfer.value"]), which TypeScript can't express at the type level. Replaced with an@remarksblock that documents the Transfer-specific guidance without polluting non-Transfer usages.Test cleanup + semantic contract assertion (
a2afd43)Removed the now-redundant
methodCalls: [{ methodName: "decimals", applyToFields: ["Transfer.value"] }]blocks fromtests/triggers/eventTrigger.test.ts(1 site) andtests/templates/telegram-alert-on-transfer.test.ts(2 sites). Added new semantic regex assertions ineventTrigger.test.tsthat pin the PR #509 contract:These catch any future regression where the operator accidentally swaps the two fields back.
Documentation surface (
44f1764)packages/sdk-js/README.md— new "Event trigger output:valuevsvalueFormatted" section with field semantics table and breaking-change calloutexamples/example.ts— comment onscheduleMonitorTransfer's ContractWrite step explaining when to usedata.value(raw, for ERC-20 calldata) vsdata.valueFormatted(display).changeset/transfer-value-semantics.md— minor bump for@avaprotocol/sdk-jsand@avaprotocol/typeswith the migration noteTest plan
yarn build— both@avaprotocol/typesand@avaprotocol/sdk-jsbuild cleanTransferEventtest fixtures use the new field semantics (value: "100500000"+valueFormatted: "100.5"for USDC)applyToFields: ["Transfer.value"]configurations are removed from active test code (only mentioned in deprecation comments)applyToFieldsusages still render normally (no@deprecatedstrikethrough)valuesemantics