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.
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:
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:
Reads the unique token identifiers for mintA and mintB from the Raydium response.
Maps any currently unmapped symbol fields needed for the external references.
Creates external references using the appropriate namespace and mint addresses.
Resolves both currencies through CurrencyIdentityService.
Constructs the TradingPair in the correct base/quote order represented by the Raydium response.
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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,
USDCmay 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:
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 correctTradingPairfrom Raydium'smintAandmintBdata instead of using a hardcoded trading pair.Goal
Introduce an AssetAZ
CurrencyIdentityServicethat:CurrencyType.CurrencyType.WellKnownCurrencyTypesas the authoritative currency registry.RaydiumImplto construct the actual trading pair returned by Raydium.Packages
Create the API in:
Create the hardcoded implementation in:
Move:
to:
Only
CurrencyTypeis 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:
The identity of an external currency is:
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:
Requirements:
null.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()andhashCode()so they use only the UUID:Consequences:
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.CurrencyIdentityServicemay 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.CurrencyTypemust 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
WellKnownCurrencyTypesintoHardcodedCurrencyIdentityService.Requirements:
CurrencyTypevalue per UUID within the service instance.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
WellKnownCurrencyTypesafter 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
WellKnownTradingPairsso it does not retain staticCurrencyTypeorTradingPairinstances outside the service lifecycle.Trading pairs must be constructed from
CurrencyTypevalues obtained through the currentCurrencyIdentityService.The final implementation must not contain a second authoritative registry of currency identities.
Raydium integration
Inject
CurrencyIdentityServiceintoRaydiumImplthrough its constructor until Nenjim can provide the dependency dynamically.Update all construction sites, decorators and tests accordingly.
Update
RaydiumImpl.fetchPoolPrice()so it:mintAandmintBfrom the Raydium response.CurrencyIdentityService.TradingPairin the correct base/quote order represented by the Raydium response.AssetPriceusing that trading pair.Remove the hardcoded:
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:
CurrencyTypeconstructor.PriceSource.The future Raydium
PriceSourcewill build on this service in a separate issue.Acceptance criteria
CurrencyIdentityServiceandExternalCurrencyReferenceexist in the AssetAZ API package.HardcodedCurrencyIdentityServiceexists in the specified implementation package.CurrencyTypehas been moved to the AssetAZ package.CurrencyType.equals()andhashCode()depend only on UUID.WellKnownCurrencyTypeshas been removed.CurrencyTypeorTradingPairinstances.RaydiumImplreceives the service through constructor injection.fetchPoolPrice()builds its trading pair from the currencies returned by Raydium.