Add AssetAZ Currency Identity Service and migrate CurrencyType #62

Closed
opened 2026-08-07 13:26:39 +02:00 by minimons · 0 comments
Owner

Background

AssetAZ needs a single authoritative service for translating currency identities between external systems and AssetAZ's internal stable identities.

External symbols are not globally unique. For example, USDC may refer to different assets depending on blockchain, exchange or protocol. Even within the same ecosystem, different tokens may use the same symbol.

An external currency must therefore be identified by:

namespace + externalId

For Solana-based integrations, the external ID will normally be the token mint address. The symbol is useful metadata, but must not be used as the unique identity.

This service is also needed before RaydiumImpl.fetchPoolPrice() can construct the correct TradingPair from Raydium's mintA and mintB data instead of using a hardcoded trading pair.

Goal

Introduce an AssetAZ CurrencyIdentityService that:

  • Owns AssetAZ's canonical currency metadata.
  • Resolves AssetAZ UUIDs to CurrencyType.
  • Resolves external currency references to CurrencyType.
  • Supports reverse lookup from an AssetAZ UUID to external references.
  • Initially uses hardcoded data.
  • Replaces WellKnownCurrencyTypes as the authoritative currency registry.
  • Allows RaydiumImpl to construct the actual trading pair returned by Raydium.

Packages

Create the API in:

com.r35157.assetaz.services.cis.CurrencyIdentityService
com.r35157.assetaz.services.cis.ExternalCurrencyReference

Create the hardcoded implementation in:

com.r35157.assetaz.services.cis.impl.hc.HardcodedCurrencyIdentityService

Move:

com.r35157.libs.valuetypes.basic.CurrencyType

to:

com.r35157.assetaz.valuetypes.CurrencyType

Only CurrencyType is moved as part of this issue. Do not start a general migration of the other value types.

External currency references

Introduce an immutable value type similar to:

public record ExternalCurrencyReference(
        String namespace,
        String externalId,
        String symbol
) {
}

The identity of an external currency is:

namespace + externalId

The symbol is metadata and must not be used as the lookup key.

The namespace describes the external identification system, not the Ticker price source. For example, a Solana mint address belongs to a Solana mint namespace even if it was observed through Raydium or Jupiter.

Do not couple this model to PriceSourceName.

CurrencyIdentityService API

The API should provide operations corresponding to:

@NotNull
CurrencyType resolve(@NotNull UUID currencyTypeId);

@NotNull
CurrencyType resolve(
        @NotNull ExternalCurrencyReference externalReference
);

@NotNull
Set<ExternalCurrencyReference> findExternalReferences(
        @NotNull UUID currencyTypeId
);

Requirements:

  • Unknown UUIDs and unknown external identities must fail with a clear exception.
  • The methods must not return null.
  • A known currency with no external references may return an empty immutable set.
  • Reverse lookup may return multiple references because one AssetAZ currency can have identities in several external systems.
  • Returned collections must not expose mutable internal state.
  • Public interface should use @Nullable and @NotNull for parameters and return types of the methods.
  • Version 1 must not expose public registration or mutation methods.

CurrencyType identity and equality

The UUID is the complete and stable identity of a CurrencyType.

Name, symbol and future metadata may change without creating a different currency.

Update CurrencyType.equals() and hashCode() so they use only the UUID:

@Override
public boolean equals(Object object) {
    return this == object
            || object instanceof CurrencyType other
            && id.equals(other.id);
}

@Override
public int hashCode() {
    return id.hashCode();
}

Consequences:

new CurrencyType(id, "USD Coin", "USDC")
        .equals(new CurrencyType(id, "Updated USD Coin", "USDC"));

must be true.

Two currencies with different UUIDs must not be equal, even if their names and symbols are identical.

Clients must use .equals(...), never ==, when comparing currency types.

CurrencyIdentityService may cache and reuse instances internally, but reference identity is not part of its contract. Nenjim will eventually be able to replace service implementations at runtime, so the same currency may be represented by different object instances.

CurrencyType must remain immutable and may retain its public constructor. The service is the authoritative production registry by architecture rather than through constructor visibility restrictions.

HardcodedCurrencyIdentityService

Move the existing UUID, name and symbol definitions from WellKnownCurrencyTypes into HardcodedCurrencyIdentityService.

Requirements:

  • Preserve all existing AssetAZ UUIDs exactly.
  • Maintain one current CurrencyType value per UUID within the service instance.
  • Support multiple external references for the same AssetAZ UUID.
  • Reject conflicting hardcoded mappings during construction.
  • The same external identity must never map to multiple AssetAZ UUIDs.
  • Conflicting entries for the same UUID inside one service instance must be detected clearly.
  • The initialized registry should be immutable and safe for concurrent reads.

Metadata may legitimately differ between two service versions. Equality across those versions must still work because it is based only on UUID.

Remove parallel registries

Remove WellKnownCurrencyTypes after all usages have been migrated.

Its contents must be owned by HardcodedCurrencyIdentityService; the new service must not merely supplement the existing enum.

Also remove or migrate WellKnownTradingPairs so it does not retain static CurrencyType or TradingPair instances outside the service lifecycle.

Trading pairs must be constructed from CurrencyType values obtained through the current CurrencyIdentityService.

The final implementation must not contain a second authoritative registry of currency identities.

Raydium integration

Inject CurrencyIdentityService into RaydiumImpl through its constructor until Nenjim can provide the dependency dynamically.

Update all construction sites, decorators and tests accordingly.

Update RaydiumImpl.fetchPoolPrice() so it:

  1. Reads the unique token identifiers for mintA and mintB from the Raydium response.
  2. Maps any currently unmapped symbol fields needed for the external references.
  3. Creates external references using the appropriate namespace and mint addresses.
  4. Resolves both currencies through CurrencyIdentityService.
  5. Constructs the TradingPair in the correct base/quote order represented by the Raydium response.
  6. Constructs the returned AssetPrice using that trading pair.

Remove the hardcoded:

WellKnownTradingPairs.SOL_SYRUPUSDC

and the related TODO.

A token symbol alone must never be used to resolve a Raydium currency.

The hardcoded implementation must contain the external mappings needed by the currently supported Raydium pool.

Tests

Do not add any unit tests at the moment.

Out of scope

This issue does not include:

  • Moving other value types into AssetAZ.
  • Database-backed or remotely managed currency mappings.
  • Runtime discovery or replacement through Nenjim.
  • A public API for registering currencies.
  • Hiding the public CurrencyType constructor.
  • Implementing the periodically polling Raydium PriceSource.
  • UnitTests

The future Raydium PriceSource will build on this service in a separate issue.

Acceptance criteria

  • CurrencyIdentityService and ExternalCurrencyReference exist in the AssetAZ API package.
  • HardcodedCurrencyIdentityService exists in the specified implementation package.
  • CurrencyType has been moved to the AssetAZ package.
  • CurrencyType.equals() and hashCode() depend only on UUID.
  • Existing currency UUIDs remain unchanged.
  • WellKnownCurrencyTypes has been removed.
  • No static parallel registry retains canonical CurrencyType or TradingPair instances.
  • RaydiumImpl receives the service through constructor injection.
  • fetchPoolPrice() builds its trading pair from the currencies returned by Raydium.
  • All affected code compiles.
  • Relevant tests pass.
  • Strict OpenSpec validation passes.
## Background AssetAZ needs a single authoritative service for translating currency identities between external systems and AssetAZ's internal stable identities. External symbols are not globally unique. For example, `USDC` may refer to different assets depending on blockchain, exchange or protocol. Even within the same ecosystem, different tokens may use the same symbol. An external currency must therefore be identified by: ```text namespace + externalId ``` For Solana-based integrations, the external ID will normally be the token mint address. The symbol is useful metadata, but must not be used as the unique identity. This service is also needed before `RaydiumImpl.fetchPoolPrice()` can construct the correct `TradingPair` from Raydium's `mintA` and `mintB` data instead of using a hardcoded trading pair. ## Goal Introduce an AssetAZ `CurrencyIdentityService` that: * Owns AssetAZ's canonical currency metadata. * Resolves AssetAZ UUIDs to `CurrencyType`. * Resolves external currency references to `CurrencyType`. * Supports reverse lookup from an AssetAZ UUID to external references. * Initially uses hardcoded data. * Replaces `WellKnownCurrencyTypes` as the authoritative currency registry. * Allows `RaydiumImpl` to construct the actual trading pair returned by Raydium. ## Packages Create the API in: ```java com.r35157.assetaz.services.cis.CurrencyIdentityService com.r35157.assetaz.services.cis.ExternalCurrencyReference ``` Create the hardcoded implementation in: ```java com.r35157.assetaz.services.cis.impl.hc.HardcodedCurrencyIdentityService ``` Move: ```java com.r35157.libs.valuetypes.basic.CurrencyType ``` to: ```java com.r35157.assetaz.valuetypes.CurrencyType ``` Only `CurrencyType` is moved as part of this issue. Do not start a general migration of the other value types. ## External currency references Introduce an immutable value type similar to: ```java public record ExternalCurrencyReference( String namespace, String externalId, String symbol ) { } ``` The identity of an external currency is: ```text namespace + externalId ``` The symbol is metadata and must not be used as the lookup key. The namespace describes the external identification system, not the Ticker price source. For example, a Solana mint address belongs to a Solana mint namespace even if it was observed through Raydium or Jupiter. Do not couple this model to `PriceSourceName`. ## CurrencyIdentityService API The API should provide operations corresponding to: ```java @NotNull CurrencyType resolve(@NotNull UUID currencyTypeId); @NotNull CurrencyType resolve( @NotNull ExternalCurrencyReference externalReference ); @NotNull Set<ExternalCurrencyReference> findExternalReferences( @NotNull UUID currencyTypeId ); ``` Requirements: * Unknown UUIDs and unknown external identities must fail with a clear exception. * The methods must not return `null`. * A known currency with no external references may return an empty immutable set. * Reverse lookup may return multiple references because one AssetAZ currency can have identities in several external systems. * Returned collections must not expose mutable internal state. * Public interface should use @Nullable and @NotNull for parameters and return types of the methods. * Version 1 must not expose public registration or mutation methods. ## CurrencyType identity and equality The UUID is the complete and stable identity of a `CurrencyType`. Name, symbol and future metadata may change without creating a different currency. Update `CurrencyType.equals()` and `hashCode()` so they use only the UUID: ```java @Override public boolean equals(Object object) { return this == object || object instanceof CurrencyType other && id.equals(other.id); } @Override public int hashCode() { return id.hashCode(); } ``` Consequences: ```java new CurrencyType(id, "USD Coin", "USDC") .equals(new CurrencyType(id, "Updated USD Coin", "USDC")); ``` must be `true`. Two currencies with different UUIDs must not be equal, even if their names and symbols are identical. Clients must use `.equals(...)`, never `==`, when comparing currency types. `CurrencyIdentityService` may cache and reuse instances internally, but reference identity is not part of its contract. Nenjim will eventually be able to replace service implementations at runtime, so the same currency may be represented by different object instances. `CurrencyType` must remain immutable and may retain its public constructor. The service is the authoritative production registry by architecture rather than through constructor visibility restrictions. ## HardcodedCurrencyIdentityService Move the existing UUID, name and symbol definitions from `WellKnownCurrencyTypes` into `HardcodedCurrencyIdentityService`. Requirements: * Preserve all existing AssetAZ UUIDs exactly. * Maintain one current `CurrencyType` value per UUID within the service instance. * Support multiple external references for the same AssetAZ UUID. * Reject conflicting hardcoded mappings during construction. * The same external identity must never map to multiple AssetAZ UUIDs. * Conflicting entries for the same UUID inside one service instance must be detected clearly. * The initialized registry should be immutable and safe for concurrent reads. Metadata may legitimately differ between two service versions. Equality across those versions must still work because it is based only on UUID. ## Remove parallel registries Remove `WellKnownCurrencyTypes` after all usages have been migrated. Its contents must be owned by `HardcodedCurrencyIdentityService`; the new service must not merely supplement the existing enum. Also remove or migrate `WellKnownTradingPairs` so it does not retain static `CurrencyType` or `TradingPair` instances outside the service lifecycle. Trading pairs must be constructed from `CurrencyType` values obtained through the current `CurrencyIdentityService`. The final implementation must not contain a second authoritative registry of currency identities. ## Raydium integration Inject `CurrencyIdentityService` into `RaydiumImpl` through its constructor until Nenjim can provide the dependency dynamically. Update all construction sites, decorators and tests accordingly. Update `RaydiumImpl.fetchPoolPrice()` so it: 1. Reads the unique token identifiers for `mintA` and `mintB` from the Raydium response. 2. Maps any currently unmapped symbol fields needed for the external references. 3. Creates external references using the appropriate namespace and mint addresses. 4. Resolves both currencies through `CurrencyIdentityService`. 5. Constructs the `TradingPair` in the correct base/quote order represented by the Raydium response. 6. Constructs the returned `AssetPrice` using that trading pair. Remove the hardcoded: ```java WellKnownTradingPairs.SOL_SYRUPUSDC ``` and the related TODO. A token symbol alone must never be used to resolve a Raydium currency. The hardcoded implementation must contain the external mappings needed by the currently supported Raydium pool. ## Tests Do not add any unit tests at the moment. ## Out of scope This issue does not include: * Moving other value types into AssetAZ. * Database-backed or remotely managed currency mappings. * Runtime discovery or replacement through Nenjim. * A public API for registering currencies. * Hiding the public `CurrencyType` constructor. * Implementing the periodically polling Raydium `PriceSource`. * UnitTests The future Raydium `PriceSource` will build on this service in a separate issue. ## Acceptance criteria * `CurrencyIdentityService` and `ExternalCurrencyReference` exist in the AssetAZ API package. * `HardcodedCurrencyIdentityService` exists in the specified implementation package. * `CurrencyType` has been moved to the AssetAZ package. * `CurrencyType.equals()` and `hashCode()` depend only on UUID. * Existing currency UUIDs remain unchanged. * `WellKnownCurrencyTypes` has been removed. * No static parallel registry retains canonical `CurrencyType` or `TradingPair` instances. * `RaydiumImpl` receives the service through constructor injection. * `fetchPoolPrice()` builds its trading pair from the currencies returned by Raydium. * All affected code compiles. * Relevant tests pass. * Strict OpenSpec validation passes.
minimons added the enhancement label 2026-08-07 13:26:39 +02:00
minimons self-assigned this 2026-08-07 13:26:39 +02:00
minimons added this to the AssetAZ project 2026-08-07 13:26:39 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: r35157/com_r35157_nenjim-hubd-impl_ref#62