Files
com_r35157_nenjim-hubd-impl…/openspec/specs/evelyn-iou-burner-service/spec.md
T

230 lines
21 KiB
Markdown

# evelyn-iou-burner-service Specification
## Purpose
Defines the Evelyn-specific service that safely converts prioritized wallet assets into Evelyn IOU, burns the complete visible EVE balance, and announces only finalized burns without automatically repeating transactions whose outcomes may be unknown.
## Requirements
### Requirement: Evelyn-specific public service boundary
The system SHALL expose `EvelynIOUBurnerService` under `com.fanitas.evelyn.service.burner` with only `start()` and `stop()` operations, and SHALL keep its reference implementation under `com.fanitas.evelyn.service.burner.impl.ref`. The service SHALL remain specific to Evelyn IOU and SHALL NOT introduce a generic token-burner abstraction, burner-specific input interfaces, records, or class hierarchies.
#### Scenario: Downstream lifecycle consumer
- **WHEN** a downstream component depends on the Evelyn burner API
- **THEN** it can start and stop the service without depending on reference-implementation types or a generic burner model
#### Scenario: Input model remains domain-level
- **WHEN** callers configure accepted buyback currencies
- **THEN** they use existing `MoneyAmount` values rather than burner-specific Solana, SPL-token, or input-asset types
### Requirement: Ordered AssetAZ input configuration
The authoritative constructor SHALL accept a non-null ordered `List<MoneyAmount>` named `inputCurrencyMinimumReserves`, defensively copy it without changing order, and interpret inclusion as acceptance, list position as priority, each `currencyType().id()` as the authoritative AssetAZ identity, and each `amount()` as the minimum balance that must remain. It SHALL ignore supplied currency name and symbol metadata by resolving each UUID through the injected Currency Identity Service and using the canonical returned currency for pricing and processing. An empty list SHALL be valid and SHALL still permit directly deposited EVE to be burned.
#### Scenario: Stale configured metadata
- **WHEN** a configured `MoneyAmount` carries a known UUID but stale name or symbol metadata
- **THEN** the service processes the current canonical currency resolved for that UUID
#### Scenario: Priority is preserved
- **WHEN** multiple configured entries have spendable balances and usable prices
- **THEN** the earliest entry in the defensively copied list is the only input selected in that iteration
#### Scenario: Empty input list
- **WHEN** no buyback inputs are configured but the wallet contains EVE
- **THEN** the service skips input selection and still evaluates the complete EVE balance for burning
### Requirement: Complete constructor validation
Construction SHALL require all injected services, the input list, maximum swap amount, and durations to be non-null; every list entry, entry amount, entry currency, and currency UUID to be non-null; each reserve to be zero or greater; each UUID to be known to the injected Currency Identity Service; UUIDs to be unique; and EVE not to be an input currency. It SHALL require `maximumSwapAmountInUSDC` to be greater than zero, slippage to be from 0 through 10000 basis points inclusive, `maximumPriceAge` to be greater than zero milliseconds, and both finalization timeout and iteration interval to be positive and non-zero.
Every configured non-SOL UUID SHALL resolve through reverse CIS lookup to exactly one external reference in the `solana-mint` namespace, and that reference's external ID SHALL be non-null and non-blank. Construction SHALL fail for a missing, ambiguous, null, or blank non-SOL mint representation. These burner-specific rules SHALL NOT change the general-purpose `MoneyAmount` contract.
#### Scenario: Valid configuration
- **WHEN** every dependency and value is valid, reserves are non-negative, UUIDs are distinct and known, EVE is absent, and every non-SOL input has one non-blank Solana mint reference
- **THEN** construction succeeds and preserves the configured priority order
#### Scenario: Invalid reserve or operating limit
- **WHEN** a reserve is negative or null, the USDC cap is non-positive, slippage is outside its inclusive range, maximum price age is non-positive, or either duration is null or non-positive
- **THEN** construction fails before a worker or transaction is created
#### Scenario: Invalid input identity
- **WHEN** an entry or its currency or UUID is null, a UUID is unknown or duplicated, or the EVE UUID is configured as input
- **THEN** construction fails with a diagnostic identifying the invalid configuration
#### Scenario: Non-SOL representation is not unique and usable
- **WHEN** a configured non-SOL currency has zero or multiple `solana-mint` references or its sole external ID is null or blank
- **THEN** construction fails without adding mint or token-program selection to Evelyn's configuration model
### Requirement: Fixed Evelyn domain values and internally resolved integration values
The service SHALL fix the EVE mint as `meveYG2iXYSkgSUn1T1uxcthH1EGMZdRHGgCntXZA3Y`, construct the EVE/USDT notification-price pair from canonical CIS resolutions of the stable EVE and USDT UUIDs, construct input/USDC pairs with the canonical USDC UUID, and use fixed notification wording. Native SOL SHALL be identified only by the stable SOL UUID; when sent to Jupiter, its input mint SHALL be the canonical CIS Solana-mint reference `So11111111111111111111111111111111111111112`. Mint addresses, token programs, price pairs, notification wording, and a separate SOL reserve SHALL NOT be constructor configuration.
#### Scenario: Native SOL configuration
- **WHEN** a configured input UUID is the stable SOL UUID
- **THEN** the service reads the native wallet balance and later supplies Jupiter with the CIS-resolved canonical SOL/WSOL mint rather than treating SOL as an SPL-token holding
#### Scenario: Fixed EVE behavior
- **WHEN** the service evaluates a burn and notification
- **THEN** it uses the fixed EVE mint and the canonical EVE/USDT pair without accepting replacements through construction
### Requirement: Immediate sequential fixed-delay lifecycle
Each service instance SHALL own at most one dedicated background worker. A successful `start()` SHALL begin the first iteration immediately; every later iteration SHALL begin only after the previous iteration completes and the configured fixed delay elapses; and iterations SHALL never overlap. `stop()` SHALL prevent new iterations, interrupt and stop the worker cleanly, and preserve interruption if the stopping thread is interrupted. A repeated start while active SHALL NOT create another worker.
Pending burn information and swap/burn suspension flags SHALL be process-local only. A completed explicit stop followed by start SHALL clear those states. The public JavaDoc SHALL disclose that this reset, or a process restart, loses reconciliation state and can weaken duplicate-prevention until unknown transactions have been reconciled externally.
#### Scenario: First and later iterations
- **WHEN** the service starts and one iteration takes longer than usual
- **THEN** the first iteration starts without initial delay and the next starts only after completion plus the configured interval
#### Scenario: Active service is started again
- **WHEN** `start()` is called while the instance already owns an active worker
- **THEN** the call is rejected without creating an overlapping worker
#### Scenario: Explicit lifecycle reset
- **WHEN** a service with pending or suspended process-local state is cleanly stopped and then explicitly started
- **THEN** the new lifecycle begins without that state and reloads wallet and chain state normally
### Requirement: Pending burn is handled before all new work
At the start of an iteration, if a prior burn signature is pending, the service SHALL only await that exact signature at `FINALIZED`. It SHALL submit no swap or burn in that iteration. An unknown timeout or I/O outcome SHALL retain the pending state and end the iteration; definitive success SHALL make the one associated notification attempt and end the iteration; and definitive on-chain failure SHALL log the slot and complete failure details, clear pending state, send no notification, and defer any new wallet decision to a later iteration.
#### Scenario: Pending result remains unknown
- **WHEN** awaiting the retained signature times out or fails through I/O
- **THEN** the service retains that signature and performs no swap, burn, or notification in the iteration
#### Scenario: Pending burn succeeds
- **WHEN** the retained signature reaches `FINALIZED` with a successful outcome
- **THEN** the service performs its sole notification attempt and does no other transaction work in that iteration
#### Scenario: Pending burn fails definitively
- **WHEN** the retained signature reaches `FINALIZED` with an on-chain failure
- **THEN** the service logs its slot and complete failure details, clears it without notification, and waits until a later iteration before evaluating another burn
### Requirement: Prioritized input inspection with internal Solana translation
Unless swaps are suspended, an iteration SHALL inspect configured input entries in order until it finds the first usable candidate. For every entry it SHALL resolve the configured UUID to its canonical currency. Native SOL SHALL use `getSolanaBalance().amount()` and nine-decimal precision. Every other input SHALL use its unique CIS-resolved Solana mint; the service SHALL inspect the mint account owner to detect the original SPL Token Program or Token-2022, use that program for wallet balance lookup, and obtain precision from Solana mint metadata. A missing SPL-token account SHALL equal zero balance.
For each entry the service SHALL subtract its configured reserve and skip non-positive spendable balance. Missing or unusable balance, mint account, supported program, mint metadata, or price SHALL produce a warning and allow later configured entries to be examined. Interruption SHALL preserve the thread interrupt state and end processing rather than becoming a skipped candidate. Failure to select an input SHALL NOT prevent the later complete-EVE burn evaluation.
#### Scenario: Missing token account
- **WHEN** a configured non-SOL mint is valid but the wallet has no token account under its detected program
- **THEN** that input is treated as a zero balance and later inputs are considered
#### Scenario: Missing or unsupported mint account
- **WHEN** a configured token's mint account is absent or owned by an unsupported program
- **THEN** the service warns, skips that input, considers later priorities, and still reaches the EVE-balance step
#### Scenario: Candidate read is interrupted
- **WHEN** wallet, CIS-adjacent processing, or Solana access is interrupted while inspecting an input
- **THEN** the service preserves interruption and ends processing without treating the input as an ordinary failure
### Requirement: Fresh canonical input prices
For each non-USDC candidate, the service SHALL request the canonical `<INPUT_CURRENCY>/USDC` pair and accept only a positive price with a non-null observation timestamp no more than `maximumPriceAge` milliseconds old at the current iteration time. The stable USDC UUID SHALL instead receive an exact price of one without a ticker observation. A missing, unsupported, invalid, or stale input price SHALL be warned about using canonical currency identity, SHALL allow the next configured input to be considered, and SHALL NOT prevent later EVE burning.
#### Scenario: USDC is the input
- **WHEN** the canonical input UUID is USDC and its spendable balance is positive
- **THEN** the service uses exactly one USDC per USDC without querying the ticker for that input price
#### Scenario: Input price is unusable
- **WHEN** an input observation is missing, unsupported, non-positive, lacks a timestamp, or is older than the maximum age
- **THEN** the service does not swap that currency and continues with later configured priorities
### Requirement: Exact USDC-capped amount and per-currency reserve
For a usable candidate, the service SHALL calculate `availableForSwap = max(balance - minimumReserve, 0)`, calculate `maximumInputAmount = maximumSwapAmountInUSDC / inputPriceInUSDC`, select the lesser amount, and round downward to the input currency's supported decimal precision. It SHALL skip a result that rounds to zero and SHALL never intentionally swap more than either the spendable balance or the configured USDC-equivalent maximum through rounding.
The SOL reserve comparison SHALL use the native balance without estimating or subtracting future transaction fees. The USDC maximum SHALL limit buybacks only and SHALL NOT cap how much EVE may be burned.
#### Scenario: Price cap is lower than spendable balance
- **WHEN** a currency has five spendable units, its price is 100 USDC per unit, and the USDC maximum is one
- **THEN** no more than 0.01 unit is selected before downward precision rounding
#### Scenario: Reserve consumes the balance
- **WHEN** the current balance is equal to or below that entry's minimum reserve
- **THEN** no amount of that currency is intentionally swapped
#### Scenario: Native transaction fees
- **WHEN** native SOL is selected with a configured reserve
- **THEN** the service enforces the reserve against the observed balance only and does not estimate fees, even though later fees may reduce the final balance or cause execution to fail
### Requirement: At most one confirmed swap attempt per iteration
After selecting a positive exact amount, the service SHALL invoke the existing Jupiter exact-input operation once with the selected CIS-resolved input mint, fixed EVE output mint, selected amount, and configured slippage. Normal return SHALL be treated as already `CONFIRMED` and SHALL NOT trigger a second confirmation wait. Once invoked, no later input currency SHALL be attempted in that iteration regardless of success or failure, and the service SHALL proceed to reload the complete EVE balance whenever processing was not interrupted.
A structured definitive on-chain swap failure SHALL be logged without suspending later-lifecycle swaps. A timed-out structured outcome SHALL suspend all later swaps in that lifecycle. Because the Jupiter `IOException` and interruption contracts can represent an unknown post-submission result, either SHALL conservatively suspend later swaps. The service SHALL never automatically rebuild, re-sign, retry, or resubmit such a swap; while swaps are suspended, later iterations SHALL continue only their burn path.
#### Scenario: First swap fails definitively
- **WHEN** the first attempted input returns a definitive on-chain failure
- **THEN** no second input is attempted in that iteration, the EVE balance is still reloaded, and a later iteration may attempt a new swap
#### Scenario: Swap outcome is unknown
- **WHEN** the attempted swap times out, throws an I/O failure that permits unknown post-submission status, or is interrupted
- **THEN** no equivalent or alternative swap is attempted and all later swaps remain suspended until explicit stop/start
#### Scenario: Swap returns normally
- **WHEN** Jupiter returns a successful result whose transaction already reached `CONFIRMED`
- **THEN** the burner performs no additional confirmation wait and reloads the wallet instead of deriving its burn amount only from the returned output amount
### Requirement: Complete visible EVE balance is eligible
After the optional swap attempt, the service SHALL detect the fixed EVE mint's supported token program internally and reload the wallet's complete available EVE balance. A missing EVE mint account, unsupported program, absent wallet token account, zero balance, or negative/invalid balance SHALL cause no burn or notification in that iteration. Every positive complete balance SHALL be eligible without a minimum threshold, including EVE deposited before the iteration or visible after a failed or absent swap.
#### Scenario: Existing EVE without buyback
- **WHEN** no swap is performed but the wallet contains a positive EVE balance
- **THEN** the service evaluates that complete balance for burning
#### Scenario: EVE account is absent or empty
- **WHEN** the wallet has no EVE token account or its complete balance is zero
- **THEN** the iteration submits no burn and sends no notification
### Requirement: Fresh EVE value is required before submission
Immediately before a new burn, the service SHALL obtain the latest canonical EVE/USDT observation and require a positive price, non-null observation timestamp, and age no greater than `maximumPriceAge` milliseconds. USDT SHALL be treated as USD for notification value, calculated as the complete EVE amount multiplied by that fresh price. If the pair is unsupported or the observation is missing, invalid, or stale, the service SHALL warn, postpone the burn, send no notification, and retry only by re-evaluating state in a later scheduled iteration.
#### Scenario: Fresh EVE price exists
- **WHEN** a complete positive EVE balance and sufficiently fresh positive EVE/USDT observation are available
- **THEN** the service calculates the notification's USD value from that complete balance before submitting the burn
#### Scenario: EVE price is stale
- **WHEN** EVE is present but its observation is older than the configured age limit
- **THEN** the service leaves EVE unburned and sends no notification in that iteration
### Requirement: Single complete-balance burn submission and pending state
For a positive EVE balance with a usable price, the service SHALL call the wallet burn operation exactly once with the fixed EVE mint and complete observed balance. It SHALL NOT automatically retry, rebuild, re-sign, or resubmit within or across iterations. Immediately after a non-blank signature is returned, it SHALL retain process-local pending data containing that signature, submitted EVE amount, and its calculated notification value or complete notification text before awaiting the signature.
If burn submission fails without a usable signature and its outcome may be unknown, including an I/O or interruption path whose contract permits post-submission uncertainty, the service SHALL conservatively suspend further burn submissions for the current lifecycle. It MAY continue ordinary iteration work that does not submit another burn. Explicit stop/start SHALL reset that suspension.
#### Scenario: Burn returns a signature
- **WHEN** the wallet submits the complete observed EVE balance and returns a non-blank signature
- **THEN** pending state is retained before the service begins finalization waiting
#### Scenario: Submission outcome may be unknown without a signature
- **WHEN** burn submission fails or is interrupted without providing a usable signature and may already have reached Solana
- **THEN** the service performs no further burn submission until explicit stop/start
### Requirement: Finalized burn outcome governs completion
Every known burn signature SHALL be awaited through the Solana blockchain at `FINALIZED` with exactly the configured finalization timeout. `SUCCEEDED` SHALL authorize notification; `FAILED` SHALL log the slot and complete on-chain failure details, clear pending state, and send no notification; `TIMED_OUT` and `IOException` SHALL keep the pending signature because its result remains unknown; and `InterruptedException` SHALL preserve interruption and pending state. None of these outcomes SHALL cause automatic transaction resubmission.
#### Scenario: Burn finalizes successfully
- **WHEN** the submitted signature reaches `FINALIZED` without an on-chain error
- **THEN** the service may perform the notification attempt associated with that exact burn
#### Scenario: Burn fails on-chain
- **WHEN** the signature reaches `FINALIZED` with an on-chain error
- **THEN** the service logs its slot and complete error, clears pending state, and never announces that burn
#### Scenario: Finalized result remains unknown
- **WHEN** finalization times out or communication fails
- **THEN** the service retains the exact signature and later awaits it again without submitting another burn
### Requirement: Exactly one post-finalization notification attempt
Only after a burn reaches successful `FINALIZED` status, the service SHALL invoke the bound notification service exactly once with this structure, using the burn signature rather than any swap signature:
```text
🔥🔥🔥 We have bought back and burned <EVE_AMOUNT> EVE (Evelyn IOU tokens, value $<USD_VALUE>) to reduce the circulating supply! 🔥🔥🔥
Proof:
https://solscan.io/tx/<BURN_TRANSACTION_SIGNATURE>
```
The EVE amount SHALL use plain decimal notation without unnecessary trailing zeroes or scientific notation. The USD value SHALL have exactly two decimal places using half-up monetary rounding. The blank line before `Proof:` SHALL be preserved. Before invoking delivery, the service SHALL make the completed pending burn ineligible for another attempt. Delivery failure SHALL be logged without notification secrets and SHALL NOT retry notification or repeat the burn.
#### Scenario: Successful finalized burn is announced
- **WHEN** 10 EVE with a calculated value of 150.225 USDT reaches successful `FINALIZED` status under a burn signature
- **THEN** one message reports `10 EVE`, `$150.23`, preserves the blank line, and links to that burn signature
#### Scenario: Notification delivery fails
- **WHEN** the sole notification invocation throws
- **THEN** the service logs a non-secret failure, clears completed state, and never retries either notification or burn