Skip to content

feat: Eco API gateway as the default quote source (+ status via /v1/intents/status) - #31

Merged
carlosfebres merged 12 commits into
mainfrom
cfebres/api-gateway-quotes
Sep 22, 2026
Merged

carlosfebres merged 12 commits into
mainfrom
cfebres/api-gateway-quotes

Conversation

@carlosfebres

@carlosfebres carlosfebres commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator

Why

routes-cli quoted against internal hosts: quotes.eco.com by default, a solver-v2 host via SOLVER_URL, or the preprod quote service. Partners and Eco tooling are moving to the public front door, api.eco.com/v1/* (AWS API Gateway, x-api-key, typed by the published @eco-foundation/api-schemas package, staging twin at api.stag.eco.com). The CLI is the reference client for self-published intents, so it should speak that contract natively. Design: docs/plans/2026-09-14-api-gateway-quotes-design.md; plan: docs/plans/2026-09-14-api-gateway-quotes-plan.md.

Behaviour change

The default quote source is now the Eco API gateway (POST /v1/quotes). SOLVER_URL, QUOTES_API_URL and QUOTES_PREPROD keep working as explicit bypasses, in that order of precedence, so anyone who relies on them sees no change. status <hash> no longer requires --chain: without it the intent is looked up through GET /v1/intents/status; with it the on-chain Portal lookup runs exactly as before.

What changed

  • src/config/ — ECO_ENV=production|staging (default production → api.eco.com, staging → api.stag.eco.com), ECO_API_URL (host override), and per-environment keys ECO_API_KEY_PRODUCTION / ECO_API_KEY_STAGING sent as x-api-key to their own host only, with ECO_API_KEY as the fallback. Keys are per environment on purpose: production answers keyless today but rejects unknown keys (401 invalid-api-key, observed with a staging key), so one shared variable would break production for anyone holding only a staging key. getQuoteEndpoint() returns a discriminated union (solver-v2 | custom | gateway) and accepts a per-command env override.
  • src/eco-api/ — new EcoApiClient (quote, intentStatus). Auth failures (401/403 or Problem code: invalid-api-key, including API Gateway's bare resource-policy 403) throw RoutesCliError.apiError naming ECO_API_KEY; every other non-2xx and network failure throws EcoApiRequestError, a plain Error subclass, so the publish flow's existing manual-route fallback still applies. Debug output logs header names only, never values.
  • src/quote/gateway-quote.adapter.ts — pure mapping between the CLI's QuoteRequest/QuoteResult and V1QuoteRequest/V1QuoteResponse on the api-schemas 0.9.0 wire (the one eco-router #51 now serves): request type: exact-in, source.funder, required dappId and destination.recipient; response destination.amountOut, portal/prover/deadline from execution.intent. 0.9.0 returns no encodedRoute, so the adapter decodes the exact Portal.publishAndFund calldata the router built and publishes that route argument — the bytes the router hashed and signed. execution: null, a non-EVM funding transaction (SVM sources keep using SOLVER_URL), or a funding call that is not publishAndFund are hard errors, not silent fallbacks.
  • src/quote/quote.service.ts — dispatches on endpoint type; the solver-v2 and v3 branches are untouched. publish --env <production|staging> overrides ECO_ENV per run.
  • src/status/ + status.command.ts — gateway-backed lookup when no --chain; IntentStatus.state carries the raw gateway word (pending, filled, refundable, refunded, …), filled/settled count as fulfilled; status --env.
  • config list also redacts keys containing api_key/apikey.
  • @eco-foundation/api-schemas@0.9.0 added as a devDependency for import type only; pnpm build confirms nothing from it reaches the ncc bundle.
  • Docs: README configuration table, .env.example, and the routes-cli skill.

How verified

  • pnpm test:unit — 180/180 (15 suites; new: config endpoint precedence incl. per-env keys, apiError, EcoApiClient with mocked fetch, adapter incl. route-bytes decoding and the three hard errors, QuoteService dispatch, status mapping). pnpm test:integration — 9/9, including a spawned-CLI case: with only an unreachable ECO_API_URL set, the gateway quote fails and the manual route still dry-runs.
  • pnpm typecheck, pnpm lint (0 errors; pre-existing no-unsafe-assignment warnings on test mocks), pnpm format:check, pnpm build — clean.
  • Live against staging (router main@9e254c5, which carries router #51 + #52), nothing broadcast, ECO_API_KEY_STAGING set: publish -s base -d arc … --dry-run --env staging → api.stag.eco.com/v1/quotes 200, 0.5 → 0.5 USDC, source portal 0xEC000064… (Base), prover 0xec004Ab4…; publish -s arc -d base … → 200, source portal 0xEC002CA1… (Arc's dedicated Portal). status <hash> --env staging → filled. Without a key the same staging calls are a 403 from the gateway's resource policy, surfaced as Eco API error 403 … ECO_API_KEY_STAGING.
  • Live against production: api.eco.com still runs the digest-pinned pre-#51 image and answers 400 invalid-request (swapType … expected, source: Unrecognized key "funder") to the 0.9.0 shape. Production quotes through this CLI work once app-deploy bumps envs/prod-486940690197/core/router to an image carrying router #51 (and #52 for Arc).

Deploy order

Merge this after the production router digest bump, or production users of the default quote source get 400s until then. QUOTES_API_URL=https://quotes.eco.com/api/v3/quotes/single remains the bypass in the meantime.

Follow-ups (out of scope)

Send the gateway's prebuilt execution.transaction instead of encoding publishAndFund locally; source chains/tokens from /v1/chains and /v1/tokens; verify the quote signature.

Stacked on #30 (Arc mainnet). Merge that first; this PR's base then moves to main.

https://claude.ai/code/session_01ENQw9Ze2ZdghQGCNpKjo9L

…ly chains

Arc is Circle's L1 (native gas token USDC, 18 dp at the native layer; the 6 dp ERC-20 view is the 0x3600…0000 precompile). Register it as a production EVM chain with the dedicated CreateX Portal 0xEC002CA1… (version 2.10.0) and the fleet HyperProver, and map USDC on 5042 to the precompile.

The pinned viem (2.40) has no Arc definition, so EvmPublisher.getChain now falls back to defineChain() built from RAW_CHAIN_CONFIGS when viem/chains lacks the id, instead of throwing. The chains.config regression test that required every production EVM id to exist in viem/chains is relaxed to "exists in viem or is fully described for the fallback", with an explicit guard that Arc is currently the only such chain.

rpc.mainnet.arc.io is IP-allowlisted while the mainnet is private; set EVM_RPC_URL_5042 (e.g. an Alchemy URL) to reach it.

Claude-Session: https://claude.ai/code/session_01ENQw9Ze2ZdghQGCNpKjo9L
…urce

Adds the approved design (docs/plans/2026-09-14-api-gateway-quotes-design.md) and the @eco-foundation/api-schemas devDependency that supplies the public /v1 types it references.

Claude-Session: https://claude.ai/code/session_01ENQw9Ze2ZdghQGCNpKjo9L
… endpoint

getQuoteEndpoint now returns a discriminated union; the Eco API gateway is the default source, with SOLVER_URL, QUOTES_API_URL and QUOTES_PREPROD kept as explicit escape hatches. The design/plan docs are aligned with main's variable names.

Claude-Session: https://claude.ai/code/session_01ENQw9Ze2ZdghQGCNpKjo9L
…t status)

Auth problems (401/403, invalid-api-key) become RoutesCliError naming ECO_API_KEY; other problems and network failures become EcoApiRequestError so the publish flow's manual-route fallback still applies. Debug output logs header names only.

Claude-Session: https://claude.ai/code/session_01ENQw9Ze2ZdghQGCNpKjo9L
…lish --env

QuoteService dispatches on the configured endpoint type: gateway (default, via EcoApiClient + adapter) or the legacy solver-v2 / quote-service v3 paths, which are unchanged. The publish flow forwards --env and the source chain's configured portal (needed for non-EVM funding transactions).

Claude-Session: https://claude.ai/code/session_01ENQw9Ze2ZdghQGCNpKjo9L
…hain is given

--chain still forces the on-chain Portal lookup. Gateway results carry the raw status word (pending, filled, refundable, refunded, ...) on IntentStatus.state; filled/settled count as fulfilled.

Claude-Session: https://claude.ai/code/session_01ENQw9Ze2ZdghQGCNpKjo9L
…on test, docs

Documents ECO_ENV / ECO_API_URL / ECO_API_KEY, the new quote-source precedence, --env, and gateway-backed status in README, .env.example and the routes-cli skill.

Claude-Session: https://claude.ai/code/session_01ENQw9Ze2ZdghQGCNpKjo9L
@eco-ai-app

eco-ai-app Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

Overview

This PR integrates the public Eco API gateway as the default source for quotes and intent status in routes-cli, adding a new EcoApiClient and quote adapter. The QA agent noted that while the primary happy paths are well-tested, several error propagation paths and the updated StatusService.watch method lack test coverage.

Highlights (4)

Severity Location Description Agents
🟡 Warning tests/status/status.service.test.ts:43 Missing test coverage for StatusService.watch, which was updated to accept chain/env parameters and trigger on state changes. agent-qa
💡 Suggestion tests/eco-api/eco-api.client.test.ts:1 Missing test for intentStatus when the gateway returns an auth error (401/403). agent-qa
💡 Suggestion tests/quote/quote.service.test.ts:1 QuoteService.getGatewayQuote lacks tests verifying error propagation (e.g., hard-stop vs. fallback). agent-qa
💡 Suggestion tests/status/status.service.test.ts:55 StatusService.getStatus lacks tests for error paths when the gateway request fails. agent-qa

QA & Test Coverage · 1 warning · 3 suggestion

Severity Location Category Description
🟡 Warning tests/status/status.service.test.ts:43 missing test coverage The watch method in StatusService was updated to accept an optional chain and to propagate env, and now also triggers onUpdate when status.state changes (not just fulfilled). There are no tests for watch at all — neither for the gateway path, the on-chain path, nor the new state-change trigger logic. A regression in the polling/notification logic would go undetected.
💡 Suggestion tests/eco-api/eco-api.client.test.ts:1 missing edge case There is no test for intentStatus when the gateway returns an auth error (401/403 or invalid-api-key problem). The existing test only covers the happy path and a pending status entry. The error-handling path for intentStatus relies on the same toError helper tested via quote, but no test confirms it is exercised from the intentStatus call path.
💡 Suggestion tests/quote/quote.service.test.ts:1 missing edge case QuoteService.getGatewayQuote has no test for the error propagation paths: when ecoApi.quote throws a RoutesCliError (auth error, should stop) vs. EcoApiRequestError (should allow the publish flow to fall back). The existing tests only cover the success path.
💡 Suggestion tests/status/status.service.test.ts:55 missing edge case StatusService.getStatus via the gateway only has one happy-path test (a filled entry). There are no tests for the error path: what happens when ecoApi.intentStatus throws (e.g., EcoApiRequestError or RoutesCliError). The expected behavior — propagate vs. suppress — is untested.

Web Security

No issues found in this domain.

Blockchain Security

Skipped: not applicable to this PR — PR changes quote sourcing and status lookup but does not modify intent signing, on-chain publishing, portal interactions, or blockchain transaction validation.

Performance

No issues found in this domain.

Stats

Metric Value
Reviewers 4
Successful 3
Skipped 1
Failed 0
Total findings 4
Cross-domain 0
Tokens (in / out) 219342 / 4439

Automated review by Eco's specialized review team. Architecture, business logic, and correctness remain with the human reviewer.

@eco-ai-app eco-ai-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Inline findings

1 critical/warning finding surfaced inline. See the full summary in the AI review comment above.

const status = fromV1StatusEntries([entry({ status: word })], HASH);
expect(status.fulfilled).toBe(false);
expect(status.state).toBe(word);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Warning · missing test coverage · agent-qa

The watch method in StatusService was updated to accept an optional chain and to propagate env, and now also triggers onUpdate when status.state changes (not just fulfilled). There are no tests for watch at all — neither for the gateway path, the on-chain path, nor the new state-change trigger logic. A regression in the polling/notification logic would go undetected.

🤖 Prompt to fix with an AI coding agent
File: tests/status/status.service.test.ts, line 43 and beyond.

Problem: The `watch` method in `StatusService` was updated to accept an optional `chain` parameter, to propagate `env`, and to trigger `onUpdate` when `status.state` changes (not just when `fulfilled` becomes true). The test file currently has no test coverage for the `watch` method at all — neither for the gateway code path, the on-chain code path, nor the new state-change trigger logic. This means regressions in the polling/notification logic would go undetected.

Task: Add test cases for the `watch` method to cover:
1. The gateway path (when no chain is given, forwarding env)
2. The on-chain path (when a chain is given)
3. The new behavior where `onUpdate` is triggered when `status.state` changes

Make the smallest, most focused addition to the test file. Do not refactor existing tests. If you believe this finding is invalid or the watch method does not need testing in this context, add a one-line justification to .agent-coding-decisions.md in the worktree root instead.

…N / ECO_API_KEY_STAGING)

Production answers keyless but rejects unknown keys (401 invalid-api-key), so a staging key in a single ECO_API_KEY broke production quotes. Keys are now looked up per gateway environment, with ECO_API_KEY as the fallback.

Claude-Session: https://claude.ai/code/session_01ENQw9Ze2ZdghQGCNpKjo9L
…rom the router's publishAndFund calldata

eco-router adopted @eco-foundation/api-schemas 0.9.0 (router #51): the request discriminator is `type`, `funder` lives under `source`, `dappId` and `destination.recipient` are required, `destination.amount` became `amountOut`, and `execution.encodedRoute` is gone in favour of the exact Portal.publishAndFund transaction the router built. The adapter now decodes that calldata and publishes the same route bytes the router hashed and signed. Non-EVM funding transactions are refused (SVM sources keep using SOLVER_URL), so the sourcePortalFallback hook is dropped.

Verified on staging (router at 9e254c5): Base→Arc and Arc→Base both quote 200 through api.stag.eco.com. Production's router is still pinned to a 0.8.0-wire image and answers 400 to this shape until app-deploy bumps its digest.

Claude-Session: https://claude.ai/code/session_01ENQw9Ze2ZdghQGCNpKjo9L
@carlosfebres
carlosfebres added this pull request to stack #32 September 22, 2026 03:58
Base automatically changed from cfebres/arc-mainnet to main September 22, 2026 03:59
@carlosfebres
carlosfebres merged commit 8f054d9 into main Sep 22, 2026
1 check passed
@carlosfebres
carlosfebres deleted the cfebres/api-gateway-quotes branch September 22, 2026 03:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant