63: Add Raydium Pool PriceSource with periodic polling
This commit is contained in:
+2
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-08-08
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
## Context
|
||||
|
||||
See `proposal.md` for motivation. Commit `2fc553b` established package ownership that anticipates later extraction into Nenjim modules while the code remains in one Gradle project. The existing Raydium Pool PriceSource is an unimplemented scaffold in its intended implementation package. The Ticker already activates registered sources only when their exact history file exists and persists callbacks before updating its in-memory latest observation.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- Complete the existing pool-source scaffold as an independently constructible, restartable PriceSource.
|
||||
- Preserve the package and future artifact dependency direction introduced by `2fc553b`.
|
||||
- Compose one EVE/USDT pool source beside the existing hardcoded EVE/USDC source using today's temporary Cauldron wiring.
|
||||
- Make timing deterministic enough for verification through package-private clock and delay injection.
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- Dynamic Nenjim discovery, configuration files, streaming, backoff, health APIs, history creation or migration, visualization, currency conversion, or further physical module splitting.
|
||||
- Changes to the generic Ticker behavior or canonical Ticker specification.
|
||||
|
||||
## Decisions
|
||||
|
||||
### Preserve future module and package ownership
|
||||
|
||||
The future boundaries remain:
|
||||
|
||||
```text
|
||||
Ticker API
|
||||
com.r35157.assetaz.services.ticker
|
||||
- TickerService
|
||||
- PriceObservation
|
||||
|
||||
PriceSource plugin API
|
||||
com.r35157.assetaz.services.ticker.plugins.pricesource
|
||||
- PriceSource
|
||||
- PriceSink
|
||||
|
||||
Ticker reference implementation
|
||||
com.r35157.assetaz.services.ticker.impl.ref
|
||||
-> Ticker API
|
||||
-> PriceSource plugin API
|
||||
|
||||
Hardcoded PriceSource implementation
|
||||
com.r35157.assetaz.services.ticker.plugins.pricesource.impl.hardcoded
|
||||
-> PriceSource plugin API
|
||||
|
||||
Raydium Pool PriceSource implementation
|
||||
com.r35157.assetaz.services.ticker.plugins.pricesource.impl.raydiumpool
|
||||
-> PriceSource plugin API
|
||||
-> Raydium API and value types
|
||||
```
|
||||
|
||||
`PriceSink` stays in the plugin API so implementations do not depend on the Ticker API or reference implementation. `TickerServiceImpl` implements `PriceSink`, owns source lifecycle, and temporarily receives sources through its varargs constructor. `NenjimHubImpl` remains the temporary composition root; dynamic discovery is deferred. The alternative of moving the sink or source packages would recreate future circular or implementation dependencies and is explicitly rejected.
|
||||
|
||||
### Use a source-owned single-thread fixed-delay scheduler
|
||||
|
||||
Each source creates a daemon single-thread scheduled executor when started. The immediate attempt is submitted to that executor with zero initial delay, followed by fixed-delay scheduling so a slow or paused call cannot produce catch-up observations. A package-private constructor injects `Clock`, delay, and time unit; the public constructor fixes UTC and one minute. A shared scheduler was rejected because lifecycle and termination must belong to the source instance.
|
||||
|
||||
### Contain each attempt at the scheduler boundary
|
||||
|
||||
One safe polling method catches retrieval, validation, and sink-publication failures, logs pool and pair context, and returns normally so fixed-delay scheduling continues. Pair mismatch is treated as an invalid observation rather than a fatal lifecycle error. The timestamp is read only after a successful matching response and truncated to milliseconds before the sink callback.
|
||||
|
||||
### Keep startup and shutdown state synchronized
|
||||
|
||||
Startup checks the running state before validating or assigning the new sink, creates fresh scheduling resources, and schedules the immediate task. Stop detaches the scheduler under synchronization, requests immediate shutdown, and waits for termination without holding the lifecycle monitor. Interrupted waiting restores the caller's interrupt flag and ensures shutdown remains requested. This avoids replacing an active sink and permits restart after resource termination.
|
||||
|
||||
### Resolve EVE/USDT through the shared Currency Identity Service
|
||||
|
||||
The API identifier class gains only a stable USDT UUID. The hardcoded catalogue owns canonical metadata and the official Solana mint mapping. NenjimHub constructs the expected pair by resolving EVE and USDT through its single shared CIS instance, constructs Raydium with that same CIS dependency, and supplies both hardcoded and Raydium sources to the temporary Ticker constructor. The pool ID is market/source identity and is not registered in CIS.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- [A blocking Raydium call can delay shutdown until the call honors interruption] → request immediate shutdown, preserve interruption, and keep the polling thread daemonized.
|
||||
- [A missing manually provisioned history silently leaves the new source inactive] → retain the existing explicit warning and document the exact pool-based history path.
|
||||
- [Temporary constructor injection can be mistaken for permanent discovery] → document it as Cauldron wiring and preserve interfaces and packages for later Nenjim discovery.
|
||||
- [A response may reverse or otherwise change the configured pair] → validate exact pair equality and publish nothing on mismatch.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
Add USDT identity data, complete the existing source, and extend only the NenjimHub composition graph. Deployment requires manually creating the exact empty EVE/USDT pool history file to activate polling. Rollback removes that file or removes the temporary source wiring; existing hardcoded EVE/USDC history is unchanged.
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
## Why
|
||||
|
||||
AssetAZ Ticker currently has only a hardcoded price source and therefore cannot observe Evelyn IOU's real Raydium market price. A pool-specific plugin is needed to poll the EVE/USDT pool while preserving the PriceSource lifecycle and future Nenjim module boundaries.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Complete the existing Raydium Pool PriceSource scaffold as a lifecycle-managed, fixed-delay polling source representing one Raydium pool.
|
||||
- Add Tether USD (USDT) as a stable, externally resolvable AssetAZ currency distinct from USDC.
|
||||
- Temporarily compose the EVE/USDT Raydium source in NenjimHub alongside the existing hardcoded EVE/USDC source.
|
||||
- Preserve history activation, persistence-before-publication, package ownership, and future artifact boundaries without adding discovery, configuration, streaming, conversion, or automatic history creation.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `assetaz-raydium-pool-price-source`: Defines pool identity, polling, validation, publication, failure isolation, and restartable lifecycle behavior for a Raydium-backed PriceSource.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `assetaz-currency-identity-service`: Adds the stable canonical USDT identity and official Solana mint mapping while keeping it distinct from USDC.
|
||||
|
||||
## Impact
|
||||
|
||||
The change affects the AssetAZ Currency Identity Service catalogue and identifiers, the existing Raydium Pool PriceSource implementation scaffold, and temporary NenjimHub composition. It uses the existing Raydium and PriceSource APIs and does not change the generic Ticker service contract or create new Gradle subprojects.
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Stable Tether USD identity
|
||||
The Currency Identity Service SHALL provide Tether USD as a stable AssetAZ currency with canonical name `Tether USD`, canonical symbol `USDT`, and an implementation-independent UUID identifier. It SHALL resolve that currency through both the stable UUID and official Solana mint `Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB`. USDT and USDC SHALL remain distinct currency identities, and resolution SHALL NOT imply conversion between them.
|
||||
|
||||
#### Scenario: Resolve USDT by stable UUID
|
||||
- **WHEN** a client resolves the stable USDT UUID
|
||||
- **THEN** the service returns the canonical Tether USD currency with symbol USDT
|
||||
|
||||
#### Scenario: Resolve USDT by official Solana mint
|
||||
- **WHEN** a client resolves the official USDT mint in the Solana-mint namespace
|
||||
- **THEN** the service returns the same canonical USDT identity as UUID resolution
|
||||
|
||||
#### Scenario: Compare USDT and USDC
|
||||
- **WHEN** a client resolves both USDT and USDC
|
||||
- **THEN** they have different UUIDs and no implicit conversion is performed
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
## Purpose
|
||||
|
||||
Provides independently lifecycle-managed price observations for one Raydium pool through the AssetAZ PriceSource plugin contract.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: One source represents one stable Raydium pool
|
||||
A Raydium Pool PriceSource instance SHALL represent exactly one Raydium pool, SHALL expose the expected trading pair supplied at construction, and SHALL expose the stable source name `Raydium-<poolId>`. Construction SHALL validate all dependencies and identity values, leave the source stopped, perform no network access, and require no price sink until startup.
|
||||
|
||||
#### Scenario: Construct a pool source
|
||||
- **WHEN** a client constructs a source with a Raydium service, pool ID, and expected trading pair
|
||||
- **THEN** the stopped source reports that trading pair and a source name containing that exact pool ID without performing a price retrieval
|
||||
|
||||
#### Scenario: Reject invalid construction
|
||||
- **WHEN** a required constructor argument is absent or invalid
|
||||
- **THEN** construction fails without starting polling or accessing the network
|
||||
|
||||
### Requirement: Poll immediately with fixed delay
|
||||
On startup, the source SHALL begin its first retrieval attempt immediately and SHALL begin each subsequent attempt one minute after the preceding attempt completes. Polling SHALL execute on a daemon scheduler thread owned exclusively by that source instance.
|
||||
|
||||
#### Scenario: Start a source
|
||||
- **WHEN** a stopped source is started with a price sink
|
||||
- **THEN** its first pool-price retrieval begins immediately on its source-owned daemon scheduler
|
||||
|
||||
#### Scenario: Complete a periodic attempt
|
||||
- **WHEN** a retrieval attempt completes
|
||||
- **THEN** the next attempt begins after the configured one-minute delay without fixed-rate catch-up attempts
|
||||
|
||||
### Requirement: Validate and publish pool observations
|
||||
The source SHALL retrieve the configured pool price and compare the response trading pair with its expected pair. For a matching response, it SHALL create the observation timestamp after the successful response, truncate it to milliseconds, and publish the returned price value through the sink supplied at startup.
|
||||
|
||||
#### Scenario: Receive the expected pair
|
||||
- **WHEN** a successful pool response contains the expected trading pair
|
||||
- **THEN** the source publishes its price with a post-response timestamp truncated to milliseconds
|
||||
|
||||
#### Scenario: Receive a different pair
|
||||
- **WHEN** a pool response contains a trading pair different from the expected pair
|
||||
- **THEN** the source publishes no observation, logs the pool ID plus expected and received pairs, and schedules the next attempt normally
|
||||
|
||||
### Requirement: Isolate temporary polling failures
|
||||
Temporary network, Raydium, parsing, interruption, or downstream persistence failures SHALL be logged with the pool ID and expected trading pair and SHALL NOT cancel later polling attempts or stop the Ticker service.
|
||||
|
||||
#### Scenario: A polling attempt fails temporarily
|
||||
- **WHEN** a retrieval or publication attempt throws an exception
|
||||
- **THEN** the source logs the contextual failure and retries on the next fixed-delay interval
|
||||
|
||||
### Requirement: Restartable source lifecycle
|
||||
The source SHALL reject a repeated startup before replacing its existing sink. Stopping SHALL be idempotent, terminate source-owned scheduling resources, correctly preserve interruption, and permit a later startup with a newly supplied sink.
|
||||
|
||||
#### Scenario: Start an already-started source
|
||||
- **WHEN** startup is invoked while the source is already running
|
||||
- **THEN** startup fails and the currently active sink remains unchanged
|
||||
|
||||
#### Scenario: Stop a running source
|
||||
- **WHEN** stop is invoked on a running source
|
||||
- **THEN** its scheduler terminates and no further polling begins
|
||||
|
||||
#### Scenario: Stop an already-stopped source
|
||||
- **WHEN** stop is invoked while the source is stopped
|
||||
- **THEN** it completes without error
|
||||
|
||||
#### Scenario: Restart a stopped source
|
||||
- **WHEN** a previously stopped source is started with a sink
|
||||
- **THEN** it creates fresh source-owned scheduling resources and begins an immediate retrieval attempt
|
||||
|
||||
### Requirement: Activation uses independent Ticker history
|
||||
The pool source SHALL participate in the existing Ticker activation contract under its exact trading pair and pool-based source name. It SHALL be active only when that exact history file already exists and SHALL NOT create or migrate history files.
|
||||
|
||||
#### Scenario: Pool history exists
|
||||
- **WHEN** the EVE/USDT history file exists beneath the EVE UUID, USDT UUID, and exact pool-based source-name path
|
||||
- **THEN** the Ticker activates the Raydium source independently of the hardcoded EVE/USDC history
|
||||
|
||||
#### Scenario: Pool history is absent
|
||||
- **WHEN** the exact Raydium source history file is absent
|
||||
- **THEN** the Ticker leaves that source inactive and creates no file or directory
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
## 1. Currency Identity
|
||||
|
||||
- [x] 1.1 Add the API-owned stable USDT UUID and hardcoded canonical Tether USD catalogue entry with the official Solana mint.
|
||||
- [x] 1.2 Verify USDT resolution by UUID and mint, distinct identity from USDC, and absence of conversion or parallel registry data.
|
||||
|
||||
## 2. Raydium Pool PriceSource
|
||||
|
||||
- [x] 2.1 Complete the existing `impl.raydiumpool.RaydiumPoolPriceSource` scaffold with validated pool identity and no construction-time sink or network access.
|
||||
- [x] 2.2 Implement immediate source-owned daemon polling with one-minute fixed delay, response-pair validation, millisecond observation timestamps, sink publication, and contextual failure isolation.
|
||||
- [x] 2.3 Implement repeated-start rejection, idempotent resource-terminating stop, interruption handling, and restart with fresh scheduling resources.
|
||||
|
||||
## 3. Temporary Composition
|
||||
|
||||
- [x] 3.1 Construct the EVE/USDT pair and Raydium pool source in `NenjimHubImpl` using the shared Currency Identity Service and hardcoded pool ID.
|
||||
- [x] 3.2 Inject the Raydium source alongside the unchanged running `HardcodedPriceSource` through the temporary Ticker constructor.
|
||||
|
||||
## 4. Boundary and Behavior Review
|
||||
|
||||
- [x] 4.1 Verify the `2fc553b` package and future module boundaries: Ticker API, PriceSource/PriceSink plugin API, Ticker reference implementation, and separate hardcoded and Raydium source implementations.
|
||||
- [x] 4.2 Verify exact pool-based source naming and EVE/USDT history path, existing-file-only activation, persistence before publication, polling recovery, and stop/restart semantics without adding automated tests.
|
||||
- [x] 4.3 Review the complete diff for stale imports, duplicate implementations, incomplete wiring, unintended HardcodedPriceSource changes, and excluded features.
|
||||
|
||||
## 5. Build and Specification Verification
|
||||
|
||||
- [x] 5.1 Compile main and test source sets without running unit tests.
|
||||
- [x] 5.2 Run strict OpenSpec validation and `git diff --check`.
|
||||
- [x] 5.3 Sync both deltas into canonical specifications, archive the completed change, and rerun strict OpenSpec validation.
|
||||
Reference in New Issue
Block a user