Home / Cardano Foundation / cardano-rosetta-java
Apr 09, 7-8 PM (0)
Apr 09, 8-9 PM (0)
Apr 09, 9-10 PM (0)
Apr 09, 10-11 PM (0)
Apr 09, 11-12 AM (0)
Apr 10, 12-1 AM (0)
Apr 10, 1-2 AM (0)
Apr 10, 2-3 AM (0)
Apr 10, 3-4 AM (4)
Apr 10, 4-5 AM (0)
Apr 10, 5-6 AM (2)
Apr 10, 6-7 AM (0)
Apr 10, 7-8 AM (0)
Apr 10, 8-9 AM (0)
Apr 10, 9-10 AM (3)
Apr 10, 10-11 AM (1)
Apr 10, 11-12 PM (2)
Apr 10, 12-1 PM (4)
Apr 10, 1-2 PM (2)
Apr 10, 2-3 PM (0)
Apr 10, 3-4 PM (0)
Apr 10, 4-5 PM (0)
Apr 10, 5-6 PM (0)
Apr 10, 6-7 PM (0)
Apr 10, 7-8 PM (0)
Apr 10, 8-9 PM (0)
Apr 10, 9-10 PM (0)
Apr 10, 10-11 PM (0)
Apr 10, 11-12 AM (0)
Apr 11, 12-1 AM (0)
Apr 11, 1-2 AM (0)
Apr 11, 2-3 AM (0)
Apr 11, 3-4 AM (0)
Apr 11, 4-5 AM (0)
Apr 11, 5-6 AM (0)
Apr 11, 6-7 AM (0)
Apr 11, 7-8 AM (0)
Apr 11, 8-9 AM (0)
Apr 11, 9-10 AM (0)
Apr 11, 10-11 AM (0)
Apr 11, 11-12 PM (0)
Apr 11, 12-1 PM (0)
Apr 11, 1-2 PM (0)
Apr 11, 2-3 PM (0)
Apr 11, 3-4 PM (0)
Apr 11, 4-5 PM (0)
Apr 11, 5-6 PM (0)
Apr 11, 6-7 PM (0)
Apr 11, 7-8 PM (0)
Apr 11, 8-9 PM (0)
Apr 11, 9-10 PM (0)
Apr 11, 10-11 PM (0)
Apr 11, 11-12 AM (0)
Apr 12, 12-1 AM (0)
Apr 12, 1-2 AM (0)
Apr 12, 2-3 AM (0)
Apr 12, 3-4 AM (0)
Apr 12, 4-5 AM (0)
Apr 12, 5-6 AM (0)
Apr 12, 6-7 AM (0)
Apr 12, 7-8 AM (0)
Apr 12, 8-9 AM (0)
Apr 12, 9-10 AM (0)
Apr 12, 10-11 AM (0)
Apr 12, 11-12 PM (0)
Apr 12, 12-1 PM (0)
Apr 12, 1-2 PM (0)
Apr 12, 2-3 PM (0)
Apr 12, 3-4 PM (0)
Apr 12, 4-5 PM (0)
Apr 12, 5-6 PM (0)
Apr 12, 6-7 PM (0)
Apr 12, 7-8 PM (0)
Apr 12, 8-9 PM (0)
Apr 12, 9-10 PM (0)
Apr 12, 10-11 PM (0)
Apr 12, 11-12 AM (0)
Apr 13, 12-1 AM (0)
Apr 13, 1-2 AM (2)
Apr 13, 2-3 AM (1)
Apr 13, 3-4 AM (0)
Apr 13, 4-5 AM (0)
Apr 13, 5-6 AM (0)
Apr 13, 6-7 AM (0)
Apr 13, 7-8 AM (0)
Apr 13, 8-9 AM (0)
Apr 13, 9-10 AM (0)
Apr 13, 10-11 AM (0)
Apr 13, 11-12 PM (0)
Apr 13, 12-1 PM (0)
Apr 13, 1-2 PM (0)
Apr 13, 2-3 PM (0)
Apr 13, 3-4 PM (0)
Apr 13, 4-5 PM (0)
Apr 13, 5-6 PM (0)
Apr 13, 6-7 PM (0)
Apr 13, 7-8 PM (1)
Apr 13, 8-9 PM (0)
Apr 13, 9-10 PM (0)
Apr 13, 10-11 PM (0)
Apr 13, 11-12 AM (0)
Apr 14, 12-1 AM (0)
Apr 14, 1-2 AM (0)
Apr 14, 2-3 AM (0)
Apr 14, 3-4 AM (0)
Apr 14, 4-5 AM (0)
Apr 14, 5-6 AM (1)
Apr 14, 6-7 AM (5)
Apr 14, 7-8 AM (0)
Apr 14, 8-9 AM (0)
Apr 14, 9-10 AM (0)
Apr 14, 10-11 AM (0)
Apr 14, 11-12 PM (0)
Apr 14, 12-1 PM (0)
Apr 14, 1-2 PM (0)
Apr 14, 2-3 PM (0)
Apr 14, 3-4 PM (0)
Apr 14, 4-5 PM (0)
Apr 14, 5-6 PM (0)
Apr 14, 6-7 PM (0)
Apr 14, 7-8 PM (0)
Apr 14, 8-9 PM (0)
Apr 14, 9-10 PM (0)
Apr 14, 10-11 PM (0)
Apr 14, 11-12 AM (0)
Apr 15, 12-1 AM (0)
Apr 15, 1-2 AM (0)
Apr 15, 2-3 AM (0)
Apr 15, 3-4 AM (0)
Apr 15, 4-5 AM (0)
Apr 15, 5-6 AM (0)
Apr 15, 6-7 AM (0)
Apr 15, 7-8 AM (0)
Apr 15, 8-9 AM (0)
Apr 15, 9-10 AM (0)
Apr 15, 10-11 AM (0)
Apr 15, 11-12 PM (0)
Apr 15, 12-1 PM (0)
Apr 15, 1-2 PM (0)
Apr 15, 2-3 PM (0)
Apr 15, 3-4 PM (0)
Apr 15, 4-5 PM (0)
Apr 15, 5-6 PM (0)
Apr 15, 6-7 PM (0)
Apr 15, 7-8 PM (1)
Apr 15, 8-9 PM (0)
Apr 15, 9-10 PM (0)
Apr 15, 10-11 PM (0)
Apr 15, 11-12 AM (0)
Apr 16, 12-1 AM (0)
Apr 16, 1-2 AM (0)
Apr 16, 2-3 AM (0)
Apr 16, 3-4 AM (0)
Apr 16, 4-5 AM (0)
Apr 16, 5-6 AM (0)
Apr 16, 6-7 AM (0)
Apr 16, 7-8 AM (0)
Apr 16, 8-9 AM (0)
Apr 16, 9-10 AM (0)
Apr 16, 10-11 AM (0)
Apr 16, 11-12 PM (3)
Apr 16, 12-1 PM (0)
Apr 16, 1-2 PM (0)
Apr 16, 2-3 PM (0)
Apr 16, 3-4 PM (0)
Apr 16, 4-5 PM (0)
Apr 16, 5-6 PM (0)
Apr 16, 6-7 PM (0)
Apr 16, 7-8 PM (0)
32 commits this week Apr 09, 2026 - Apr 16, 2026
build(docker): harden apt against transient Ubuntu-mirror failures
Drops an /etc/apt/apt.conf.d/99-retries file into every ubuntu:24.04
stage before the first apt call:

  Acquire::Retries "5";
  Acquire::http::Timeout "30";
  Acquire::https::Timeout "30";
  Acquire::http::No-Cache "true";

Motivation: docker compose builds on shared hosts have been hitting
"File has unexpected size" errors from security.ubuntu.com when a
mirror edge is mid-sync. Retries + No-Cache let apt transparently
re-fetch from a fresh mirror instance instead of failing the whole
build. Does not help when the mirror is serving a permanently-wrong
file (nothing does), but cleans up the common transient case.

Applied to:
- api/Dockerfile                          (build-common)
- yaci-indexer/Dockerfile                 (build-common)
- docker/dockerfiles/mithril/Dockerfile   (cardano-builder + mithril-runner)
- docker/dockerfiles/node/Dockerfile      (cardano-builder + node-runner)
- docker/dockerfiles/postgres/Dockerfile

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
build(docker): harden apt against transient Ubuntu-mirror failures
Drops an /etc/apt/apt.conf.d/99-retries file into every ubuntu:24.04
stage before the first apt call:

  Acquire::Retries "5";
  Acquire::http::Timeout "30";
  Acquire::https::Timeout "30";
  Acquire::http::No-Cache "true";

Motivation: docker compose builds on shared hosts have been hitting
"File has unexpected size" errors from security.ubuntu.com when a
mirror edge is mid-sync. Retries + No-Cache let apt transparently
re-fetch from a fresh mirror instance instead of failing the whole
build. Does not help when the mirror is serving a permanently-wrong
file (nothing does), but cleans up the common transient case.

Applied to:
- api/Dockerfile                          (build-common)
- yaci-indexer/Dockerfile                 (build-common)
- docker/dockerfiles/mithril/Dockerfile   (cardano-builder + mithril-runner)
- docker/dockerfiles/node/Dockerfile      (cardano-builder + node-runner)
- docker/dockerfiles/postgres/Dockerfile

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
refactor: move TOKEN_REGISTRY_ENABLED to serialization, always serve decimals
Client feedback: on most Rosetta chains decimals is a native, mandatory
field on every asset — and on Cardano we need to enrich it via assets-ext
even when the rest of the token-registry metadata isn't exposed. So the
flag shouldn't gate DB access; it should gate only the extra enrichment
fields in currency.metadata.

New wire contract:
- currency.decimals              — always populated (CIP-26/CIP-68 value or 0 fallback)
- currency.metadata.policyId     — always populated
- currency.metadata.{subject,name,description,ticker,url,logo,version}
                                 — only when TOKEN_REGISTRY_ENABLED=true

Code changes:
- TokenQueryServiceImpl drops the enabled short-circuit and always
  queries the assets-ext tables; mergeMetadata seeds decimals=0 and
  always populates policyId + subject.
- TokenRegistryCurrencyData.decimals back to @Nonnull Integer.
- AccountMapperUtil / TransactionMapperUtils drop the
  getDecimalsWithFallback helper and inline metadata.getDecimals().
- DataMapper injects TOKEN_REGISTRY_ENABLED; when false it emits a
  policyId-only CurrencyMetadataResponse, when true it delegates to
  TokenRegistryMapper for the full mapping. Decimals continues to flow
  to CurrencyResponse.decimals unconditionally.

Tests:
- DataMapperTest gains a TokenRegistryEnabledFlagTests nested class
  covering both flag states and null-metadata input.
- AccountMapperUtilTest and TransactionMapperUtilsTest flip the flag on
  at construction since they assert on enrichment fields.
- TokenQueryServiceTest drops the RegistryEnabledFlagTests class and
  updates the 'nothing found' assertions to match the new contract
  (subject populated, decimals=0).

Env and docs:
- New .env.docker-compose-preview (NETWORK=preview, PROTOCOL_MAGIC=2,
  enrichment + logos on). Allow-listed in .gitignore.
- token-metadata.md rewritten to separate 'decimals always' from
  'enrichment gated'; per-network defaults table (mainnet off,
  preprod/preview on).
- env-vars.md, helm-values.md, kubernetes/deployment.md updated to
  reflect the new semantics.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
feat: reintroduce TOKEN_REGISTRY_ENABLED flag (default disabled)
Clients rely on a runtime switch to turn token-registry enrichment on and
off; the assets-ext refactor dropped it. Reintroduce a single flag,
TOKEN_REGISTRY_ENABLED (default false), and route it through
TokenQueryServiceImpl so disabled requests short-circuit to a policyId-only
fallback without hitting the DB.

Also revert the decimals-non-null contract from e5efb3b05: the disabled
fallback now mirrors main (policyId only, subject/decimals null), so the
mergeMetadata path no longer seeds decimals=0. Restored getDecimalsWithFallback
in AccountMapperUtil and TransactionMapperUtils and flipped
TokenRegistryCurrencyData.decimals back to @Nullable.

Stripped the legacy HTTP-era flags (TOKEN_REGISTRY_BASE_URL,
TOKEN_REGISTRY_CACHE_TTL_HOURS, TOKEN_REGISTRY_REQUEST_TIMEOUT_SECONDS)
from every env template, docker-compose, Helm chart, CI workflow, and doc
that still referenced them. Enabled token registry + logo on preprod
(.env.docker-compose-preprod, values-preprod.yaml). Rewrote
docs/docs/advanced-configuration/token-metadata.md to describe the new
DB-backed yaci-store assets-ext flow instead of the removed
cf-token-metadata-registry HTTP proxy setup.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
build: bump all yaci-store dependencies to 2.1.0-pre4-d358232-SNAPSHOT
Previously only the two yaci-store-assets-ext artifacts tracked the
upstream feature/asset-store-ext SNAPSHOT while the other seven
starters (spring-boot, blocks, transaction, utxo, staking, admin,
admin-ui, governance, epoch) stayed on 2.0.0. That version mismatch
triggered a runtime ClassNotFoundException for
com.bloxbean.cardano.yaci.store.core.service.SyncStatusService — the
class has been moved in 2.1.0 and the assets-ext starter transitively
pulls 2.1.0 interfaces that reference the new location, while the
other starters pulled the 2.0.0 jar where it doesn't exist.

Unify everything via the yaci-store.version property (now
2.1.0-pre4-d358232-SNAPSHOT) so the whole dependency graph resolves
against the same upstream commit.

Required code changes:
- YaciSyncHealthIndicator and YaciSyncHealthIndicatorTest: import
  SyncStatusService from core.service (not adminui.service), and
  use the new common.domain.SyncStatus record instead of the old
  adminui.dto.SyncStatusDto class. Accessor calls switch from
  getSlot() / getNetworkSlot() / getSyncPercentage() / isSynced()
  to the record accessors slot() / networkSlot() / syncPercentage()
  / synced().

Tests: yaci-indexer 63/63 pass. api module recompiles clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
refactor: guarantee non-null decimals + TokenQueryService interface/impl split
Main change: TokenRegistryCurrencyData.decimals is now guaranteed non-null
for every token the query service returns. TokenQueryService.mergeMetadata
seeds the builder with decimals=0 before applying CIP-26 and CIP-68 overrides,
so tokens that advertise no decimal count in either standard get the canonical
default instead of null. The @Nullable annotation on the field is replaced by
@Nonnull (picked up by Lombok's AllArgsConstructor which now enforces the
contract at build time).

Downstream consequences:
- AccountMapperUtil and TransactionMapperUtils both had a duplicated
  getDecimalsWithFallback() helper applying the same .orElse(0) default.
  Both deleted; callers inline metadata.getDecimals() directly since the
  contract now guarantees non-null.

Interface/impl split:
- TokenQueryService is now an interface in the common.service package
- The implementation moves to TokenQueryServiceImpl (fixes the earlier
  typo'd TokenQueryServiceIImpl class name across 5 files)
- @Override added on queryMetadataBatch in the impl
- TokenQueryService interface gets a Javadoc describing the contract
  (merge priority, CIP-68 prefix conversion, non-null decimals guarantee)

Defensive numeric conversion:
- applyCip26 / applyCip68 use Math.toIntExact(Long) instead of .intValue()
  when narrowing the decimals column. Silent truncation at Integer.MAX_VALUE
  is now a loud IllegalStateException — a nit that will never fire in
  practice (decimal counts are 0-18) but removes a footgun.

Tests: 78/78 pass. TokenQueryServiceTest.shouldLeaveNullDecimalsAsNull
renamed to shouldDefaultDecimalsToZeroWhenBothStandardsLackIt and asserts
equal-to-0 instead of null. TokenRegistryMapperTest.shouldHandleAllNullFields
renamed to shouldHandleAllNullableFieldsNull and seeds decimals=0 on the
builder. Several DataMapperTest and TokenRegistryMapperTest cases needed
.decimals(0) added since the builder now rejects null.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
refactor: reduce TokenQueryService.queryMetadataBatch complexity
Break up the 109-line queryMetadataBatch method into focused helpers
and introduce two private record types to encapsulate batch-fetch
results, reducing cyclomatic complexity and eliminating the in-method
20-line comment about structured concurrency.

Changes:
- queryMetadataBatch is now ~20 lines of linear orchestration
- New Cip26Batch record holds the metadata + logo maps
- New Cip68Batch record holds the entity map + reverse lookup, with
  a findFor(AssetFingerprint) helper hiding the double indexing
- fetchCip26InParallel() owns the StructuredTaskScope logic and moves
  the trade-off explanation to an @implNote Javadoc on the method
- fetchCip68() owns the candidate collection and batch query
- mergeMetadata() owns the per-fingerprint merge logic

Unified the apply* helper shape:
- applyCip26(builder, entity, logo) now takes the logo as a parameter,
  matching applyCip68's fields+logo-in-one-call API
- Dropped the separate applyCip26Fields + applyCip26Logo split

Added @Slf4j and a single debug log after the merge loop reporting
the batch size and hit counts for CIP-26 / CIP-68 — operational
visibility for preprod testing.

Minor: consistency fix — use Function.identity() instead of m -> m.

No behavioral change. Tests: 62/62 pass across TokenQueryServiceTest,
TokenRegistryServiceImplTest, TokenRegistryMapperTest, DataMapperTest.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
refactor: add @Builder.Default on MetadataReferenceNftEntity.label and drop length hint on asset_name
- Add @Builder.Default with value 333 on the label field so builder-created
  instances default to the CIP-68 fungible-token label, matching the real
  column default and removing the Lombok warning about an ignored initializer.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
refactor: inline CIP-68 batch query as @Query to match upstream yaci-store
Replace the custom repository fragment (MetadataReferenceNftRepositoryCustom
+ CustomImpl) with a single @Query(nativeQuery=true) method on
MetadataReferenceNftRepository using CONCAT(policy_id, asset_name) IN
(:concatenatedKeys). Policy IDs are fixed 56-char hex so the concatenation
is unambiguous.

Mirrors the upstream approach in bloxbean/yaci-store's
MetadataReferenceNftRepository.findLatestByConcatenatedKeys — the two
codebases now share the same SQL shape and the same Spring Data idiom.
Once #731 merges the api and yaci-indexer modules into rosetta-app, this
downstream repository layer disappears and the upstream query becomes the
single source of truth.

Removes ~56 lines net:
- Delete MetadataReferenceNftRepositoryCustom interface (PolicyAssetPair
  record + findLatestByPolicyAssetPairs method)
- Delete MetadataReferenceNftRepositoryCustomImpl (dynamic tuple-IN
  EntityManager implementation)
- TokenQueryService.queryMetadataBatch collects List<String> concatenated
  keys instead of List<PolicyAssetPair>, calls findLatestByConcatenatedKeys
- Tests: 14 stubs switch from findLatestByPolicyAssetPairs(anyList(), ...)
  to findLatestByConcatenatedKeys(anyCollection(), ...); ArgumentCaptor
  test verifies the concatenated string instead of a PolicyAssetPair

Performance trade-off: CONCAT on the WHERE side means the planner can't
do composite-index seeks on (policy_id, asset_name, slot); it does a
label-filtered scan + CONCAT comparison instead. Acceptable because
metadata_reference_nft is a small table (one row per token metadata
update, updates are rare) and the optional idx_metadata_reference_nft
_policy_label index from optional-indexes.sql turns the scan into an
index scan when needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
build: seed Maven cache from host .m2 for local yaci-store snapshots
Adds an optional host-m2 build stage to the api and yaci-indexer
Dockerfiles. During the mvn step we bind-mount the stage and copy
com/bloxbean/cardano artifacts into the cache, then build with -nsu
so Maven does not try to re-resolve snapshots from remote repos.

The host-m2 context is wired into the api, indexer, and
integration-test-indexer compose files via additional_contexts,
defaulting to $HOME/.m2/repository. Regular builds without the
context are unaffected because the default stage is empty scratch.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>