Add Raydium Pool PriceSource with periodic polling #63

Closed
opened 2026-08-08 12:46:12 +02:00 by minimons · 0 comments
Owner

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:

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:
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:
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:

com.r35157.assetaz.services.ticker.plugins.pricesource.impl.raydiumpool.RaydiumPoolPriceSource

Its public constructor should conceptually receive:

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:

Raydium-<poolId>

For the first source, the name will therefore be:

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:
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:

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:

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.
## 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`.
minimons added the enhancement label 2026-08-08 12:46:12 +02:00
minimons self-assigned this 2026-08-08 12:46:12 +02:00
minimons added this to the AssetAZ project 2026-08-08 12:46:12 +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#63