Home / Cardano Foundation / cardano-rosetta-java
Apr 04, 8-9 AM (0)
Apr 04, 9-10 AM (0)
Apr 04, 10-11 AM (0)
Apr 04, 11-12 PM (0)
Apr 04, 12-1 PM (0)
Apr 04, 1-2 PM (0)
Apr 04, 2-3 PM (0)
Apr 04, 3-4 PM (0)
Apr 04, 4-5 PM (0)
Apr 04, 5-6 PM (0)
Apr 04, 6-7 PM (0)
Apr 04, 7-8 PM (0)
Apr 04, 8-9 PM (0)
Apr 04, 9-10 PM (0)
Apr 04, 10-11 PM (0)
Apr 04, 11-12 AM (0)
Apr 05, 12-1 AM (0)
Apr 05, 1-2 AM (0)
Apr 05, 2-3 AM (0)
Apr 05, 3-4 AM (0)
Apr 05, 4-5 AM (0)
Apr 05, 5-6 AM (0)
Apr 05, 6-7 AM (0)
Apr 05, 7-8 AM (0)
Apr 05, 8-9 AM (0)
Apr 05, 9-10 AM (0)
Apr 05, 10-11 AM (0)
Apr 05, 11-12 PM (0)
Apr 05, 12-1 PM (0)
Apr 05, 1-2 PM (0)
Apr 05, 2-3 PM (0)
Apr 05, 3-4 PM (0)
Apr 05, 4-5 PM (0)
Apr 05, 5-6 PM (0)
Apr 05, 6-7 PM (0)
Apr 05, 7-8 PM (0)
Apr 05, 8-9 PM (0)
Apr 05, 9-10 PM (0)
Apr 05, 10-11 PM (0)
Apr 05, 11-12 AM (0)
Apr 06, 12-1 AM (0)
Apr 06, 1-2 AM (0)
Apr 06, 2-3 AM (0)
Apr 06, 3-4 AM (0)
Apr 06, 4-5 AM (0)
Apr 06, 5-6 AM (0)
Apr 06, 6-7 AM (0)
Apr 06, 7-8 AM (0)
Apr 06, 8-9 AM (0)
Apr 06, 9-10 AM (0)
Apr 06, 10-11 AM (0)
Apr 06, 11-12 PM (0)
Apr 06, 12-1 PM (0)
Apr 06, 1-2 PM (0)
Apr 06, 2-3 PM (0)
Apr 06, 3-4 PM (0)
Apr 06, 4-5 PM (25)
Apr 06, 5-6 PM (3)
Apr 06, 6-7 PM (0)
Apr 06, 7-8 PM (0)
Apr 06, 8-9 PM (0)
Apr 06, 9-10 PM (0)
Apr 06, 10-11 PM (0)
Apr 06, 11-12 AM (0)
Apr 07, 12-1 AM (0)
Apr 07, 1-2 AM (0)
Apr 07, 2-3 AM (0)
Apr 07, 3-4 AM (0)
Apr 07, 4-5 AM (0)
Apr 07, 5-6 AM (0)
Apr 07, 6-7 AM (0)
Apr 07, 7-8 AM (0)
Apr 07, 8-9 AM (1)
Apr 07, 9-10 AM (3)
Apr 07, 10-11 AM (0)
Apr 07, 11-12 PM (0)
Apr 07, 12-1 PM (0)
Apr 07, 1-2 PM (0)
Apr 07, 2-3 PM (0)
Apr 07, 3-4 PM (0)
Apr 07, 4-5 PM (0)
Apr 07, 5-6 PM (0)
Apr 07, 6-7 PM (0)
Apr 07, 7-8 PM (0)
Apr 07, 8-9 PM (0)
Apr 07, 9-10 PM (0)
Apr 07, 10-11 PM (0)
Apr 07, 11-12 AM (0)
Apr 08, 12-1 AM (0)
Apr 08, 1-2 AM (0)
Apr 08, 2-3 AM (0)
Apr 08, 3-4 AM (0)
Apr 08, 4-5 AM (0)
Apr 08, 5-6 AM (0)
Apr 08, 6-7 AM (0)
Apr 08, 7-8 AM (0)
Apr 08, 8-9 AM (0)
Apr 08, 9-10 AM (0)
Apr 08, 10-11 AM (0)
Apr 08, 11-12 PM (0)
Apr 08, 12-1 PM (0)
Apr 08, 1-2 PM (1)
Apr 08, 2-3 PM (4)
Apr 08, 3-4 PM (0)
Apr 08, 4-5 PM (0)
Apr 08, 5-6 PM (0)
Apr 08, 6-7 PM (0)
Apr 08, 7-8 PM (0)
Apr 08, 8-9 PM (0)
Apr 08, 9-10 PM (0)
Apr 08, 10-11 PM (0)
Apr 08, 11-12 AM (0)
Apr 09, 12-1 AM (0)
Apr 09, 1-2 AM (0)
Apr 09, 2-3 AM (0)
Apr 09, 3-4 AM (0)
Apr 09, 4-5 AM (0)
Apr 09, 5-6 AM (0)
Apr 09, 6-7 AM (0)
Apr 09, 7-8 AM (0)
Apr 09, 8-9 AM (0)
Apr 09, 9-10 AM (0)
Apr 09, 10-11 AM (0)
Apr 09, 11-12 PM (3)
Apr 09, 12-1 PM (2)
Apr 09, 1-2 PM (0)
Apr 09, 2-3 PM (1)
Apr 09, 3-4 PM (0)
Apr 09, 4-5 PM (0)
Apr 09, 5-6 PM (1)
Apr 09, 6-7 PM (5)
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 (1)
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)
66 commits this week Apr 04, 2026 - Apr 11, 2026
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]>
fix: separate InterruptedException from IOException in YaciHttpGatewayImpl (#732) (#733)
Thread.currentThread().interrupt() was called for both IOException and
InterruptedException. On IOException (e.g. yaci-indexer not ready), this
left the request thread in interrupted state. With pruning enabled, the
subsequent oldest_block_identifier DB query hit the interrupted flag,
causing JDBC to close the socket and fail with "Unable to rollback
against JDBC Connection".

Closes #732

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
fix: separate InterruptedException from IOException in YaciHttpGatewayImpl (#732)
Thread.currentThread().interrupt() was called for both IOException and
InterruptedException. On IOException (e.g. yaci-indexer not ready), this
left the request thread in interrupted state. With pruning enabled, the
subsequent oldest_block_identifier DB query hit the interrupted flag,
causing JDBC to close the socket and fail with "Unable to rollback
against JDBC Connection".

Closes #732

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
fix: separate InterruptedException from IOException in YaciHttpGatewayImpl (#732)
Thread.currentThread().interrupt() was called for both IOException and
InterruptedException. On IOException (e.g. yaci-indexer not ready), this
left the request thread in interrupted state. With pruning enabled, the
subsequent oldest_block_identifier DB query hit the interrupted flag,
causing JDBC to close the socket and fail with "Unable to rollback
against JDBC Connection".

Closes #732

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
build: bump yaci-store-assets-ext to 2.1.0-pre4-d358232-SNAPSHOT
Picks up the upstream CIP-68 batch-query optimization
(bloxbean/yaci-store commit d358232b8) which replaces the per-subject
N+1 pattern in TokenQueryService.prefetchBatch with a single
window-function query.

Only affects the yaci-indexer classpath — the api module uses its
own JPA entities and its own MetadataReferenceNftRepository batch
query, so no behavioral change on the read path.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
docs: explain the @Transactional / StructuredTaskScope trade-off
The fork/join block in queryMetadataBatch is deliberate: parallelising
the two CIP-26 queries (metadata + logos) saves a round-trip at the
cost of losing the outer readOnly transaction context inside the
forked virtual threads. Since CIP-26 rows are only written during
the periodic offchain token-registry sync, the read-skew risk is
negligible for our use case.

Document the caveat, the reasoning, and the revert recipe so a
future reader is not surprised when they notice that each fork
opens its own transaction.

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