Issue #61 introduced the PriceSource/PriceSink architecture for AssetAZ Ticker, and issue #62 added CurrencyIdentityService, allowing external currency identities to be resolved to canonical AssetAZ currencies.
The next step is to add a new PriceSource implementation for Raydium pools with the first real prices.
The first source must follow this Raydium concentrated liquidity pool:
8rN4BTEzbogQosEQYgsEu18XwfKS5Yoxqwit8zEVFwEe
The pool’s trading pair is:
EVE/USDT
This will provide Evelyn with a real market price that can later, for example, be displayed as a graph in Evelyn Mission Control.
Starting Point and Existing Module Structure
This issue starts from commit 03be810.
The package and future module boundaries introduced by that commit are intentional and must be preserved:
TickerService and PriceObservation belong to the Ticker API.
PriceSource and PriceSink belong together in the PriceSource plugin API:
TickerServiceImpl is the Ticker reference implementation.
HardcodedPriceSource is a separate PriceSource implementation.
RaydiumPoolPriceSource is a separate PriceSource implementation and already exists as an unimplemented scaffold.
Do not move PriceSink back into the Ticker API, recreate RaydiumPoolPriceSource in the old package, or otherwise redesign these module boundaries as part of this issue.
Goal
Complete the existing general RaydiumPoolPriceSource scaffold so that one instance represents exactly one Raydium pool and periodically delivers its price to AssetAZ Ticker through the existing PriceSink interface.
For now, the selected pool must be hardcoded when constructing the source in NenjimHubImpl. Configuration files and Nenjim discovery must not be added as part of this issue.
Add a new capability delta named assetaz-raydium-pool-price-source describing the Raydium-specific PriceSource behavior.
Add a delta to the existing assetaz-currency-identity-service capability describing the addition of USDT.
Document the package and future module boundaries from commit 03be810 in the change’s design.md and relevant implementation tasks.
Leave the canonical assetaz-ticker-service specification unchanged unless the implementation reveals an actual behavioral change to the generic Ticker contract.
Leave the archived changes for issues #60, #61, and #62 unchanged. Their old package names are historical documentation and must not be rewritten solely because the classes have moved.
Raydium-specific behavior must not be added to the generic assetaz-ticker-service capability.
Add USDT to CIS
The pool’s quote currency is USDT, which does not yet exist in the CIS catalog.
Add:
A stable USDT_ID to CurrencyTypeIds.
A canonical currency named "Tether USD" with the symbol "USDT".
The following Solana mint reference:
Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB
USDT and USDC must remain separate CurrencyType identities. No implicit conversion between them must be performed.
RaydiumPoolPriceSource
Complete the existing PriceSource scaffold located at:
The pool address represents the source/market identity and must not be registered as an external currency reference in CIS.
Polling
When the source is started:
The first price retrieval attempt must begin immediately.
Subsequent retrieval attempts must use a fixed delay of one minute after the previous attempt has completed.
Polling must run on a source-owned daemon scheduler thread.
The price must be retrieved using:
raydium.fetchPoolPrice(poolId)
The returned AssetPrice.tradingPair() must be compared with the expected trading pair stored by the source.
If the trading pair does not match:
The observation must not be published.
A clear error must be logged containing the pool ID, expected pair, and received pair.
Polling must continue at the next interval.
For a valid response, the price value must be published through the PriceSink supplied to start(...).
The observation timestamp must be created after the successful Raydium response and truncated to milliseconds before the observation is published.
An internal or package-private constructor may make the Clock and polling delay injectable so that the behavior can be verified without waiting one minute. The production constructor must continue to use UTC and a one-minute interval.
Error Handling and Lifecycle
Temporary network, Raydium, parsing, and persistence errors must:
Be logged with the pool ID and trading pair.
Not stop the Ticker.
Not cancel future polling.
Be retried at the next interval.
start(...) must reject repeated invocation before replacing the existing sink.
stop() must:
Be idempotent.
Stop the scheduler thread.
Allow the source to be started again later.
Handle interruption correctly without leaving resources running.
Temporary Wiring in NenjimHubImpl
NenjimHubImpl must construct the required Raydium implementation and then create the source using the hardcoded pool ID:
Use the correct ΩRaydiumLiquidityPoolIdΩ type in the actual .tjava code.
The source must be injected through the existing temporary TickerServiceImpl constructor.
HardcodedPriceSource must remain in the codebase and remain wired and running exactly as it is now. The new RaydiumPoolPriceSource must run alongside it.
Activation and Price History
Preserve the existing Ticker behavior: the source is active only when its exact history file already exists.
The file and its directories must not be created automatically. An empty, manually created file activates the source.
The existing hardcoded EVE/USDC history must not be moved or copied.
Out of Scope
This issue must not add:
A configuration file for Raydium sources.
Dynamic Nenjim discovery.
WebSocket or streaming support.
Exponential backoff.
A health/status API.
A separate MarketReference model.
Automatic creation or migration of history files.
USDT/USDC conversion.
Price visualization in Evelyn Mission Control.
Any redesign of the module boundaries introduced by commit 03be810.
The EMC graph will be a subsequent issue that consumes the new Ticker price.
Final Verification
Verify that USDT can be resolved through both USDT_ID and its Solana mint.
Verify that the trading pair returned for the EVE/USDT pool matches the expected pair.
Verify that the source name contains the pool ID and produces a separate history.
Verify that a successful observation is persisted before being published by the Ticker.
Verify that a temporary polling failure does not prevent subsequent attempts.
Verify that stop() terminates polling and that the source can subsequently be restarted.
Verify that the module and package boundaries introduced by commit 03be810 remain intact.
Verify that HardcodedPriceSource remains wired and running alongside RaydiumPoolPriceSource.
Compile both the main and test source sets without running unit tests.
Run strict OpenSpec validation.
Run git diff --check.
Review the complete diff for incomplete wiring, old package imports, and unintended changes to HardcodedPriceSource.
## Background
Issue #61 introduced the `PriceSource`/`PriceSink` architecture for AssetAZ Ticker, and issue #62 added `CurrencyIdentityService`, allowing external currency identities to be resolved to canonical AssetAZ currencies.
The next step is to add a new PriceSource implementation for Raydium pools with the first real prices.
The first source must follow this Raydium concentrated liquidity pool:
```text
8rN4BTEzbogQosEQYgsEu18XwfKS5Yoxqwit8zEVFwEe
```
The pool’s trading pair is:
```text
EVE/USDT
```
This will provide Evelyn with a real market price that can later, for example, be displayed as a graph in Evelyn Mission Control.
## Starting Point and Existing Module Structure
This issue starts from commit `03be810`.
The package and future module boundaries introduced by that commit are intentional and must be preserved:
* `TickerService` and `PriceObservation` belong to the Ticker API.
* `PriceSource` and `PriceSink` belong together in the PriceSource plugin API:
```text
com.r35157.assetaz.services.ticker.plugins.pricesource
```
* `TickerServiceImpl` is the Ticker reference implementation.
* `HardcodedPriceSource` is a separate PriceSource implementation.
* `RaydiumPoolPriceSource` is a separate PriceSource implementation and already exists as an unimplemented scaffold.
Do not move `PriceSink` back into the Ticker API, recreate `RaydiumPoolPriceSource` in the old package, or otherwise redesign these module boundaries as part of this issue.
## Goal
Complete the existing general `RaydiumPoolPriceSource` scaffold so that one instance represents exactly one Raydium pool and periodically delivers its price to AssetAZ Ticker through the existing `PriceSink` interface.
For now, the selected pool must be hardcoded when constructing the source in `NenjimHubImpl`. Configuration files and Nenjim discovery must not be added as part of this issue.
## OpenSpec Changes
Create a new OpenSpec change for issue #63.
The change must:
* Add a new capability delta named `assetaz-raydium-pool-price-source` describing the Raydium-specific PriceSource behavior.
* Add a delta to the existing `assetaz-currency-identity-service` capability describing the addition of USDT.
* Document the package and future module boundaries from commit `03be810` in the change’s `design.md` and relevant implementation tasks.
* Leave the canonical `assetaz-ticker-service` specification unchanged unless the implementation reveals an actual behavioral change to the generic Ticker contract.
* Leave the archived changes for issues #60, #61, and #62 unchanged. Their old package names are historical documentation and must not be rewritten solely because the classes have moved.
Raydium-specific behavior must not be added to the generic `assetaz-ticker-service` capability.
## Add USDT to CIS
The pool’s quote currency is USDT, which does not yet exist in the CIS catalog.
Add:
* A stable `USDT_ID` to `CurrencyTypeIds`.
* A canonical currency named `"Tether USD"` with the symbol `"USDT"`.
* The following Solana mint reference:
```text
Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB
```
USDT and USDC must remain separate `CurrencyType` identities. No implicit conversion between them must be performed.
## `RaydiumPoolPriceSource`
Complete the existing `PriceSource` scaffold located at:
```text
com.r35157.assetaz.services.ticker.plugins.pricesource.impl.raydiumpool.RaydiumPoolPriceSource
```
Its public constructor should conceptually receive:
```java
RaydiumPoolPriceSource(
Raydium raydium,
ΩRaydiumLiquidityPoolIdΩ poolId,
TradingPair expectedTradingPair
)
```
The constructor must:
* Validate its arguments.
* Store the pool ID and expected trading pair.
* Leave the source in a stopped state.
* Perform no network calls.
* Not receive or otherwise know a `PriceSink` before `start(...)` is invoked.
* Have no knowledge of `TickerService`.
One `RaydiumPoolPriceSource` instance represents exactly one pool.
### Source Identity
`getTradingPair()` must return the expected trading pair supplied to the constructor.
`getSourceName()` must return:
```text
Raydium-<poolId>
```
For the first source, the name will therefore be:
```text
Raydium-8rN4BTEzbogQosEQYgsEu18XwfKS5Yoxqwit8zEVFwEe
```
The pool address represents the source/market identity and must not be registered as an external currency reference in CIS.
## Polling
When the source is started:
* The first price retrieval attempt must begin immediately.
* Subsequent retrieval attempts must use a fixed delay of one minute after the previous attempt has completed.
* Polling must run on a source-owned daemon scheduler thread.
* The price must be retrieved using:
```java
raydium.fetchPoolPrice(poolId)
```
The returned `AssetPrice.tradingPair()` must be compared with the expected trading pair stored by the source.
If the trading pair does not match:
* The observation must not be published.
* A clear error must be logged containing the pool ID, expected pair, and received pair.
* Polling must continue at the next interval.
For a valid response, the price value must be published through the `PriceSink` supplied to `start(...)`.
The observation timestamp must be created after the successful Raydium response and truncated to milliseconds before the observation is published.
An internal or package-private constructor may make the `Clock` and polling delay injectable so that the behavior can be verified without waiting one minute. The production constructor must continue to use UTC and a one-minute interval.
## Error Handling and Lifecycle
Temporary network, Raydium, parsing, and persistence errors must:
* Be logged with the pool ID and trading pair.
* Not stop the Ticker.
* Not cancel future polling.
* Be retried at the next interval.
`start(...)` must reject repeated invocation before replacing the existing sink.
`stop()` must:
* Be idempotent.
* Stop the scheduler thread.
* Allow the source to be started again later.
* Handle interruption correctly without leaving resources running.
## Temporary Wiring in `NenjimHubImpl`
`NenjimHubImpl` must construct the required `Raydium` implementation and then create the source using the hardcoded pool ID:
```java
TradingPair eveUsdt = new TradingPair(
currencyIdentityService.resolve(EVE_ID),
currencyIdentityService.resolve(USDT_ID)
);
PriceSource priceSource = new RaydiumPoolPriceSource(
raydium,
"8rN4BTEzbogQosEQYgsEu18XwfKS5Yoxqwit8zEVFwEe",
eveUsdt
);
```
Use the correct `ΩRaydiumLiquidityPoolIdΩ` type in the actual `.tjava` code.
The source must be injected through the existing temporary `TickerServiceImpl` constructor.
`HardcodedPriceSource` must remain in the codebase and remain wired and running exactly as it is now. The new `RaydiumPoolPriceSource` must run alongside it.
## Activation and Price History
Preserve the existing Ticker behavior: the source is active only when its exact history file already exists.
The expected path will be:
```text
data/assetaz/ticker/
<EVE_ID>/
<USDT_ID>/
Raydium-8rN4BTEzbogQosEQYgsEu18XwfKS5Yoxqwit8zEVFwEe/
EVE_USDT.prices
```
The file and its directories must not be created automatically. An empty, manually created file activates the source.
The existing hardcoded EVE/USDC history must not be moved or copied.
## Out of Scope
This issue must not add:
* A configuration file for Raydium sources.
* Dynamic Nenjim discovery.
* WebSocket or streaming support.
* Exponential backoff.
* A health/status API.
* A separate `MarketReference` model.
* Automatic creation or migration of history files.
* USDT/USDC conversion.
* Price visualization in Evelyn Mission Control.
* Any redesign of the module boundaries introduced by commit `03be810`.
The EMC graph will be a subsequent issue that consumes the new Ticker price.
## Final Verification
* Verify that USDT can be resolved through both `USDT_ID` and its Solana mint.
* Verify that the trading pair returned for the EVE/USDT pool matches the expected pair.
* Verify that the source name contains the pool ID and produces a separate history.
* Verify that a successful observation is persisted before being published by the Ticker.
* Verify that a temporary polling failure does not prevent subsequent attempts.
* Verify that `stop()` terminates polling and that the source can subsequently be restarted.
* Verify that the module and package boundaries introduced by commit `03be810` remain intact.
* Verify that `HardcodedPriceSource` remains wired and running alongside `RaydiumPoolPriceSource`.
* Compile both the main and test source sets without running unit tests.
* Run strict OpenSpec validation.
* Run `git diff --check`.
* Review the complete diff for incomplete wiring, old package imports, and unintended changes to `HardcodedPriceSource`.
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
Issue #61 introduced the
PriceSource/PriceSinkarchitecture for AssetAZ Ticker, and issue #62 addedCurrencyIdentityService, allowing external currency identities to be resolved to canonical AssetAZ currencies.The next step is to add a new PriceSource implementation for Raydium pools with the first real prices.
The first source must follow this Raydium concentrated liquidity pool:
The pool’s trading pair is:
This will provide Evelyn with a real market price that can later, for example, be displayed as a graph in Evelyn Mission Control.
Starting Point and Existing Module Structure
This issue starts from commit
03be810.The package and future module boundaries introduced by that commit are intentional and must be preserved:
TickerServiceandPriceObservationbelong to the Ticker API.PriceSourceandPriceSinkbelong together in the PriceSource plugin API:TickerServiceImplis the Ticker reference implementation.HardcodedPriceSourceis a separate PriceSource implementation.RaydiumPoolPriceSourceis a separate PriceSource implementation and already exists as an unimplemented scaffold.Do not move
PriceSinkback into the Ticker API, recreateRaydiumPoolPriceSourcein the old package, or otherwise redesign these module boundaries as part of this issue.Goal
Complete the existing general
RaydiumPoolPriceSourcescaffold so that one instance represents exactly one Raydium pool and periodically delivers its price to AssetAZ Ticker through the existingPriceSinkinterface.For now, the selected pool must be hardcoded when constructing the source in
NenjimHubImpl. Configuration files and Nenjim discovery must not be added as part of this issue.OpenSpec Changes
Create a new OpenSpec change for issue #63.
The change must:
assetaz-raydium-pool-price-sourcedescribing the Raydium-specific PriceSource behavior.assetaz-currency-identity-servicecapability describing the addition of USDT.03be810in the change’sdesign.mdand relevant implementation tasks.assetaz-ticker-servicespecification unchanged unless the implementation reveals an actual behavioral change to the generic Ticker contract.Raydium-specific behavior must not be added to the generic
assetaz-ticker-servicecapability.Add USDT to CIS
The pool’s quote currency is USDT, which does not yet exist in the CIS catalog.
Add:
USDT_IDtoCurrencyTypeIds."Tether USD"with the symbol"USDT".USDT and USDC must remain separate
CurrencyTypeidentities. No implicit conversion between them must be performed.RaydiumPoolPriceSourceComplete the existing
PriceSourcescaffold located at:Its public constructor should conceptually receive:
The constructor must:
PriceSinkbeforestart(...)is invoked.TickerService.One
RaydiumPoolPriceSourceinstance represents exactly one pool.Source Identity
getTradingPair()must return the expected trading pair supplied to the constructor.getSourceName()must return:For the first source, the name will therefore be:
The pool address represents the source/market identity and must not be registered as an external currency reference in CIS.
Polling
When the source is started:
The returned
AssetPrice.tradingPair()must be compared with the expected trading pair stored by the source.If the trading pair does not match:
For a valid response, the price value must be published through the
PriceSinksupplied tostart(...).The observation timestamp must be created after the successful Raydium response and truncated to milliseconds before the observation is published.
An internal or package-private constructor may make the
Clockand polling delay injectable so that the behavior can be verified without waiting one minute. The production constructor must continue to use UTC and a one-minute interval.Error Handling and Lifecycle
Temporary network, Raydium, parsing, and persistence errors must:
start(...)must reject repeated invocation before replacing the existing sink.stop()must:Temporary Wiring in
NenjimHubImplNenjimHubImplmust construct the requiredRaydiumimplementation and then create the source using the hardcoded pool ID:Use the correct
ΩRaydiumLiquidityPoolIdΩtype in the actual.tjavacode.The source must be injected through the existing temporary
TickerServiceImplconstructor.HardcodedPriceSourcemust remain in the codebase and remain wired and running exactly as it is now. The newRaydiumPoolPriceSourcemust run alongside it.Activation and Price History
Preserve the existing Ticker behavior: the source is active only when its exact history file already exists.
The expected path will be:
The file and its directories must not be created automatically. An empty, manually created file activates the source.
The existing hardcoded EVE/USDC history must not be moved or copied.
Out of Scope
This issue must not add:
MarketReferencemodel.03be810.The EMC graph will be a subsequent issue that consumes the new Ticker price.
Final Verification
USDT_IDand its Solana mint.stop()terminates polling and that the source can subsequently be restarted.03be810remain intact.HardcodedPriceSourceremains wired and running alongsideRaydiumPoolPriceSource.git diff --check.HardcodedPriceSource.