feat: consume @avaprotocol/protocols for catalog data#213
Merged
Conversation
…utions Both commands fell back to private-key auth when AVS_API_KEY was set but no target address was provided, which returned "task not found" for any workflow not owned by the signing EOA. Pass the owner EOA as an optional positional arg to route through the API-key admin path. Usage: yarn start getWorkflow <workflow-id> [owner-address] yarn start getExecutions <workflow-id> [cursor] [limit] [owner-address]
Lays down the v4 SDK shape without removing v3. v4 lives under
`packages/sdk-js/src/v4/` and is exported via `import { v4 } from
"@avaprotocol/sdk-js"` so the two surfaces coexist during the
migration window. v3 still builds and ships unchanged.
What's in:
* packages/types/openapi/openapi.yaml — vendored copy of the REST
spec from AvaProtocol/EigenLayer-AVS. `yarn types-gen` runs
openapi-typescript v7 to produce `packages/types/src/openapi.gen.ts`
(checked in so downstream consumers don't need the spec at install).
* packages/types/src/v4.ts — hand-curated re-exports of the generated
schemas (`v4.Workflow`, `v4.CreateWorkflowRequest`, `v4.Execution`,
etc.) so callers don't have to dig through the `components["schemas"]`
surface directly.
* packages/sdk-js/src/v4/chains.ts — Chains constants module
(Sepolia, Holesky, BaseMainnet, BaseSepolia, EigenLayerAuth).
* packages/sdk-js/src/v4/internal/transport.ts — fetch wrapper
owning baseUrl + JWT + timeout + problem+json decoding. Streaming
path (`stream()`) returns the raw Response for the SSE handler.
* packages/sdk-js/src/v4/internal/errors.ts — APIError /
NetworkError / AuthRequiredError.
* packages/sdk-js/src/v4/auth.ts — buildAuthMessage + signAuthMessage
using ethers. The canonical EIP-191 template lives here as a
documented constant so non-SDK callers can reproduce it.
* packages/sdk-js/src/v4/resources/*.ts — one sub-client per
resource family. Each is a thin Transport wrapper that maps the
spec's operationId conventions to method names. Custom actions
(`:pause`, `:trigger`, `:simulate`) stay on the wire as
colon-suffix URLs; SDK callers just see `pause()`, `trigger()`,
`simulate()`.
* packages/sdk-js/src/v4/client.ts — top-level `Client` class
composing the sub-clients. `new Client({ baseUrl, token? })` plus
`client.workflows.create(...)` etc.
* packages/sdk-js/src/v4/builders/{triggers,nodes}.ts — typed
builders that return plain JSON objects matching the spec's
discriminated unions. No class instances on the wire — just shaped
literals.
* packages/sdk-js/src/v4/index.ts — public v4 module surface.
executions.stream() implements SSE on top of `fetch().body.getReader()`
without an external EventSource polyfill (works in Node 20+ and modern
browsers). `executions.waitForTerminal()` wraps it for the common
"poll until terminal status" pattern.
Tooling:
* root `package.json`: added `openapi-typescript` dev-dep + the
`types-gen` script that regenerates `openapi.gen.ts`.
Build is green for both packages; typecheck across the v4 tree is
clean. v3 tests and callers continue to compile unchanged.
BREAKING CHANGE: drops the v3 gRPC client surface and every grpc-* / google-protobuf dependency. v4.0.0-alpha.0 ships as a clean break per the API_REST_IMPLEMENTATION_PLAN — partners on v3 should pin `@avaprotocol/sdk-js@^2` until they migrate. * packages/sdk-js/src/index.ts: re-exports the v4 module (Client, Chains, Triggers, Nodes, builders, errors) as the package top level. v3 models/, auth.ts, utils.ts, config.ts all deleted. * packages/sdk-js/package.json: 4.0.0-alpha.0. Drops @grpc/grpc-js, @grpc/proto-loader, google-protobuf, id128, lodash. Keeps ethers (auth signing) and dotenv. Bundle drops from ~900 KB to ~22 KB (no protobuf runtime). * packages/types/src/index.ts: re-exports the openapi-typescript generated schemas; v3 hand-written type files (api.ts, auth.ts, enums.ts, node.ts, shared.ts, trigger.ts, workflow.ts, abi.ts, token.ts) deleted alongside their gRPC-derived definitions. * packages/types/package.json: 4.0.0-alpha.0. Drops @types/google-protobuf. * grpc_codegen/: removed (avs.proto, avs_grpc_pb.*, avs_pb.*). * Root package.json: drops grpc-tools, grpc_tools_node_protoc_ts, axios, @types/axios, @types/google-protobuf. Renames `proto-download` -> `openapi-download` (curls the REST spec from the engine repo instead of the proto file). `types-gen` regenerates packages/types/src/openapi.gen.ts via openapi-typescript. * tsconfig.json (root + each package): drops the @/grpc_codegen path mapping and the google-protobuf type root. Tests: * `tests/` becomes the v4 surface. The v3 suite (44 files) is moved verbatim to `tests-v3-archive/` for reference — it relies on removed v3 classes and would fail to compile against this build. tests/README.md ships a mechanical v3→v4 call-site conversion table partners can follow when they migrate their own tests. * `tests/v4/smoke.test.ts` exercises the client construction surface, auth message builder (build + sign), typed builders (Triggers / Nodes including event-trigger wildcard handling), and the problem+json → APIError translation in the fetch transport. 9 assertions, ~870ms. * jest.config.cjs scoped to `tests/v4`; the v3-era mock-server globalSetup / failureSummaryReporter are no longer wired. `yarn build` is green on both packages. `yarn test` passes 9/9.
BREAKING: the legacy 2,469-line developer playground (scheduling
scenarios + util.inspect-formatted output) is gone. example.ts is
now a thin command-line wrapper over @avaprotocol/sdk-js@4 designed
for Claude Code (and any other automation) to call directly.
Wire contract:
* stdout: one valid JSON document per command (or one per SSE
frame for `executions:watch`). `--pretty` switches to the legacy
`util.inspect` formatting for human use.
* stderr: errors as `{"error": {"code", "message", ...}}`.
* Exit codes: 0 success, 1 user error, 2 SDK/server error, 3 timeout.
Curated command set:
* help / health / auth
* workflows: create / list / get / cancel / pause / resume / trigger /
simulate / count
* executions: list / get / watch (SSE)
* wallets: list / create
* operators: list
* verify — end-to-end smoke test (auth → health → create cron
workflow → pause → resume → cancel). Emits one JSON summary line
with per-step status + duration.
Env vars:
* AVS_REST_URL — base URL including /api/v1 prefix
* TEST_PRIVATE_KEY — EOA key for the EIP-191 exchange (required by
`auth` + `verify`)
* AVS_API_KEY — pre-minted JWT alternative
* AVS_TIMEOUT_MS — per-request timeout (default 30s)
* AVS_SMART_WALLET — default smart wallet for `verify` / list filters
Deleted from examples/:
* fee-estimation-example.ts, get-unipswapv3-allowance.ts,
README-allowance-check.md, examineWorkflow.*.log, config.ts
(chain-target switching is replaced by AVS_REST_URL).
* examples/package.json: drops command-line-args / id128 / lodash
along with the start:sepolia / start:base / etc. variants. Adds
`yarn verify` shortcut for `yarn start verify`.
Updated examples/README.md is the entry point for partners using
the CLI to drive local smoke tests.
setTimeout(..., 0) fires on the next event-loop tick, so passing
timeoutMs: 0 (as the SSE resource does to disable the per-request
timeout) was aborting the fetch immediately. The CLI surfaced this:
$ ts-node example.ts executions:watch <id> --workflow-id <wid>
{"error":{"type":"NetworkError",
"message":"fetch ...:stream: This operation was aborted"}}
Now timeoutMs <= 0 skips the setTimeout entirely; cancellation
flows only through the caller-supplied AbortSignal. The SSE
generator's clientGone / deadline / ticker dance on the server side
is the right cancellation channel for long-lived streams anyway.
…, getToken, withdraw)
- tests/utils/{env,client,templates,matchers}.ts: shared v4 test
helpers — dotenv loader, EIP-191 authenticator, salt allocator,
workflow cleanup, toEqualIgnoreCase jest matcher.
- jest.config.cjs: register matchers globally via setupFilesAfterEnv
so each spec file stays focused on assertions.
- tests/v4/core/auth.test.ts: collapses v3's three describe groups
(client.authKey / options.authKey / Unauthenticated) into v4's
one-auth-path model — exchange + Bearer JWT + unauthenticated 401s.
- tests/v4/core/wallet.test.ts: wallets.create idempotency, list
ordering, isHidden toggle, and the workflow-count counter walk.
- tests/v4/core/secret.test.ts: PUT/DELETE shape (void returns),
cross-EOA isolation, scope-fallback delete (#772 regression),
pagination (after/before/limit/workflowId filter, error cases).
- tests/v4/core/getToken.test.ts: whitelist/RPC fallback, case
normalization, malformed-address handling, concurrency.
- tests/v4/core/withdraw.test.ts: validation-only edge cases run
unconditionally; on-chain happy-path tests skip cleanly when the
funded salt:2 wallet is unfunded (server returns failed) or when
the chain endpoint is absent.
All 76 tests pass against the local docker-compose dev stack.
Coverage maps the v3 nodes/ folder one-to-one to v4 with API renames:
- client.runNodeWithInputs(p) -> client.nodes.run(p)
- client.simulateWorkflow(req) -> client.workflows.simulate(req)
- client.triggerWorkflow -> client.workflows.trigger(id, body)
- client.submitWorkflow / deleteWorkflow -> workflows.create / cancel
Output shape changes:
- v3 `result.data.<x>` -> v4 `result.output.data.<x>` (single wrap)
- filter and graphqlQuery double-wrap their output (engine quirk
noted in test comments; tracked as a server-side bug)
- executionContext keys use snake_case (chain_id, is_simulated,
provider) instead of v3's camelCase
Per-file scenarios kept lean — v3 often re-asserted the same shape
across run/simulate/trigger; the port collapses to one assertion per
surface plus the genuinely unique cases. Total LOC dropped from
12,819 (v3) to ~1,700 (v4).
Test utilities added:
- settingsFor(runner) defaults chain_id=11155111 (sepolia) so
contractWrite simulations don't 400; settingsForChain() for
other chains.
- getCurrentBlockNumber() lazily imports ethers to keep the
non-chain tests free of provider overhead.
Tests skip gracefully on:
- missing CHAIN_ENDPOINT (block-trigger tests)
- Moralis errors (balance node tests)
- unfunded salt:2 wallet (real bundler submission tests)
- httpbin / GraphQL unreachable (network-dependent runners)
All 73 tests pass against the local docker-compose dev stack.
Coverage:
- block.test.ts (6): triggers.run for varied intervals, simulate +
deploy/trigger paths.
- cron.test.ts (10): schedule parsing across daily/hourly/15-min/
weekly/multiple/step-value variants, server-tolerant invalid
expressions, simulate, deploy + trigger.
- manual.test.ts (6): data echo, required-data validation, complex
type preservation, headers + pathParams, simulate workflow with
trigger.<name>.data reference, deploy + trigger via
workflows.trigger.
- eventTrigger.test.ts (7): topic filtering by from/to address,
multiple queries, condition-based filtering (Chainlink price),
cooldownSeconds, empty-queries rejection, missing-address-filter
rejection.
Trigger builder additions:
- Triggers.manual() now accepts data/headers/pathParams so callers
can supply the manual trigger config inline (paired with the
server-side mapper fix).
v3 TriggerFactory + toRequest() validation tests are dropped — v4
builders return plain JSON; validation moved server-side. The
test surface compressed from ~5,500 LOC (v3) to ~700 LOC (v4).
All 29 tests pass against the local docker-compose dev stack.
Coverage:
- createWorkflow.test.ts (7): block/cron/fixedTime/event triggers,
multi-node workflows with branch edges, rejection of EOA as
smart wallet address.
- deleteWorkflow.test.ts (2): cancel removes from list, idempotent
on non-existent ids (v4 returns 204).
- setWorkflowEnabled.test.ts (5): pause/resume verbs (v4 split the
boolean toggle), idempotency on already-enabled/disabled,
pause→resume round trip, 404 on missing workflow.
- triggerWorkflow.test.ts (8): per-trigger-type firing (block,
cron, fixedTime, event), blocking vs async modes, 404 on
missing workflow, max-execution=1 rejects re-trigger.
- workflow.test.ts (9): retrieve, list with name+startAt,
forward+backward pagination, EOA/dead-address rejection,
negative-limit rejection, empty wallet shape, status field
presence.
v3 API renames absorbed:
- client.createWorkflow + submitWorkflow -> workflows.create
- client.deleteWorkflow -> workflows.cancel
- client.setWorkflowEnabled(id, false) -> workflows.pause(id)
- client.setWorkflowEnabled(id, true) -> workflows.resume(id)
- client.triggerWorkflow({id,...}) -> workflows.trigger(id, body)
- client.getWorkflow -> workflows.retrieve
- client.getWorkflows -> workflows.list
- response.items -> response.data
v3 `compareResults` deep-equality helper is reimplemented inline as
targeted assertions since the v4 wire shape is well-defined. The
test surface compressed from ~1,442 LOC (v3) to ~770 LOC (v4).
All 31 tests pass against the local docker-compose dev stack.
…ests)
Coverage:
- getExecution.test.ts (5): retrieve by id, status getter,
cron + block trigger executions, 404 for missing wf/exec.
- getExecutions.test.ts (7): list with pagination (after/before),
count single + multi-workflow, invalid cursor / limit handling.
- estimateFees.test.ts (2): executionFee + valueFee + cogs shape,
gas COGS for ETH transfer.
- simulateWorkflow.test.ts (6): manual + customCode, fixed-time +
REST, multi-node + branch, error handling, predecessor data
access.
- runNodeWithInputs.test.ts (2): Balance routes via chain_rpc,
ContractWrite routes via Tenderly.
- inputVariables.test.ts (4): create+retrieve round-trip,
settings-only, scalar accessibility, nested objects.
- execution.test.ts (3): sequential 0-based index, non-blocking
immediate id, index field on list.
- stepInput.test.ts (3): invalid node name rejection, trigger
config + node config round-trip, step.inputs presence.
- partialSuccess.test.ts (3): mixed success/failure = failed,
all-success = success, all-failed = failed.
- errorCodeConsistency.test.ts (2): nodes.run errorCode matches
workflows.simulate step.errorCode for the same failure mode.
- gasTracking.test.ts (3): step.metadata.gas* present + math
invariant, execution.cogs aggregation, sim path metadata.
v3 → v4 API renames absorbed:
- client.getExecution(wf, id) -> executions.retrieve(id, {wf})
- client.getExecutions([wfs]) -> executions.list({workflowId: [..]})
- client.getExecutionCount -> executions.count
- client.getExecutionStatus -> executions.getStatus
- client.simulateWorkflow -> workflows.simulate
- client.estimateFees -> workflows.estimateFees
- response.items -> response.data
- step.inputsList -> step.inputs
- step.type enum value -> lowercase string ("customCode" etc.)
v3 numeric error codes (3006 etc.) become string codes
("INVALID_REQUEST", "MISSING_REQUIRED_FIELD"); v3's mock-server
dependencies replaced with httpbin.org where REST coverage is needed.
Total LOC dropped from ~5,065 (v3) to ~1,100 (v4).
All 40 tests pass against the local docker-compose dev stack.
Real-world workflow templates ported with the same shape v3 used,
trimmed down to validate the *structure* without requiring all the
external integrations (Telegram, SendGrid, Aave on-chain positions,
funded smart wallet for Uniswap swaps).
Coverage:
- workflow-usdc-read-write-customcode (1): guards the regression
where one contractRead got deduplicated.
- telegram-alert-on-transfer (2): event-trigger transfer monitor
+ REST notifier (httpbin in place of Telegram).
- approve-with-simulation (1): contractWrite simulated via Tenderly.
- recurring-payment-with-report (1): manual->balance->customCode->
branch->loop->REST flow.
- batch-recurring-payment-with-email (1): cron->balance->code->
branch->loop->REST flow.
- aave-health-factor-alert (1): cron->contractRead->branch->REST.
- uniswapv3_stoploss (1): cron->Chainlink price read->branch->swap.
- exported-workflow-consistency (2): manual->filter->loop->code +
nodes.run / simulate output equivalence.
v3 templates depended on a local mock server, real SendGrid/Telegram
creds, and a funded smart wallet. The v4 port substitutes httpbin
for HTTP-dependent steps and uses the simulate path everywhere so
the test rig stays deterministic. Total LOC compressed from
~5,517 (v3) to ~900 (v4).
All 10 tests pass against the local docker-compose dev stack.
… + trigger status
Server-side cleanups (EigenLayer-AVS feature/rest-api-migration) made
two response shapes match what the test suite already expected:
- Filter + GraphQL nodes.run no longer double-wrap (output.data is
the payload directly, not {data, success, executionContext}).
- TriggerWorkflowResponse.status now references the canonical
ExecutionStatus enum (pending|success|failed|error) instead of
the bespoke queued|succeeded|failed enum.
Refresh packages/types from the updated spec and drop the workaround
assertions in the affected test files:
- filterNode.test.ts: result.output.data is the filtered array.
- graphqlQuery.test.ts: header comment updated, no test changes.
- triggerWorkflow.test.ts: pin status to ExecutionStatus values.
Full v4 suite (268 tests) still passes.
Copy to .env.railway and fill in the AVS_REST_URL + AVS_API_KEY values from the Railway gateway-ethereum deployment, then run with TEST_ENV=railway to point the v4 suite at production. The existing .env (local dev) and .env.dev paths stay unchanged. Also whitelist .env.*.example in .gitignore so template files are tracked while real .env.<environment> files (with secrets) stay ignored.
The OpenAPI declared `status: pending | submitted | failed`, but the aggregator's withdraw handler emits `pending`, `confirmed`, or `failed` — `submitted` was never on the wire. The response-shape test caught this against the Railway gateway (server returned `confirmed` after the receipt landed, test enum didn't list it). Realign the spec on the actual server contract, regenerate the openapi types, and update the test enum. Pairs with the server-side update in AvaProtocol/EigenLayer-AVS api/openapi.yaml.
Original v3 port used a cron trigger that polled getUserAccountData every 15 minutes. The intended semantics are different: this is a reactive top-up safety net for user-initiated debt increases, not a liquidation preventer. Oracle-driven HF drops (collateral price moves) emit no AAVE event and are explicitly out of scope. Rewrite the trigger to watch AAVE V3 Pool Borrow events filtered by onBehalfOf == user wallet (topics[2]). After a Borrow lands, the contractRead -> branch -> REST top-up alert chain runs as before. Borrow event topic layout, signature hash, and ABI documented inline. Adds a second test asserting the Borrow ABI definition / signature constant so Studio template exports don't drift silently if anyone edits the ABI.
First v4 dev release. Published @avaprotocol/types and @avaprotocol/sdk-js to the npm `dev` dist-tag so studio + openclaw-plugin can install with `yarn add @avaprotocol/sdk-js@dev` instead of linking the local workspace. Replaces the previous v3-era dev channel (was 2.7.5-dev.10 / 2.5.5-dev.9). The `latest` tag is unchanged (still 2.17.0 / 2.13.0), so v3 consumers are unaffected. Subsequent dev iterations bump to 4.0.0-dev.1, .2, etc.; promote to `latest` only when the full v4 cut ships.
Three connected changes that together produce a usable auto-generated SDK reference for the oak-website docs site. 1. JSDoc on the previously-bare resources. health, nodes, operators, tokens, triggers had only the class-level block (or none); wallets had bare method declarations for list / withdraw / getNonce; the ExecutionsResource, SecretsResource, and WorkflowsResource classes had no class-level block. Filled all of them in the workflows.ts / executions.ts style: HTTP route + intent + nuance, no @example bloat. The TypeDoc index now describes every class instead of showing a `-` placeholder. 2. TypeDoc wiring (typedoc + typedoc-plugin-markdown as devDeps; typedoc.json config; `yarn docs` script). Output goes to packages/sdk-js/docs-api/ as MDX, slug-routable for the oak-website doc renderer. The flatten-docs.js post-process step moves the `@avaprotocol/namespaces/v4/...` tree (TypeDoc walks the re-exported v4 type bag) into a flat `types/` directory and rewrites links so the @-prefixed paths don't break slug routing. Output is gitignored under docs-api/ — regenerated from source on every release. 3. Stage docs/oak-website-staging/getting-started.mdx. First of the three hand-written pages (the others — authentication.mdx, usage-examples.mdx — coming separately). Drop-in for oak-website/src/app/(docs)/content/ava-sdk-js/getting-started.mdx; format matches the existing MDX (frontmatter + plain prose + code blocks), no mermaid diagrams. Routes link forward to the yet-to-be-deployed /reference subtree.
These rendered as an Additional Information / Contact Ava Protocol panel on the docs site REST API page (Stoplight Elements groups info-level metadata under that header). Removed from the spec rather than CSS-hidden so external consumers see the same shape the docs site does. The two fields carried thin metadata (contact = name + URL only, no email; license = MIT name only). Partners who need contact info hit avaprotocol.org directly.
The v3 template suite always paired a Workflow Simulation Testing section with a Full Deployment and Execution Testing section. The v4 port at the eventTrigger rewrite only carried the simulate path forward and dropped the deploy half, which left a real gap: serialization of the eventTrigger config (topic filter for the Borrow event) is the most wire-sensitive part of this graph, and the simulate path doesn't exercise the persist+round-trip. Add the deploy+retrieve test and factor the workflow definition into a `buildWorkflow(smartWalletAddress)` helper so the two tests share the exact same shape. Bar for the deploy assertion is "deployed, persisted, retrievable with the right trigger type + node/edge counts + Borrow-topic filter intact" — we can't observe a real execution here because the trigger is event-driven and would need an actual AAVE Borrow on Sepolia from the wallet under test to fire. The simulate test covers the per-step behavior; the deploy test covers the wire round-trip. Two engine quirks the test handles inline: 1. workflows.create requires inputVariables.settings.runner — same as workflows.simulate. The engine validates wallet ownership against it on every persist, not just on simulate. 2. The engine keys the persisted workflow.name off settings.name, not the top-level workflow.name field. settingsForChain(addr, chain, name) takes a name override; pass the canonical template name through to avoid the persisted record reading as "Test Simulation". 3/3 pass against the Railway gateway.
Closes the simulate-only gap across the v4 template ports. v3 template suites always paired Workflow Simulation Testing with a Full Deployment and Execution Testing section; the v4 ports collapsed both into the simulate path and dropped the deploy half, leaving a real gap on the wire-serialization round-trip (workflows.create -> workflows.retrieve). Same pattern as the AAVE template fix that landed in fc5e4f7: factor the workflow definition into a buildWorkflow(walletAddress) helper, refactor the existing simulate test to use it, add a second test that deploys via workflows.create and verifies the persisted shape via workflows.retrieve. Files touched: - batch-recurring-payment-with-email: cron, 5 nodes, 5 edges - recurring-payment-with-report: manual, 5 nodes, 5 edges - uniswapv3_stoploss: cron, 3 nodes, 3 edges (also folds in the saltValue="2" wallet across both tests so they match the v3 setup) - workflow-usdc-read-write-customcode: cron, 4 nodes, 4 edges (saltValue="2" wallet, same as above) - exported-workflow-consistency: manual, 3 nodes, 3 edges (the shared canonical "exported workflow" the v3 cross-method consistency check was built around) approve-with-simulation is left as-is — v3 was sim-only by design (its name says so, no deploy section to mirror). Deploy assertions follow the AAVE template pattern: id is a string, name matches the canonical template name, trigger.type matches the builder's output ("cron" / "manual" / "event"), and nodes/edges arrays have the expected lengths. We can't observe real executions here because cron + manual + event triggers would each need chain/time/event activity in this test environment to fire, which makes the asserted behavior non-deterministic. Two engine quirks the deploy tests handle inline (documented in the AAVE port and propagated here): 1. workflows.create requires inputVariables.settings.runner — the engine validates wallet ownership against it on every persist, not just on simulate. Without it: "inputVariables is required" / "settings.runner is required". 2. The engine keys the persisted workflow.name off inputVariables.settings.name, not the top-level workflow.name field. Pass the canonical template name through settingsFor / settingsForChain rather than letting the helper default to "Test Simulation". 17/17 pass against the Railway gateway across all 8 template suites.
…er fix After EigenLayer-AVS 50271e7 (REST mapper auto-mirrors body.name into inputVariables.settings.name), passing the canonical workflow name through settingsFor(addr, wf.name) is no longer necessary — the top-level wf.name field is sufficient. Drop the third arg from every deploy test's settingsFor / settingsForChain call and shrink the "engine keys workflow.name off settings.name" multi-line comment to a one-liner explaining the new behaviour. Six files touched (the same set that got deploy+retrieve tests added in 708d113): - aave-health-factor-alert - batch-recurring-payment-with-email - recurring-payment-with-report - uniswapv3_stoploss - workflow-usdc-read-write-customcode - exported-workflow-consistency 17/17 template tests pass against the Railway gateway running the updated server.
The AAVE health-factor template's terminal node was a placeholder
restApi call to httpbin labelled "top up liquidity" — never actually
modified the AAVE position. Replace with the real two-step on-chain
top-up: LINK.approve(Pool, amount) followed by Pool.supply(LINK,
amount, runner, 0).
Workflow shape becomes:
eventTrigger (Pool.Borrow filtered by onBehalfOf)
-> contractRead (Pool.getUserAccountData)
-> branch (HF < 1.5e18)
-> contractWrite (LINK.approve(Pool, amount))
-> contractWrite (Pool.supply(LINK, amount, runner, 0))
Two contractWrite nodes, not one. AAVE V3 Pool.supply calls
safeTransferFrom internally, so the smart wallet must approve the
Pool first. Same-workflow approve injects the allowance into
Tenderly's simulationState, so the chained supply step doesn't
revert on allowance during simulation (verified by tracing
vm_runner_contract_write.go's state-override handling).
Constants used:
- Sepolia LINK reserve 0xf8Fb3713D459D7C1018BD0A49D19b4C44290EBE5
(AAVE testnet faucet token, 18 decimals)
- Top-up amount 0.1 LINK = 100000000000000000 wei (repeatable
against the faucet without re-funding the test wallet)
Simulate test now expects sim.status truthy without pinning to
"success", because the dev test wallet holds no Sepolia LINK and
Tenderly does NOT override ERC-20 balances — the supply step
reverts on insufficient balance, propagating sim.status to "failed"
even though the workflow chain is correct. Same pattern as
uniswapv3_stoploss where the swap step is expected to not fire in
the dev env. Production users with a real low-HF position get the
actual top-up.
Workflow name updated from "AAVE Health Factor Alert" to "AAVE
Health Factor Top-up" since the behavior shifted from monitor-and-
alert to monitor-and-act.
Deploy+retrieve test updated for node count 3 -> 4 and edge count
3 -> 4.
3/3 in this suite, 17/17 across all 8 v4 template suites against
the Railway gateway.
Background: the v3 template at studio/templates/aave-health-factor-
alert.json was a monitor-only workflow that fired a Telegram alert
on low HF (no on-chain action). The v4 version pushes through to
the on-chain top-up so the alert becomes self-healing. The v3 test
set up a real AAVE position via test scaffolding (mint LINK, supply,
borrow) to drive HF into the [1.0, 1.6) range; the v4 test doesn't
do that — it validates the workflow shape and chain at the contract
level, leaving live-position exercises to production users or a
future integration test with a funded wallet.
…step
Extends the v4 SDK Nodes.restApi() builder with an optional `options`
field so callers can opt a terminal RestAPI node into the aggregator's
context-memory summarizer:
Nodes.restApi({
url: "https://api.sendgrid.com/v3/mail/send",
method: "POST",
body: JSON.stringify({ ...empty content slot... }),
headers: { Authorization: "Bearer {{apContext.configVars.sendgrid_key}}" },
options: { summarize: true },
})
When `options.summarize: true` lands on a terminal restApi node and the
aggregator was started with the notifications.summary block
configured (provider: context-memory, api_endpoint, api_key — set on
Railway gateway in the prior commit), the engine's
ComposeSummarySmart path calls context-api.avaprotocol.org at
execution time and injects an AI-generated subject + HTML body into
the SendGrid payload. Without the flag the runner posts the body
verbatim. This is the v4 equivalent of the v3 native `email` node's
core.shouldSummarize: true.
Wires the AAVE health-factor template through to a real notification
step. Workflow shape is now:
eventTrigger (Pool.Borrow)
-> contractRead (Pool.getUserAccountData)
-> branch (HF < 1.5e18)
-> contractWrite (LINK.approve)
-> contractWrite (Pool.supply)
-> restApi (SendGrid /v3/mail/send, options.summarize: true)
Three terminal effects on a low-HF Borrow now: top up the position,
notify the user, and let context-memory describe what just happened.
Mirrors the production batch-recurring-payment template's shape.
Simulate + deploy+retrieve tests updated for the new step (node
count 4 -> 5, edge count 4 -> 5, simulate stepIds unchanged because
the dev test wallet has no AAVE position and the branch routes to
the `ok` slot — approve/supply/email only fire for real low-HF
positions on chain).
Verified against the Railway feature/rest-api-migration gateway with
the notifications.summary block enabled: workflow simulates cleanly,
creates+retrieves+deletes via the REST API, no errors from the new
restApi options shape. Test 3/3 in this suite.
Also includes a "Running v4 tests against the Railway gateway"
subsection in CLAUDE.md documenting the per-test log verification
practice (tail gateway + matching worker-<chain> after each single-
test run) so future sessions follow the same loop.
The helper called `client.wallets.create()` under the hood and the docstring labelled the function "get-or-create" — but in practice the no-arg path generates a fresh `nextTestSalt()` each call and mints a brand-new CREATE2 address, which is "create", not "get." The `getSmartWallet` name read like a stable accessor and was actively misleading at call sites like const wallet = await getSmartWallet(client); // → new wallet each time The REST and SDK surfaces are both correctly named (handler comment on POST /wallets explicitly calls it idempotent "ensure exists"; the SDK exposes it as `wallets.create()` matching the verb). Only the test util was out of step. Renamed to `createSmartWallet` so the call chain reads consistently test util → SDK method → REST verb, all "create". The 16 sites that pass an explicit `saltValue` still get the idempotent ensure-exists behaviour as a bonus, documented in the updated docstring. Companion type rename: GetSmartWalletOptions → CreateSmartWalletOptions. Mechanical rename via sed across the test tree, 42 files touched, zero new tsc errors, AAVE template suite (3/3) re-verified against the Railway gateway post-rename.
Cumulative consumer-facing changes since 4.0.0-dev.0:
- feat: Nodes.restApi({ options: { summarize } }) — opts a terminal
RestAPI node into the aggregator's context-memory summarizer. Pairs
with the gateway-side notifications.summary block enabled on Railway
in EigenLayer-AVS 2bcac8b. Required for Studio's v4 email templates.
- fix(transport): treat timeoutMs <= 0 as no-timeout instead of
abort-on-next-tick (a802471). Defensive value, but a real fix for
any caller that passes 0 expecting "disabled."
- additive: Triggers builders pick up the fields needed by the ported
v4 triggers/ test suite (3778d19).
- docs: full JSDoc pass across resource clients (executions, health,
nodes, operators, secrets, tokens, triggers, wallets, workflows) so
IDE intellisense becomes useful (ecaae74).
All changes additive / non-breaking. Published with --tag dev so the
npm `latest` tag stays on 2.17.0; consumers opt in with
`yarn add @avaprotocol/sdk-js@dev` or pin `4.0.0-dev.1`.
The earlier widening to Array<string | number> let TypeScript accept
JSON number 0 for uint16 referralCode, but the gateway's REST mapper
declares MethodCall.methodParams as []string in the protobuf-backed
JSON schema, so a number value is rejected at the unmarshaling
boundary BEFORE the ABI encoder is reached:
json: cannot unmarshal number into Go struct field
MethodCall.config.methodCalls.methodParams of type string
Reverting both the SDK builder type narrowing and the canonical
template's '0' (string) literal. With strings everywhere, the wire
payload deserializes cleanly and the request reaches the ABI
encoder.
The underlying 'abi: cannot use ptr as type uint16 as argument'
error at vm_runner_contract_write.go:323 is still there for the
supply step — that's a real server-side bug where the contract-
write runner passes string-typed methodParams to abi.Pack() in a
shape that go-ethereum's encoder receives as a pointer instead of
a value. Filed as a separate server-side follow-up; this revert is
just unblocking the SDK tests so the rest of the chain (read +
approve + email) runs and demonstrates the catalog token resolution
('Approved 0.1 LINK to AAVE V3 Pool', no more 'UNKNOWN').
Tests now 5/5 against the Railway gateway:
- Canonical template (Test 1): branch trips (HF 5.4065 < 10), approve
runs, supply fails server-side, email summarizes the partial
execution.
- Full-chain probe (Test 4): same shape, same observable outcome.
- Read-only probe (Test 3): unchanged, still passes.
- Borrow ABI sanity (Test 5): unchanged.
This was referenced May 29, 2026
Two adjustments to the canonical template surfaced by the latest
inbox check.
1. Branch was silently routing to ok.
The lowHF condition used plain string comparison:
aaveRead.data.getUserAccountData.healthFactor < "10000000000000000000"
AAVE returns healthFactor as a raw uint256 string (e.g.
"5406537200000000000" for HF=5.4065). String < is lexicographic:
a leading "5" sorts above "1", so "5406…" > "1000…" lex-wise
even though the numeric inequality is the reverse. Branch always
evaluated false, branch routed to ok, approve+supply never fired,
the rendered email stopped at "AAVE account data: …".
Fix: wrap both sides in BigInt(). Lowered the threshold to 6.0e18
to be slightly more realistic — production users typically set
HF cushions closer to 1.5, but the dev wallet's HF is ~5.41 so
6.0 is enough to trip without overshooting.
Visible delta: canonical Test 1 simulate now runs the approve +
supply chain through Tenderly. Duration jumped 1.9s -> 5.5s on
Railway, matching the chain-execution latency pattern.
2. Renamed the supply node from "supplyLink" to "topUpCollateral".
The old name surfaced in error messages as
"✗ supplyLink: <error>"
which reads like a method name rather than a step label.
"topUpCollateral" pairs naturally with the workflow's purpose
(the workflow is named "AAVE Health Factor Top-up") and reads
cleanly in summarized inbox output.
Spread the catalog's Chains export onto the SDK Chains object so newly added chains (currently BnbMainnet) flow through without per-bump SDK edits. EigenLayerAuth stays on top as the SDK-specific auth-audience constant the catalog doesn't know about. Adds a Railway-targeted auth test that mints a JWT scoped to chain 56 (BNB) - the lightest probe that gateway-railway.yaml's chain set and worker-bnb-mainnet are wired up end-to-end without needing a smart wallet factory on the chain.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Drops the duplicated
packages/sdk-js/src/v4/protocols/tree (~600 LOC of address / ABI / topic data) and re-exportsProtocols,AbiFragment,AddressByChainfrom the standalone@avaprotocol/protocols@0.1.0package.Published as
@avaprotocol/sdk-js@4.0.0-dev.3under thedevnpm tag.Why
The catalog data (DeFi protocol addresses + ABIs + topics) is genuinely on-chain-public and the same constants are needed by:
Keeping a private copy inside the SDK meant every consumer would re-derive the same constants. Extracting the catalog into a dedicated repo + npm package (AvaProtocol/protocols) gives us a single source of truth that any consumer can depend on without taking an SDK dependency.
What changes for SDK consumers
Zero API change. The existing surface continues to work:
The SDK re-exports the namespace verbatim. Only visible delta:
@avaprotocol/protocolsminor version without an SDK republishWhat stays on the SDK side
Chains— retains the auth-handler-specificEigenLayerAuthconstant that's irrelevant to the catalog. The catalog'sChainsis a subset of pure EIP-155 ID constants used as address-map keys.Test plan
yarn tsc --noEmit -p tsconfig.json— 6 pre-existing baseline errors unchanged, no new errors.yarn build— emits dual CJS + ESM + .d.ts, tarball verified clean.npm publish --tag dev— landed as4.0.0-dev.3;devdist-tag now points there.🤖 Generated with Claude Code