Skip to content

catch up#1

Open
Corey-Code wants to merge 957 commits into
vidulum:masterfrom
trezor:master
Open

catch up#1
Corey-Code wants to merge 957 commits into
vidulum:masterfrom
trezor:master

Conversation

@Corey-Code

Copy link
Copy Markdown
Member

No description provided.

pragmaxim and others added 30 commits April 7, 2026 10:07
* chore(currencyRateTicker): remove strings.toLower normalization to avoid converting base58 tron addresses

* fix(fiatRates): test expectations

* refactor(public_test): extend the setup http server method by passing currency rates tickers to properly test the /tickers/ endpoint

* feat(public_test): extend current fiat rates tests to cover all tickers endpoint for both HTTP and websocket

* chore(tron): change platformVsCurrency to "usd" cuz coingecko doesnt support "trx"
#1464)

* feat(test-websocket): add tron presets and ability to switch between tron/ethereum using button

* chore(test-websocket): remove hard-coded values in the inputs for ethereum as they are in the presets

* feat(tron): add preset values for estimateFee in test-websocket.html
* feat(tron): add account activation recognizition

* tests(tron): add account creation transaction to rpc testdata
… voting rewards (#1463)

* chore(tron): do not set tx.value for delegation, trc-10 transfers

* feat(tron): freeze/unfreeze to be count as out/ingoing correctly

* refactor(balanceHistory): balanceHistory code refactor and Tron staking semantics

* feat(tron): add support for tron voting reward to be counted as ingoing tx in balanceHistory

* fix(tron tests): tron rpc tx expectations for trc10 and delegation transactions

* tests(tron): account creation detection

* tests(tron): add tron transactions to RPC testdata for unfreeze, withdraw, votereward, account creation

* feat(tron): use unfreeze balance field for stake 1.0 unfreeze tx
* chore(erc4626): adding ws endpoint to fetch 4626 data for token

* chore(erc4626): evolve getErc4626 into getContractInfo endpoint

* chore(erc4626): unify getAccountInfo with getContractInfo

* chore(erc4626): cleanup

* chore(erc4626): add EVM-only guard for getContractInfo

* chore(erc4626): add EVM-only guard for getAccountInfo

* chore(erc4626): add tron spec to ContractInfoResult, Token and TokenTransfer
pragmaxim and others added 30 commits June 17, 2026 15:03
Fetching the confirmed nonce on every address request adds a second
eth_getTransactionCount("latest") backend call for every EVM address,
which most callers do not need. Gate it so the extra call is made only
when the client opts in.

- REST: GET /api/v2/address/<addr>?confirmedNonce=true
- WebSocket: getAccountInfo request gains a confirmedNonce boolean
- both map to AddressFilter.WithConfirmedNonce

When the flag is off (default), behavior is identical to before this
feature: only the pending nonce is fetched (single call) and the
confirmedNonce field is omitted from the response. When the flag is on,
pending + latest are fetched in a single JSON-RPC batch round-trip
(sequential fallback when the client does not support batching), and
confirmedNonce is included. Tron is unaffected: its single NonceAt call
already returns the latest nonce, so it ignores the flag.

openapi.yaml gains the confirmedNonce query parameter and the
WsAccountInfoReq.confirmedNonce property; blockbook-api.ts is updated in
lockstep. Server tests cover both gated-on (REST + WebSocket) and the
gated-off default. Verified by build, unit tests, and the OpenAPI parity
typecheck.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…aths

Address review follow-up on the confirmed-nonce feature.

Best-effort confirmed nonce: once a client opts in with confirmedNonce=true,
a transient failure of the "latest" lookup must not take down the whole
address/getAccountInfo response (balance, txs, tokens). EthereumTypeGetNonces
now returns (pending, confirmed, confirmedOK, err): the pending nonce is
required (error is fatal), while the confirmed nonce is best-effort — if only
the latest lookup fails it is logged and reported as confirmedOK=false, and
the worker simply omits confirmedNonce from the response. This applies to the
batched, sequential-fallback, and alternative-provider paths alike.

Tests:
- new bchain/coins/eth unit tests for EthereumTypeGetNonces covering: gated-off
  fetches pending only (no "latest" call), gated-on batched success, confirmed
  failure is best-effort (pending returned, no error), pending failure and batch
  transport failure are fatal, and the sequential fallback (client without
  BatchCallContext) incl. its best-effort confirmed path
- new OpenAPI e2e test GetAddressConfirmedNonceEVM (enabled for EVM coins):
  asserts the opt-in gate (no confirmedNonce without the flag), and when opted
  in validates the field and that confirmed <= pending, skipping gracefully on
  backends where the feature is not deployed yet

No wire/contract change: confirmedNonce remains an optional response field, so
openapi.yaml and blockbook-api.ts are unchanged. Verified by build, unit tests,
the OpenAPI parity typecheck, and render_grafana.py --check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirror the REST GetAddressConfirmedNonceEVM e2e check on the WebSocket
getAccountInfo path: assert confirmedNonce is absent without the opt-in
flag, and when confirmedNonce:true is passed validate the field and that
confirmed <= pending, skipping gracefully on backends where the feature
is not deployed yet. Enabled for the EVM coins in tests/tests.json.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
watchMempoolTxs ran on a ticker for the process lifetime with no way to
stop it. Add a stop channel closed by a nil-safe, idempotent shutdown(),
wired into EthereumRPC.Shutdown alongside consensusMonitor.shutdown() and
mirroring the existing consensusVersionMonitor lifecycle.

Harmless for the process-lifetime singleton today, but prevents a
goroutine leak if per-chain teardown is ever added and aligns with the
recent "cancel in-flight RPCs on shutdown" work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A fresh address (no indexed data) skipped the nonce fetch entirely, so an
opted-in caller got nonce:"0" but confirmedNonce omitted - ambiguous with
"feature not deployed". Since every tx sender is recorded in the index,
ca==nil means the account never sent a tx, so both nonces are genuinely 0;
synthesize confirmedNonce:"0" locally (no backend call) for symmetry with
the indexed path. The default (flag-off) response is byte-for-byte
unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
reconcileMempoolTxs evicted timed-out txs via RemoveTransaction, which
only deletes from the alternative provider's own cache map. The
provider_missing / mined / nonce_superseded paths instead use
removeMempoolTx, which prefers the removeTransactionFromMempool callback
and therefore clears BOTH the main b.Mempool (the addrDesc->txid index
that drives address/WS mempool listings) and the alternative cache.

As a result a timed-out tx was dropped from the cache but lingered as a
ghost "pending" entry in the main mempool until that mempool's own,
independently configured (and by default longer) timeout expired - and
with private/Blink relays there is no backend re-query to clear it
lazily. The nonce-superseded healing commit explicitly stated evictions
go "via removeMempoolTx (both Blockbook mempool and cache), consistent
with the mined/provider_missing removals", but the two timeout branches
diverged from that contract.

Use removeMempoolTx in both timeout branches so every reconcile eviction
path removes the tx from both the main mempool and the alternative cache.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AlternativeSendTxProvider.getNonces is the production nonce path for
private/Blink relay coins and duplicates getNoncesRPC's batch +
best-effort logic, but no test exercised it: every nonce test in
nonce_test.go leaves alternativeSendTxProvider nil and hits the primary
RPC. A regression in its pending-fatal vs latest-best-effort handling
would have gone uncaught.

Add a batch-aware JSON-RPC httptest server (the batched path uses
BatchCallContext, which the existing method-keyed mock cannot serve) and
mirror the getNoncesRPC cases: pending-only when gated off (asserting the
latest tag is not queried), batched pending+confirmed when gated on,
best-effort confirmed failure (pending returned, confirmedOK=false, no
error), fatal pending failure, and fatal batch transport failure. Green
under -race.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The reconcile tests fixed mempoolTxsTimeout at time.Hour with a tx
timestamped ~2 check periods ago, so timedOut was always false and
neither timeout-eviction branch ran. The PR repeatedly calls the timeout
path the "safety net" that bounds premature-eviction risk, yet it had
zero coverage.

Add a table-driven test that shrinks mempoolTxsTimeout so the cached tx
is timed out, covering both branches: provider error + timed out, and
still-pending (nonce not superseded) + timed out. Both assert via
assertReconcileOutcome, which requires the removeTransactionFromMempool
callback to have fired - so this also guards the preceding fix that
routes timeout eviction through removeMempoolTx (reverting it makes this
test fail). Green under -race.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
decodeConfirmedNonce has two best-effort failure paths: a lookup/transport
error, and a successful call that returns unparsable hex. Only the former
was tested (errs:{"latest":...}); the decode branch - which feeds the
public confirmedNonce field when a backend answers 200 with garbage - was
unverified on the RPC path.

Add batched and sequential-fallback cases that return "0xZZ" for the
latest tag with no error, asserting pending is still returned with
confirmedOK=false and no error surfaced.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…etMetrics

SetMetrics has a single caller (blockchain.go NewBlockChain), which runs
it before bc.Initialize(); the alternative provider is only created later
inside Initialize -> InitAlternativeProviders, which already passes
b.metrics into NewAlternativeSendTxProvider. So b.alternativeSendTxProvider
is always nil when SetMetrics runs and the inner assignment never executed
- dead and redundant.

Remove it. This also makes provider.metrics write-once (set only at
construction, before SetupMempool starts the reconcile goroutine that
reads it), removing the only post-construction mutation site and the
latent data race it would become if the construction ordering ever
changed. Documented the invariant in a comment so the branch is not
reintroduced.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The PR converted every Tron address assertion to ?confirmedNonce=true,
removing the prior full-body match that implicitly proved confirmedNonce
absence. Re-add a no-flag case for TronAddrTZ asserting a substring that
spans the nonce->tokens boundary, which matches only when confirmedNonce
is not inserted after nonce - mirroring the EVM gated-off check and
covering Tron's opt-in gate end to end.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…'s first page

GetAddressTxids / GetAddressTxs / WsGetAccountInfo derived the sample
address as the first vin/sender of a recent tx, then asserted that tx
appears in the address's first page (addressPageSize=10). On busy
account-based chains this breaks: a Tron USDT transfer's sender is a hot
wallet with thousands of txs, so the sampled tx sits hundreds of entries
deep and is paged out, failing the assertion (the recipient, by contrast,
is low-traffic and lists it on page 1).

Replace the blind sender pick with a self-verifying resolver
(getSampleAddressTx / sampleAddressTxOrSkip): it walks a tx's participants
- token recipients and outputs first, then inputs - and selects the first
one whose first page actually lists the tx, probing additional recent
txs if needed. The three address-listing tests now assert against a pair
that is structurally guaranteed consistent, instead of assuming the
sender lists the tx early.

Verified against the live Tron backend: the resolver picks the token
recipient and both the txids and txs first pages contain the sample tx;
simulated over several current tip-block txs it finds a valid pair every
time. No Blockbook/production code changed; typecheck passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The e2e suite dials the dev backends directly, so it can't run from a
network that only permits outbound traffic via HTTP(S)_PROXY (e.g. a
sandboxed/corporate environment): fetch() gets EAI_AGAIN and the ws client
can't connect. Node's fetch (undici) and the ws library both ignore the
proxy env by default.

Wire both transports through the proxy when one is configured, and no-op
otherwise (zero effect in CI / direct-network dev):
- runner.ts: when proxyFromEnv() is set, install an undici ProxyAgent as
  the global fetch dispatcher (requestTls drops cert verification for the
  proxied target under OPENAPI_INSECURE_TLS, since dev backends are
  self-signed); otherwise keep the previous plain insecure-TLS Agent.
- context.ts: the ws client doesn't use undici's dispatcher, so pass an
  https-proxy-agent as its `agent` (proxyFromEnv/wsProxyAgent helpers,
  shared with runner.ts).
- declare https-proxy-agent (already present transitively via @redocly/cli)
  as a direct devDependency.

Verified by running the full Tron suite through a proxy: 35 ok, 3 skip,
0 fail (incl. the HTTP GetAddressTxids/GetAddressTxs and the ws
WsGetAccountInfo). typecheck passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…timeout

The reconcile loop added in #1562 evicted a still-pending tx the moment a single
eth_getTransactionByHash returned empty ("provider_missing"). Blink-style
private/MEV relays stop surfacing a still-mineable tx via getTransactionByHash
while it stays broadcast, so a tx that showed "Unconfirmed" on both sender and
recipient disappeared from both ~1-2 min after send - before either the 5-min
alternative cache timeout or the 10-min mempool timeout. Pre-#1562 there was no
proactive reconcile and the tx stayed visible until mined or timed out.

Treat an empty getTransactionByHash as non-authoritative: defer eviction to the
absolute cache timeout, keeping `mined` and `nonce_superseded` as the only
deterministic early evictions (both are positive, irreversible on-chain facts).
Also run the nonce-superseded check even when the provider no longer surfaces
the tx, so a provably spent nonce is still cleaned up promptly. The new metric
label `provider_missing_pending` marks the kept-not-evicted case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three independently-merged PRs each assigned the same Grafana panel ids
(321-325) on master, so the render --check fails (Grafana refuses to import a
dashboard with duplicate panel ids) - including this PR's CI. Panel ids are
internal to Grafana and only need to be unique; panels.yaml joins by
x-panel-key, not by id.

Keep the REST API row at its contiguous 321-326 block and renumber the two
colliding groups into the free range (max id was 331):
  - sync.eth_alternative_mempool_reconciliation: 321 -> 332
  - row.fiat_rates + 4 panels:                    321-325 -> 333-337

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Grafana render --check is pure static analysis (no secrets, no backend) -
the same profile as the coin backend-artifact lint - and its only dependency is
the pinned PyYAML. Keeping it in the self-hosted unit-tests job had two
downsides: it was the sole reason that job installed Python deps, and - because
unit-tests is skipped for fork PRs - dashboard regressions in fork PRs went
unvalidated.

Fold both render_grafana checks into the lint job (renamed backend-artifact-lint
-> lint, on the ephemeral GitHub-hosted runner that already runs for forks) and
drop the now-unused Python setup from unit-tests, leaving it pure Go.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
base_archive runs blockbook against op-geth on the OP-Stack chain, whose
non-sequencer nodes have no usable public mempool - the node rejects
eth_subscribe newPendingTransactions with "invalid subscription type for
subscribe", which aborted startup (exitCodeFatal -> systemd crash loop).
Set disableMempoolSync: true so the pending-tx subscription is skipped,
matching the polygon_archive/bsc_archive precedent; mempool/sendtx data is
sourced from the alternative mempool provider instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The reconcile loop added in #1562 only had a decision counter labeled by
action, which shows the rate of each reconcile outcome but not the thing
#1573 is really about: how long an unconfirmed tx actually survives in the
alternative-provider-only send-tx cache, and why it eventually disappears.
That lifetime is non-deterministic (mined fast, or held to the 5/10-min
timeout, or evicted early), so it needs to be measured, not reasoned about.

Add two metrics and the panels to read them:

- eth_alternative_mempool_tx_residence_seconds (histogram by action): the
  age of a cache entry at the moment it leaves the cache, per exit reason.
  This is the signal that would have surfaced the regression #1573 fixed -
  provider_missing evictions clustering at ~1-2 min instead of near the
  timeout - and shows how quickly relays actually confirm.
- eth_alternative_mempool_cache_size (gauge): current cache depth, sampled
  at the end of each reconcile cycle.

Route every cache-exit through the same observation so neither metric
undercounts: the reconcile-loop removals (via a new evictMempoolTx helper),
GetTransaction's read-path timeout eviction, and handleMempoolTransaction's
RBF replacement. Without the latter two the timeout series would be biased
low and RBF lifetimes invisible. Also refresh the now-stale reconciliation
counter help/panel text (it never mentioned provider_missing_pending, kept
or skipped_fresh).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…#1578)

* mq existence check added to tronrpc.go to fix mempool initialization without it

* Add polling only mode to tipWatchdog

* tipWatchdogTick now know about case when b.mq == nil

* Apply 1s floor in tipWatchdog when mq is disabled to avoid hammering the RPC if averageBlockTimeMs is misconfigured

* tests added

* spaces -> tabs

* chore(tron): remove dead code capping the block polling interval

* chore(tron): avoid race condition when initializing MQ subscription

* chore(tron): briefer comment for bchain.MQ

---------

Co-authored-by: Henrik Mesropyan <henrik.m@nownodes.io>
Co-authored-by: Jakub Jerabek <xjerab28@stud.fit.vutbr.cz>
Blockbook reads operator secrets and per-coin tunables (INFURA_API_KEY,
COINGECKO_API_KEY, <coin>_* limits, ...) from its process environment at
runtime. These were provided out of band via systemd DefaultEnvironment in
/etc/systemd/system.conf, hand-maintained per host.

Add an optional EnvironmentFile=-/etc/blockbook/blockbook.env to the blockbook
service unit so a single file can supply them per host. The leading '-' keeps
the file optional, so public/community installs and existing hosts that still
use DefaultEnvironment are unaffected when it is absent; a unit-level value also
overrides a DefaultEnvironment value for the same key, allowing a no-flag-day
migration off system.conf.

On the dev deploy path (bbcli -> deploy.yml on self-hosted runners) the file is
materialized from the BB_BLOCKBOOK_ENV GitHub Actions secret before the service
restart; the step is a no-op when the secret is unset so it is safe to merge
before the secret exists. Prod hosts will render the same file via bbctl.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The set of BB_ build-time variable prefixes was duplicated in three places that
had to be hand-synced: the Makefile forwarding regex, the export-env-vars action
normalizer, and templates.go. A drift failed silently (e.g. a var exported to
GITHUB_ENV but absent from the Makefile regex never reaches the build).

Introduce build/bb-build-var-prefixes.txt as the canonical list. The Makefile
now derives the forwarding match from it (output verified identical to the old
regex; BB_BUILD_ENV continues to be forwarded separately via -e on each docker
run), and the action reads it for coin-alias normalization. templates.go keeps
its typed constants but a new test (TestCanonicalPrefixesCoverTemplateConsumers)
asserts every prefix it relies on is present in the file, so drift now fails in
CI instead of at build time.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The materialize step wrote the env file with `tee` (umask default, ~0644) and
only then chmod'd it to 0600, leaving a brief window where the secrets were
world-readable on the runner. Pre-create the file 0600 root:root with
`install /dev/null`; tee then truncates it without changing the mode, closing
the window and dropping the now-redundant chown/chmod.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…sconfigured

EVM coins that set `alternative_estimate_fee` to "infura"/"1inch" require the
matching API-key env var (INFURA_API_KEY / ONE_INCH_API_KEY). Previously a
missing key only logged an error and silently reverted to default fee
estimation, so a misconfigured node started and served wrong fees.

Propagate the provider-construction error from initAlternativeFeeProvider
through the existing InitAlternativeProviders path (already checked by every EVM
coin), so a node whose config selects a provider it cannot construct fails to
start instead of degrading silently. A coin with no provider configured stays a
no-op.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The fail-fast added in c468fa3 makes EVM chain initialization abort when a
configured alternative_estimate_fee provider cannot be built because its
API-key env var is unset. Integration tests render the production coin configs
and call chain.Initialize(); avalanche selects "infura", but the test
environment is never given INFURA_API_KEY (the CI Makefile only forwards BB_*
vars), so TestIntegration/avalanche=main/rpc now fails during init.

Supply harmless placeholder keys for the EVM fee providers at the start of the
integration run so the chain initializes and falls back to default fee
estimation; the background fee fetch just 401s and is ignored. These tests
assert RPC/sync behavior, not fees, and a real key in the environment is still
respected. Production fail-fast is unchanged and covered by the unit tests in
bchain/coins/eth.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
BB_BLOCKBOOK_ENV was redundant (BB already stands for Blockbook) and did not
convey that its content is the runtime environment file. Rename the GitHub
Actions secret reference to BB_RUNTIME_ENV, consistent with the new
BB_ADMIN_USER/BB_ADMIN_PASSWORD runtime variables.

The GitHub repository secret must be renamed to match; until it is, the
materialize step no-ops and leaves blockbook.env untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The internal server binds all interfaces by default (configs/coins/*:
internal_binding_template is ":<port>"), so its /admin pages and
state-mutating POST handlers (internal-data refetch, contract-info updates)
were reachable unauthenticated by any peer that could reach the port. Gate
the whole /admin surface behind HTTP Basic auth, credentials from
BB_ADMIN_USER/BB_ADMIN_PASSWORD (delivered via blockbook.env).

Basic auth (rather than a bearer token) lets the admin HTML pages and forms
be used directly from a browser via its native login prompt, and still works
for scripts via curl -u. The surface is fail-closed: unless both variables
are set, every /admin route returns 503. /metrics, the status page and
static assets are unaffected, so Prometheus scraping is unchanged.

- A trailing-slash catch-all (/admin/) is gated too, so unregistered or
  trailing-slash paths can't fall through to the unauthenticated index.
- Credentials are trimmed of surrounding whitespace at load (a stray newline
  in blockbook.env won't lock the operator out) and only their SHA-256
  digests are retained; user and password are compared in constant time with
  both comparisons evaluated before being combined.

Transport is the existing self-signed cert, intended for a trusted,
firewalled internal segment (see docs/env.md).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The /admin/ trailing-slash catch-all added to gate unknown admin subpaths
also returned 404 for a bare "/admin/", so navigating to the natural
trailing-slash URL dead-ended after login instead of showing the admin index
(served at /admin, no trailing slash). Redirect "/admin/" to the canonical
"/admin" while still returning a gated 404 for any genuinely unknown
/admin/* path, so the subtree stays authenticated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…di, nile) (#1575)

* fix(tests): keep testnet network suffix in matchable test name

getMatchableName/matchable_name collapsed every "<coin>_testnet*" key to
"<coin>=test", so sibling testnets (sepolia vs hoodi, testnet vs testnet4)
shared one subtest name. The deploy connectivity regex built for one testnet
then also selected its siblings, so a deploy could fail because an unrelated
testnet's blockbook was unreachable.

Preserve the network suffix after "_testnet" so the mapping is injective
("ethereum_testnet_sepolia" -> "ethereum=test_sepolia"), and anchor each name
in the connectivity regex so "bitcoin=test" no longer substring-matches
"bitcoin=test4". Add Go and Python regression guards and update the docs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(integration): add tron_testnet_nile integration tests

Mirror the mainnet tron entry (connectivity http, api, rpc). The rpc fixture
tests/rpc/testdata/tron_testnet_nile.json already exists, and the BB_DEV_*
RPC/API repository variables for tron_testnet_nile are already provisioned, so
no new GitHub variables are required.

Adding the connectivity group lets the deploy pipeline gate tron_testnet_nile
on backend + Blockbook reachability, and the api group enables the post-deploy
OpenAPI e2e suite for it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(integration): add ethereum_testnet_sepolia and _hoodi integration tests

Give both Ethereum testnets connectivity (http + ws) and the no-fixture EVM api
list (the same list used by avalanche/arbitrum/optimism). The fixture-backed EVM
tests (ERC4626, contract-info, protocols) are intentionally omitted because they
require tests/openapi/fixtures/<coin>.json with mainnet contract addresses, which
only exist for ethereum and base; no rpc group is added for the same reason
(no tests/rpc/testdata/<coin>.json fixture yet).

The BB_DEV_* RPC/API repository variables for both testnets already exist (the
RPC URLs resolve via the *_archive fallback), so no new GitHub variables are
required. Connectivity gates their deploys and api enables the OpenAPI e2e suite.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(integration): support disabling a coin via tests.json "disabled" flag

tron_testnet_nile's backend and Blockbook dev instance are not currently
deployed, so its connectivity/rpc/api tests cannot pass. Rather than delete
the test definitions, mark the coin "disabled": true in tests/tests.json and
teach every consumer to skip it:

- tests/integration.go skips disabled coins with a visible SKIP
- tests/openapi/src/config.ts drops disabled coins from e2e selection
- .github/scripts/runner.py treats disabled coins as non-deployable

Remove the flag to re-enable. Documented in AGENTS.md and covered by Go and
Python unit tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(connectivity): gate backend/node RPC checks behind BB_TEST_BACKEND_CONNECTIVITY

The connectivity suite dialed both the raw backend/node RPC (rpc_url) and the
Blockbook API for every coin. The node RPC endpoints are only routable from the
CI/CD network, so local runs failed on unreachable nodes even when Blockbook
itself was healthy.

Gate the node RPC checks behind BB_TEST_BACKEND_CONNECTIVITY: off by default
(local runs verify Blockbook reachability only), on in CI. The flag is set in
the testing.yml connectivity job and the deploy.yml post-deploy connectivity
step, forwarded into the build container by the Makefile, and exposed locally
via a --backend-connectivity flag on run-integration-tests.sh. Documented in
AGENTS.md and covered by a unit test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(connectivity): route Blockbook websocket dial through egress proxy

BlockbookHTTPIntegrationTest's HTTP client already honors the proxy via
http.ProxyFromEnvironment, but the gorilla websocket.Dialer in
BlockbookWSIntegrationTest did not, so the WS check failed to resolve the
Blockbook host from behind an egress proxy (where it is only reachable through
the proxy). Set Proxy: http.ProxyFromEnvironment on the dialer to match the
HTTP client and the OpenAPI e2e suite; a no-op when no proxy is configured.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(ci): resolve build/deploy coin aliases against the context, not cwd

resolve_build_selection and resolve_deploy_selection normalized the "_"→"-"
coin alias with normalize_coin_name(Path.cwd(), ...), so the underscore form of
a hyphenated coin (e.g. ethereum_classic → ethereum-classic) only resolved when
the process happened to run from the repo root. Run from any other directory
(e.g. the unit tests) the alias was left unresolved and rejected as an unknown
coin.

Resolve against the already-loaded context.all_coins instead, which is derived
from the workspace the context was built from, so selection no longer depends
on the working directory. Adds a deploy-side regression test alongside the
existing build one.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(integration): drop fiat-rate e2e tests for ETH testnets (no price feed)

ethereum_testnet_sepolia and ethereum_testnet_hoodi disable fiat rates in
configs/coins (fiat_rates-disabled instead of fiat_rates), because valueless
testnet ETH has no CoinGecko feed — the rate downloader never runs. The
WsGetCurrentFiatRates e2e test therefore hard-failed with "No tickers found\!",
and GetCurrentFiatRates/GetTickersList/GetMultiTickers/WsGetFiatRatesForTimestamps/
WsGetFiatRatesTickersList only ever skipped.

Remove the six fiat-dependent tests from both testnet entries; the non-fiat
GetBalanceHistory/WsGetBalanceHistory stay. Mainnet ethereum (fiat enabled) and
tron_testnet_nile (fiat enabled via mainnet TRX's CoinGecko id) keep them.

Verified: OpenAPI e2e now passes for both coins (54 ok, 4 unrelated skips, 0 fail).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(e2e): cover GetContractInfoNonVaultEVM on ethereum_testnet_sepolia

Add an OpenAPI fixtures file for Sepolia with two canonical, permanently
deployed ERC20s — Chainlink LINK (0x7798…4789) and Circle USDC
(0x1c7D…7238) — and enable GetContractInfoNonVaultEVM for the coin. The test
GETs /api/v2/contract/<addr>?protocols=erc4626 and asserts the contract
resolves and is NOT mis-flagged as an ERC4626 vault (the strict opt-in gate).

Both addresses were verified indexed on the deployed Sepolia Blockbook
(correct symbol/decimals, protocols absent); e2e passes (29 ok, 1 unrelated
skip, 0 fail).

Not added for ethereum_testnet_hoodi: that testnet has no token ecosystem
indexed, so there is no stable contract to anchor a fixture. The remaining
EVM contract/protocol/ERC4626 tests still require curated on-chain vault and
holder fixtures that do not exist on these testnets.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(ci): harden testnet test plumbing after review

Follow-up hardening surfaced by a deep review of the testnet test work; no
behavior change on the happy path.

- deploy_plan.build_connectivity_regex: fail closed on an empty name list. An
  empty "()" alternation matches the empty string, so `go test -run` would
  select EVERY connectivity subtest instead of none. The caller already filters
  to a non-empty set; this makes the deploy-gating helper safe on its own.
- runner_test: pin canonical_coin_name's check ordering so underscore-native
  coins (e.g. base_archive, ethereum_testnet_sepolia) are returned unchanged
  rather than rewritten to a hyphen variant — a reorder regression would
  otherwise stay green.
- config.ts: document the "disabled" stay-in-sync contract on the actual
  filtering logic (it previously lived only on the types.ts declaration) and
  explain why the explicit-OPENAPI_COINS guard is needed alongside the filter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
… convention

Token transfer / token balance responses dropped the `decimals` field
whenever it was 0 (the JSON tag carried `,omitempty`), so clients could
not distinguish a genuine 0-decimal token from missing metadata and had
to guess (trezor-suite defaults to 18). See #1577.

- Remove `,omitempty` from api.Token.Decimals and api.TokenTransfer.Decimals
  so the field is always present (matches the existing ContractInfoResult).
- In getContractDescriptorInfo, when a contract's metadata cannot be read
  on-chain, fall back to the coin's default decimals (18 for ERC-20)
  instead of surfacing/persisting an ambiguous 0. Genuinely 0-decimal
  tokens always carry a resolved (handled) standard, so they keep their 0.
- Mirror the always-present field into blockbook-api.ts and mark decimals
  required in openapi.yaml for Token and TokenTransfer (parity typecheck).
- Add a regression test asserting decimals is serialized even when 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add assertTokenDecimals and wire it into GetAddressTokensEVM (Token) and
GetTransactionEVMShape (TokenTransfer), making the #1577
expectation explicit on top of the schema's `required: decimals`. The check
reuses already-fetched responses (no extra requests), runs on all EVM coins,
and additionally enforces a non-negative integer, which the schema does not.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

8 participants