93: Add a minimum EVE burn amount and retained reserve to the Evelyn IOU burner

This commit is contained in:
2026-10-06 08:27:59 +02:00
parent 49df1ac9d0
commit 00a7f13c6f
11 changed files with 394 additions and 26 deletions
+30 -4
View File
@@ -131,10 +131,36 @@ ingen inputtoken/USDC-pris eller nye Ticker-kilder og historikfiler.
Højst ét input forsøges vekslet pr. iteration, med en times pause efter hver Højst ét input forsøges vekslet pr. iteration, med en times pause efter hver
afsluttet iteration. Jupiter vælger pool og rute til EVE. USDT har fortsat første afsluttet iteration. Jupiter vælger pool og rute til EVE. USDT har fortsat første
prioritet, så en vedvarende spendabel USDT-saldo kan udsætte SyrupUSDC-veksling. prioritet, så en vedvarende spendabel USDT-saldo kan udsætte SyrupUSDC-veksling.
Hele den synlige EVE-saldo er derefter berettiget til burn, også ved direkte Efter buyback genlæses hele den synlige EVE-saldo, også ved direkte
EVE-indbetalinger. Burn kræver stadig en frisk EVE/USDT-observation gennem det EVE-indbetalinger. Ejeren har valgt et produktionsminimum på **0,1 EVE pr. burn**
uændrede `TickerService.getLatestPrice()` og bekræftes ved `FINALIZED` før det og en **reserve på 0,01 EVE**. Hub'en angiver begge som `MoneyAmount` med EVE's
eneste notifikationsforsøg. Inputgrænserne begrænser ikke EVE-burn. kanoniske UUID. Minimum skal være positivt, reserve skal være ikke-negativ;
en reserve på nul er gyldig. Null-værdier og andre valutaer afvises ved oprettelse.
Burnbeløbet beregnes som `max(EVE-saldo - EVE-reserve, 0)`, afrundet ned til
EVE-mintens præcision. Der brændes kun, når dette faktiske beløb er positivt og
mindst minimum. Når tærsklen nås, brændes hele det berettigede beløb over
reserven; minimum er en tærskel, og der er intet burn-maksimum. Eventuelle
afrundingsrester bliver i wallet'en sammen med reserven. Små buybacks fortsætter
uafhængigt og samler EVE op mellem burns. Under tærsklen sker hverken burn eller
burn-notifikation.
| EVE-saldo | Produktionshandling | EVE tilbage efter succes |
| ---: | --- | ---: |
| 0,105 | Ingen burn | 0,105 |
| 0,11 | Burn 0,1 EVE | 0,01 |
| 0,135 | Burn 0,125 EVE | 0,01 |
Issue #93 bruger minimum 1 EVE og reserve 0,1 EVE som adfærdseksempler:
1,05 EVE giver ingen burn, 1,1 EVE giver burn af 1 EVE, og 1,35 EVE giver burn af
1,25 EVE. Produktionsværdierne ovenfor blev valgt særskilt den 2026-10-06.
Burn kræver stadig en frisk EVE/USDT-observation gennem det uændrede
`TickerService.getLatestPrice()` og bekræftes ved `FINALIZED` før det eneste
notifikationsforsøg. Pending burn og notifikationens EVE-beløb og dollarværdi
bruger det faktisk indsendte burnbeløb uden reserven. Et pending burn afklares
med sin oprindelige signatur uden ny tærskelvurdering eller genindsendelse.
Inputgrænserne begrænser ikke EVE-burn.
Ved ukendt swap-resultat gemmes den oprindelige signatur, inputvaluta, beløb og Ved ukendt swap-resultat gemmes den oprindelige signatur, inputvaluta, beløb og
validitetsoplysninger i processen. Senere iterationer undersøger samme signatur validitetsoplysninger i processen. Senere iterationer undersøger samme signatur
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-06
@@ -0,0 +1,28 @@
# Design
## Context
See proposal.md for the operational need. The burner already reloads EVE after its independent buyback stage, retains known burn signatures before finalization, and prioritizes pending burns. It can reuse supported-program and mint-supply precision validation already used for input selection. Monetary values and CurrencyType are immutable.
## Goals / Non-Goals
**Goals:** Preserve a configured EVE reserve and accumulate small buys/deposits until a representable burn amount qualifies, retaining existing transaction and notification safety.
**Non-Goals:** Fixed burn batches, maximum burn size, a new configuration framework, notification batching, altered buybacks, persistent reconciliation or live deployment.
## Decisions
1. Add `minimumBurnAmount` and `eveReserve` MoneyAmount constructor arguments beside input bounds. Validate non-null amount/currency/UUID, canonical EVE UUID, positive minimum and non-negative reserve before startup, copying into canonical EVE MoneyAmount values. Stale display metadata is accepted by UUID. A separate record or ValueTag adds no needed domain safety.
2. Reload EVE after buybacks. Subtract the reserve and skip non-positive available balance, then resolve EVE mint precision using its detected token program. Round DOWN and compare that actual amount to the exact unrounded minimum. Skip non-positive/below-minimum amounts before price lookup/submission. Rounding configuration up or down would alter the owner's reserve/minimum; instead any fractional remainder stays in the wallet.
3. Submit all eligible EVE above reserve, using the same amount for price multiplication and PendingBurn. Keep pending-burn priority and awaiting unchanged so later thresholds/balances never rebuild an unresolved burn.
4. The owner chose production minimum **0.1 EVE** and reserve **0.01 EVE** on 2026-10-06. Supply both using canonical EVE in the production Hub. Issue #93's 1/0.1 values remain independent constructor acceptance examples, not production defaults.
## Risks / Trade-offs
- [EVE mint metadata unavailable] → Postpone burning without notification, using existing warning and interruption behavior; buybacks have already been evaluated independently.
- [Configuration precision exceeds mint precision] → Round only the eligible burn amount DOWN; compare to exact minimum and retain at least the exact reserve.
- [Unknown transaction outcome] → Preserve original signature and existing process-local suspension/reconciliation behavior. Restart still requires external reconciliation.
## Migration Plan
Compile the reference sources and update the production construction call together. Verify controlled threshold, precision, accumulation, direct-deposit, validation, pending-state and notification cases offline, synchronize specs and archive. Deployment is a separate task; no private deployment files or live transactions are touched. Rollback requires restoring the prior constructor and Hub call together, after reconciling uncertain transactions.
@@ -0,0 +1,27 @@
# Proposal
## Why
Small Evelyn buybacks currently create tiny burns and excessive proof notifications. Accumulating EVE for larger burns while retaining a wallet reserve supports predictable Evelyn operation without changing its small buybacks.
## What Changes
- Add independently configured EVE `MoneyAmount` values for a strictly positive burn minimum and non-negative retained reserve.
- Burn the entire balance above reserve, rounded down to mint precision, only when that actual amount reaches the minimum.
- Keep buybacks independent and preserve pending transaction reconciliation and successful FINALIZED proof delivery using the actual burned amount.
- Configure production with the owner's chosen minimum 0.1 EVE and reserve 0.01 EVE; retain issue #93's 1/0.1 examples as acceptance cases.
- **BREAKING**: Reference implementation constructor requires the two burn policy amounts.
## Capabilities
### New Capabilities
None.
### Modified Capabilities
- `evelyn-iou-burner-service`: EVE burn policy configuration/validation, precision-aware eligibility above reserve, submitted/notification amounts and production settings.
## Impact
Reference burner implementation and its domain JavaDoc, production Hub constructor, Evelyn configuration documentation and directly affected OpenSpec requirements. No new ValueTags, administration API, buyback policy, scheduling, deployment changes or permanent tests. Verification uses controlled offline dependencies without live transactions.
@@ -0,0 +1,125 @@
# Spec Delta
## ADDED Requirements
### Requirement: Independent EVE burn policy configuration
The constructor SHALL accept minimumBurnAmount and eveReserve as EVE-denominated MoneyAmount values identified by canonical EVE UUID regardless of supplied display metadata. Both values, their amounts, currencies and UUIDs SHALL be non-null; minimum SHALL be strictly positive and reserve non-negative. Zero reserve SHALL be valid. Invalid burn configuration SHALL fail before worker startup without changing existing input validation.
#### Scenario: Invalid burn configuration
- **WHEN** either value, amount, currency or UUID is null, a UUID is not EVE, the minimum is zero/negative, or the reserve is negative
- **THEN** construction fails before creating a worker or transaction
#### Scenario: Stale EVE metadata and zero reserve
- **WHEN** both values carry the EVE UUID with stale names/symbols and the reserve is zero
- **THEN** construction succeeds and uses canonical EVE identity with the configured exact numeric amounts
## MODIFIED Requirements
### 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. The service SHALL subtract its configured EVE reserve, clamp negative available amounts to zero and round down to validated EVE mint precision using the detected token program. It SHALL submit a new burn only when the actual rounded amount is positive and at least the configured minimum burn amount; equality qualifies. It SHALL burn the entire eligible amount above reserve, without a maximum or fixed batch size, including EVE deposited before the iteration or visible after a failed or absent swap. Below threshold or with unusable mint precision it SHALL retain the EVE and send no burn notification. This threshold SHALL NOT suppress independent buybacks.
#### 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 under the EVE reserve, precision and burn-minimum rules
#### 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
#### Scenario: Issue example threshold and reserve
- **WHEN** minimum is 1 EVE and reserve is 0.1 EVE
- **THEN** balances 0, 0.05, 0.1, 1.05 and 1.099999 produce no burn; balance 1.1 burns 1 and balance 1.35 burns 1.25, retaining 0.1 after success
#### Scenario: Accumulation across small buybacks
- **WHEN** individually eligible small buybacks accumulate EVE over multiple iterations
- **THEN** each below-threshold iteration still permits its buyback and sends no burn message; the first qualifying iteration burns all eligible EVE and subsequent accumulation starts with the retained reserve
#### Scenario: Exact configuration exceeds mint precision
- **WHEN** six-decimal mint precision rounds available 1.0000009 to 1.000000 while minimum is 1.0000001
- **THEN** no burn occurs; no amount is rounded upward and the reserve is never consumed to reach the minimum
### 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 actual rounded eligible EVE amount above reserve 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 positive rounded EVE amount at or above the burn minimum and sufficiently fresh positive EVE/USDT observation are available
- **THEN** the service calculates the notification's USD value from that actual eligible burn amount, excluding reserve 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 rounded EVE amount at or above the minimum with a usable price, the service SHALL call the wallet burn operation exactly once with the fixed EVE mint and entire eligible rounded amount above reserve. 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, actual submitted EVE amount excluding reserve, 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 process. It MAY continue ordinary iteration work that does not submit another burn. Recovery SHALL require external reconciliation followed by a complete process restart, not a Burner service stop/start operation.
#### Scenario: Burn returns a signature
- **WHEN** the wallet submits the entire eligible rounded amount above reserve 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 external reconciliation and a complete process restart
#### Scenario: Pending burn bypasses new threshold evaluation
- **WHEN** a retained burn signature remains pending while balance is now below the minimum or at the reserve
- **THEN** only the original signature is awaited, its original submitted amount/value are preserved and it is never re-evaluated, rebuilt or resubmitted
### Requirement: Exact token-capped amount and per-currency reserve
The service SHALL select the lesser of positive `balance - minimumReserve` and the input UUID's maximum token amount, then round downward to mint precision. It SHALL skip non-positive results and actual rounded amounts below that UUID's minimum, continuing to later inputs in the same iteration without counting a swap attempt. Equality SHALL qualify. It SHALL never round upward, top up from reserves or exceed either existing bound. Input selection SHALL require no input/USDC Ticker observation. Minima and maxima SHALL constrain buyback inputs only; EVE burns SHALL remain uncapped and governed independently by their own EVE minimum and reserve. Native SOL reserves SHALL use observed balance without estimating future fees.
#### Scenario: SyrupUSDC maximum is lower than spendable balance
- **WHEN** SyrupUSDC balance is 0.5, its reserve is 0.095, its minimum is 0.045 and its maximum is 0.2
- **THEN** 0.2 SyrupUSDC is selected without requesting a SyrupUSDC/USDC observation
#### Scenario: SyrupUSDC spendable balance is lower than the maximum
- **WHEN** SyrupUSDC balance is 0.245, its reserve is 0.095, its minimum is 0.045 and its maximum is 0.2
- **THEN** 0.150 SyrupUSDC is selected before downward mint-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: USDT maximum preserves the production bound
- **WHEN** USDT balance is 0.35, its reserve is 0.1, its minimum is 0.05 and its maximum is 0.25
- **THEN** 0.25 USDT is selected before downward mint-precision rounding
#### Scenario: Precision rounding never increases the spend
- **WHEN** the lesser of spendable balance and maximum is 0.1509999, the mint supports six decimals and its minimum is no greater than 0.150999
- **THEN** the service selects 0.150999, and if downward rounding instead produces zero the input is skipped
#### 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
#### Scenario: USDT below and at its minimum
- **WHEN** USDT reserve is 0.1 and minimum is 0.05
- **THEN** balances 0.14 and 0.149999 are skipped, while balance 0.15 selects exactly 0.05
#### Scenario: Tiny first input permits useful later input
- **WHEN** USDT balance is 0.100001 with reserve 0.1/minimum 0.05, and SyrupUSDC balance is 0.502048 with reserve 0.095/minimum 0.045/maximum 0.2
- **THEN** the USDT remainder is skipped and 0.2 SyrupUSDC is the only attempted swap
#### Scenario: SyrupUSDC minimum boundary
- **WHEN** SyrupUSDC reserve is 0.095 and minimum is 0.045
- **THEN** balance 0.14 selects exactly 0.045 while 0.139999 is skipped
#### Scenario: Actual rounded candidate falls below a fractional minimum
- **WHEN** six-decimal input precision rounds candidate 0.0500009 down to 0.050000 and its minimum is 0.0500001
- **THEN** the candidate is skipped without rounding up or using reserves
#### Scenario: Input minima do not suppress direct EVE burn
- **WHEN** every configured input is below its minimum but the wallet contains EVE with a usable EVE/USDT price
- **THEN** the complete EVE balance is still evaluated and its rounded eligible amount above the EVE reserve is burned if it meets the EVE burn minimum
### Requirement: Production burner accepts USDT and SyrupUSDC
The production Hub SHALL configure USDT first with reserve 0.1, minimum input 0.05 and maximum input 0.25 USDT, and SyrupUSDC second with reserve 0.095, minimum input 0.045 and maximum input 0.2 SyrupUSDC. These SHALL be token-denominated limits without a USD-value guarantee. The production EVE burn minimum SHALL be 0.1 EVE and its retained reserve SHALL be 0.01 EVE, as selected by the owner on 2026-10-06, configured as MoneyAmount values using the canonical EVE UUID. The existing burner wallet, Jupiter route selection, registration, hourly interval, slippage, EVE/USDT freshness, confirmation settings and burn recovery behavior SHALL be preserved without new Ticker sources or history storage.
#### Scenario: Production inputs and settings
- **WHEN** the Hub constructs the production burner
- **THEN** it supplies the exact reserves, minima and maxima as MoneyAmount values in USDT-then-SyrupUSDC priority, supplies minimum burn 0.1 EVE/reserve 0.01 EVE and retains the other existing production arguments
#### Scenario: Production burn threshold
- **WHEN** production EVE balance is below 0.11, exactly 0.11, or 0.135 with usable mint precision and fresh price
- **THEN** below 0.11 produces no burn, 0.11 burns 0.1 and 0.135 burns 0.125; successful burns retain 0.01 EVE
@@ -0,0 +1,15 @@
# Tasks
## 1. EVE burn policy
- [x] 1.1 Add canonical EVE MoneyAmount minimum/reserve constructor validation and constructor JavaDoc; verify invalid/null/wrong UUID values fail before worker/transaction activity, stale metadata works and zero reserve is valid in controlled offline checks.
- [x] 1.2 Replace full-balance submission with reserve-aware downward precision rounding and actual-minimum qualification; update domain JavaDoc and verify issue boundaries, rounding, direct deposits, accumulation, independent buybacks and actual pending/notification amounts with controlled dependencies.
## 2. Production configuration
- [x] 2.1 Supply owner's selected minimum 0.1 EVE and reserve 0.01 EVE in the production Hub, update Evelyn docs and verify source arguments plus production 0.11 threshold/0.135 balance examples offline.
## 3. Integration and completion
- [x] 3.1 Compile relevant sources, verify pending burn/swap reconciliation and suspension regressions without live transactions, and record results in verification.md; run strict OpenSpec validation and git diff --check.
- [x] 3.2 Synchronize directly affected main specification, verify all delta blocks match while unrelated content survives, run strict validation and archive completed artifacts/tasks.
@@ -0,0 +1,30 @@
# Verification
Verified on 2026-10-06 with `/usr/local/software/java/jfx-25`.
## Compilation and controlled acceptance checks
- `JAVA_HOME=/usr/local/software/java/jfx-25 ./gradlew compileJava compileTestJava --offline --no-daemon` → **BUILD SUCCESSFUL in 6s**; normal Detag and both source compilations executed. The initial sandbox attempt could not create Gradle's existing cache lock outside the workspace; the same compilation passed with approved cache access.
- Temporary harness sources in `/tmp/evelyn-issue93` compiled with `javac --enable-preview --release 25 -cp 'build/classes/java/main:build/libs/*' -d /tmp/evelyn-issue93`.
- `java --enable-preview -cp '/tmp/evelyn-issue93:build/classes/java/main:build/libs/*' Issue93Verification` → **PASS: 235 focused burn-policy assertions**.
- Same invocation for the copied `Issue92Verification` → **PASS: 89 buyback-minimum regression assertions**.
- Same invocation for the copied `Issue91Verification`, restricted to its burner checks → **PASS: 63 pending-swap/recovery regression assertions**. Unchanged RPC/Jupiter library checks were not rerun.
- Production source check confirms canonical EVE minimum **0.1** and reserve **0.01** in the correct constructor order and preserved input bounds, hourly interval, freshness and finalization arguments.
All wallet, transaction, chain, pricing and notification dependencies are controlled. No real RPC, signing or live financial operation occurs. No permanent automated tests were added. The older temporary harness copies add only a positive EVE burn minimum of 0.000001 and zero reserve to their constructor calls, retaining their original buyback/recovery expectations; original temporary files are preserved.
The #93 checks cover every issue #93 1/0.1 example and production 0.1/0.01 boundaries, numerical equality across decimal scales, zero reserve, fractional minimum above the rounded candidate, fractional reserve preservation, zero after rounding, both SPL Token and Token-2022 precision, missing/unsupported mint data, supply/mint/program mismatch, invalid decimals, I/O and interruption, missing/negative balance, stale price, repeated small buyback accumulation and subsequent reserve-based accumulation, direct deposits with absent/below-minimum inputs, no burn maximum, failed/unknown buybacks, original pending signature and amount/value across timeout/I/O/interruption, failed finalization without notification, once-only notification after delayed finalization, delivery failure and signature-less burn suspension with continued buybacks. Null/invalid numeric configuration, wrong/unknown UUIDs and stale EVE metadata are checked before worker/transaction activity; CurrencyType itself rejects null UUIDs before burner configuration.
An initial temporary harness compile failed because its notification-error fixture used Throwable rather than the transport's IOException contract; the fixture was corrected. Its default successful swap output was also corrected from zero to a positive amount, preventing fixture-only result validation failures. The final checks above pass against the unchanged production implementation.
## Review and workflow
Parent independent source/design/documentation and harness review found no material findings. Two delta scenario wording fixes were applied to state the actual eligible amount and grammatically separate the new production burn settings. Scope is limited to constructor burn policy, EVE eligibility/actual amount use, JavaDoc, production arguments, Evelyn docs and their specification. Buyback limits, priority, pending recovery, lifecycle, Jupiter, Ticker, ValueTags and private deployment files are preserved.
Strict active-change validation and `git diff --check` passed before synchronization. Final strict validation of the active change and synchronized main specification passed. Six delta requirement blocks match the main specification exactly; 12 untouched requirements remain byte-for-byte intact. The purpose now describes accumulated, threshold-qualified burns above reserve. Existing long-requirement informational hints remain because modified requirement blocks stay whole.
An initial temporary synchronization matcher allowed a requirement title to span lines and copied an invalid delta header into the main specification. Strict validation caught this before archive. The matcher was corrected and the main-spec edit rebuilt from the clean starting version plus the six intended blocks, then compared for exact delta matching and untouched-content preservation. Final strict validation passes.
All four planning artifacts and all five tasks are complete. The completed change is archived at `openspec/changes/archive/2026-10-06-93-add-minimum-eve-burn-and-reserve`. After archival, `openspec list --json` reports no active changes, and `openspec validate --archived --json --report findings` passed for all **31** archived changes with **0 failures**. Final whitespace validation also passes.
No deployment, push, Gitea write or live integration was performed.
@@ -2,7 +2,7 @@
## Purpose ## 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. Defines the Evelyn-specific service that safely converts prioritized wallet assets into Evelyn IOU, accumulates EVE for burns above a configured minimum while retaining an EVE reserve, and announces only finalized burns without automatically repeating transactions whose outcomes may be unknown.
## Requirements ## Requirements
### Requirement: Evelyn-specific public service boundary ### Requirement: Evelyn-specific public service boundary
The system SHALL expose an empty lifecycle-free `EvelynIOUBurnerService` under `com.fanitas.evelyn.service.burner` as the typed domain service and Registry view until external domain operations exist, and SHALL keep its reference implementation under `com.fanitas.evelyn.service.burner.impl.ref`. The same reference implementation object SHALL implement both `EvelynIOUBurnerService` and `NenjimProcess`; the Hub SHALL start it only through its `NenjimProcess` view. Neither the service interface nor the implementation SHALL expose a general stop, restart, worker-control, or other lifecycle operation. 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. The system SHALL expose an empty lifecycle-free `EvelynIOUBurnerService` under `com.fanitas.evelyn.service.burner` as the typed domain service and Registry view until external domain operations exist, and SHALL keep its reference implementation under `com.fanitas.evelyn.service.burner.impl.ref`. The same reference implementation object SHALL implement both `EvelynIOUBurnerService` and `NenjimProcess`; the Hub SHALL start it only through its `NenjimProcess` view. Neither the service interface nor the implementation SHALL expose a general stop, restart, worker-control, or other lifecycle operation. 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.
@@ -161,40 +161,56 @@ A structured definitive on-chain swap failure SHALL be logged without suspending
- **THEN** the burner performs no additional confirmation wait and reloads the wallet instead of deriving its burn amount only from the returned output amount - **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 ### 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. 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. The service SHALL subtract its configured EVE reserve, clamp negative available amounts to zero and round down to validated EVE mint precision using the detected token program. It SHALL submit a new burn only when the actual rounded amount is positive and at least the configured minimum burn amount; equality qualifies. It SHALL burn the entire eligible amount above reserve, without a maximum or fixed batch size, including EVE deposited before the iteration or visible after a failed or absent swap. Below threshold or with unusable mint precision it SHALL retain the EVE and send no burn notification. This threshold SHALL NOT suppress independent buybacks.
#### Scenario: Existing EVE without buyback #### Scenario: Existing EVE without buyback
- **WHEN** no swap is performed but the wallet contains a positive EVE balance - **WHEN** no swap is performed but the wallet contains a positive EVE balance
- **THEN** the service evaluates that complete balance for burning - **THEN** the service evaluates that complete balance under the EVE reserve, precision and burn-minimum rules
#### Scenario: EVE account is absent or empty #### Scenario: EVE account is absent or empty
- **WHEN** the wallet has no EVE token account or its complete balance is zero - **WHEN** the wallet has no EVE token account or its complete balance is zero
- **THEN** the iteration submits no burn and sends no notification - **THEN** the iteration submits no burn and sends no notification
#### Scenario: Issue example threshold and reserve
- **WHEN** minimum is 1 EVE and reserve is 0.1 EVE
- **THEN** balances 0, 0.05, 0.1, 1.05 and 1.099999 produce no burn; balance 1.1 burns 1 and balance 1.35 burns 1.25, retaining 0.1 after success
#### Scenario: Accumulation across small buybacks
- **WHEN** individually eligible small buybacks accumulate EVE over multiple iterations
- **THEN** each below-threshold iteration still permits its buyback and sends no burn message; the first qualifying iteration burns all eligible EVE and subsequent accumulation starts with the retained reserve
#### Scenario: Exact configuration exceeds mint precision
- **WHEN** six-decimal mint precision rounds available 1.0000009 to 1.000000 while minimum is 1.0000001
- **THEN** no burn occurs; no amount is rounded upward and the reserve is never consumed to reach the minimum
### Requirement: Fresh EVE value is required before submission ### 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. 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 actual rounded eligible EVE amount above reserve 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 #### Scenario: Fresh EVE price exists
- **WHEN** a complete positive EVE balance and sufficiently fresh positive EVE/USDT observation are available - **WHEN** a positive rounded EVE amount at or above the burn minimum 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 - **THEN** the service calculates the notification's USD value from that actual eligible burn amount, excluding reserve before submitting the burn
#### Scenario: EVE price is stale #### Scenario: EVE price is stale
- **WHEN** EVE is present but its observation is older than the configured age limit - **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 - **THEN** the service leaves EVE unburned and sends no notification in that iteration
### Requirement: Single complete-balance burn submission and pending state ### 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. For a positive rounded EVE amount at or above the minimum with a usable price, the service SHALL call the wallet burn operation exactly once with the fixed EVE mint and entire eligible rounded amount above reserve. 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, actual submitted EVE amount excluding reserve, 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 process. It MAY continue ordinary iteration work that does not submit another burn. Recovery SHALL require external reconciliation followed by a complete process restart, not a Burner service stop/start operation. 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 process. It MAY continue ordinary iteration work that does not submit another burn. Recovery SHALL require external reconciliation followed by a complete process restart, not a Burner service stop/start operation.
#### Scenario: Burn returns a signature #### Scenario: Burn returns a signature
- **WHEN** the wallet submits the complete observed EVE balance and returns a non-blank signature - **WHEN** the wallet submits the entire eligible rounded amount above reserve and returns a non-blank signature
- **THEN** pending state is retained before the service begins finalization waiting - **THEN** pending state is retained before the service begins finalization waiting
#### Scenario: Submission outcome may be unknown without a signature #### 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 - **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 external reconciliation and a complete process restart - **THEN** the service performs no further burn submission until external reconciliation and a complete process restart
#### Scenario: Pending burn bypasses new threshold evaluation
- **WHEN** a retained burn signature remains pending while balance is now below the minimum or at the reserve
- **THEN** only the original signature is awaited, its original submitted amount/value are preserved and it is never re-evaluated, rebuilt or resubmitted
### Requirement: Finalized burn outcome governs completion ### 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. 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.
@@ -231,7 +247,7 @@ The EVE amount SHALL use plain decimal notation without unnecessary trailing zer
- **THEN** the service logs a non-secret failure, clears completed state, and never retries either notification or burn - **THEN** the service logs a non-secret failure, clears completed state, and never retries either notification or burn
### Requirement: Exact token-capped amount and per-currency reserve ### Requirement: Exact token-capped amount and per-currency reserve
The service SHALL select the lesser of positive `balance - minimumReserve` and the input UUID's maximum token amount, then round downward to mint precision. It SHALL skip non-positive results and actual rounded amounts below that UUID's minimum, continuing to later inputs in the same iteration without counting a swap attempt. Equality SHALL qualify. It SHALL never round upward, top up from reserves or exceed either existing bound. Input selection SHALL require no input/USDC Ticker observation. Minima and maxima SHALL constrain buyback inputs only; EVE burns SHALL remain uncapped and unaffected. Native SOL reserves SHALL use observed balance without estimating future fees. The service SHALL select the lesser of positive `balance - minimumReserve` and the input UUID's maximum token amount, then round downward to mint precision. It SHALL skip non-positive results and actual rounded amounts below that UUID's minimum, continuing to later inputs in the same iteration without counting a swap attempt. Equality SHALL qualify. It SHALL never round upward, top up from reserves or exceed either existing bound. Input selection SHALL require no input/USDC Ticker observation. Minima and maxima SHALL constrain buyback inputs only; EVE burns SHALL remain uncapped and governed independently by their own EVE minimum and reserve. Native SOL reserves SHALL use observed balance without estimating future fees.
#### Scenario: SyrupUSDC maximum is lower than spendable balance #### Scenario: SyrupUSDC maximum is lower than spendable balance
- **WHEN** SyrupUSDC balance is 0.5, its reserve is 0.095, its minimum is 0.045 and its maximum is 0.2 - **WHEN** SyrupUSDC balance is 0.5, its reserve is 0.095, its minimum is 0.045 and its maximum is 0.2
@@ -275,14 +291,18 @@ The service SHALL select the lesser of positive `balance - minimumReserve` and t
#### Scenario: Input minima do not suppress direct EVE burn #### Scenario: Input minima do not suppress direct EVE burn
- **WHEN** every configured input is below its minimum but the wallet contains EVE with a usable EVE/USDT price - **WHEN** every configured input is below its minimum but the wallet contains EVE with a usable EVE/USDT price
- **THEN** the complete EVE balance is still evaluated and burned by the existing rules - **THEN** the complete EVE balance is still evaluated and its rounded eligible amount above the EVE reserve is burned if it meets the EVE burn minimum
### Requirement: Production burner accepts USDT and SyrupUSDC ### Requirement: Production burner accepts USDT and SyrupUSDC
The production Hub SHALL configure USDT first with reserve 0.1, minimum input 0.05 and maximum input 0.25 USDT, and SyrupUSDC second with reserve 0.095, minimum input 0.045 and maximum input 0.2 SyrupUSDC. These SHALL be token-denominated limits without a USD-value guarantee. The existing burner wallet, Jupiter route selection, registration, hourly interval, slippage, EVE/USDT freshness, confirmation settings and burn recovery behavior SHALL be preserved without new Ticker sources or history storage. The production Hub SHALL configure USDT first with reserve 0.1, minimum input 0.05 and maximum input 0.25 USDT, and SyrupUSDC second with reserve 0.095, minimum input 0.045 and maximum input 0.2 SyrupUSDC. These SHALL be token-denominated limits without a USD-value guarantee. The production EVE burn minimum SHALL be 0.1 EVE and its retained reserve SHALL be 0.01 EVE, as selected by the owner on 2026-10-06, configured as MoneyAmount values using the canonical EVE UUID. The existing burner wallet, Jupiter route selection, registration, hourly interval, slippage, EVE/USDT freshness, confirmation settings and burn recovery behavior SHALL be preserved without new Ticker sources or history storage.
#### Scenario: Production inputs and settings #### Scenario: Production inputs and settings
- **WHEN** the Hub constructs the production burner - **WHEN** the Hub constructs the production burner
- **THEN** it supplies the exact reserves, minima and maxima as MoneyAmount values in USDT-then-SyrupUSDC priority and retains the other existing production arguments - **THEN** it supplies the exact reserves, minima and maxima as MoneyAmount values in USDT-then-SyrupUSDC priority, supplies minimum burn 0.1 EVE/reserve 0.01 EVE and retains the other existing production arguments
#### Scenario: Production burn threshold
- **WHEN** production EVE balance is below 0.11, exactly 0.11, or 0.135 with usable mint precision and fresh price
- **THEN** below 0.11 produces no burn, 0.11 burns 0.1 and 0.135 burns 0.125; successful burns retain 0.01 EVE
### Requirement: Pending swaps reconcile before new input selection ### Requirement: Pending swaps reconcile before new input selection
After pending-burn priority, a retained swap SHALL be checked read-only by its original signature at CONFIRMED or stronger in a bounded operation. Success, on-chain failure or demonstrated non-executed expiry SHALL clear it and permit newly read balances to drive at most one new swap. Unknown, absent without expiry proof or I/O outcomes SHALL remain pending without a new swap while allowing existing EVE burns. Interruption SHALL propagate with pending state intact. After pending-burn priority, a retained swap SHALL be checked read-only by its original signature at CONFIRMED or stronger in a bounded operation. Success, on-chain failure or demonstrated non-executed expiry SHALL clear it and permit newly read balances to drive at most one new swap. Unknown, absent without expiry proof or I/O outcomes SHALL remain pending without a new swap while allowing existing EVE burns. Interruption SHALL propagate with pending state intact.
@@ -305,3 +325,14 @@ Swap diagnostics SHALL identify canonical input symbol/UUID, attempted amount, k
#### Scenario: Unknown outcome is diagnosed #### Scenario: Unknown outcome is diagnosed
- **WHEN** a swap outcome remains unknown - **WHEN** a swap outcome remains unknown
- **THEN** diagnostics identify whether its original signature will be checked next iteration or manual investigation is required, without exposing secret or payload data - **THEN** diagnostics identify whether its original signature will be checked next iteration or manual investigation is required, without exposing secret or payload data
### Requirement: Independent EVE burn policy configuration
The constructor SHALL accept minimumBurnAmount and eveReserve as EVE-denominated MoneyAmount values identified by canonical EVE UUID regardless of supplied display metadata. Both values, their amounts, currencies and UUIDs SHALL be non-null; minimum SHALL be strictly positive and reserve non-negative. Zero reserve SHALL be valid. Invalid burn configuration SHALL fail before worker startup without changing existing input validation.
#### Scenario: Invalid burn configuration
- **WHEN** either value, amount, currency or UUID is null, a UUID is not EVE, the minimum is zero/negative, or the reserve is negative
- **THEN** construction fails before creating a worker or transaction
#### Scenario: Stale EVE metadata and zero reserve
- **WHEN** both values carry the EVE UUID with stale names/symbols and the reserve is zero
- **THEN** construction succeeds and uses canonical EVE identity with the configured exact numeric amounts
@@ -23,6 +23,11 @@ import java.util.List;
* internally through the Currency Identity Service and Solana and are not * internally through the Currency Identity Service and Solana and are not
* part of Evelyn's input configuration.</p> * part of Evelyn's input configuration.</p>
* *
* <p>Two independent EVE-denominated {@link MoneyAmount} values configure the
* minimum per burn and retained EVE reserve. Their canonical EVE UUID is
* authoritative; the minimum must be strictly positive and the reserve
* non-negative. A burn threshold does not suppress independent buybacks.</p>
*
* <p>Every non-overlapping iteration has this order:</p> * <p>Every non-overlapping iteration has this order:</p>
* <ol> * <ol>
* <li>If a prior burn has a pending signature, await only that exact * <li>If a prior burn has a pending signature, await only that exact
@@ -56,11 +61,16 @@ import java.util.List;
* <li>Reload the complete visible Evelyn IOU balance, including tokens * <li>Reload the complete visible Evelyn IOU balance, including tokens
* deposited or acquired before this iteration. Absence or failure of a * deposited or acquired before this iteration. Absence or failure of a
* swap does not suppress this step, and the input buyback limit never caps * swap does not suppress this step, and the input buyback limit never caps
* the amount eligible for burning.</li> * the amount eligible for burning. Subtract the retained EVE reserve and
* round the positive remainder down to EVE mint precision. If the actual
* rounded amount is zero or below the burn minimum, keep the EVE for a
* later iteration and send no burn notification. Equality qualifies;</n+ * never round upward or consume reserve to reach the minimum.</li>
* <li>Require a positive, sufficiently fresh canonical EVE/USDT price and * <li>Require a positive, sufficiently fresh canonical EVE/USDT price and
* calculate the complete balance's dollar value before submitting a burn. * calculate the actual eligible burn amount's dollar value, excluding
* the retained reserve, before submitting a burn.
* Missing, invalid, or stale price data postpones the burn.</li> * Missing, invalid, or stale price data postpones the burn.</li>
* <li>Submit the complete EVE balance once and retain its signature, * <li>Submit all eligible rounded EVE above reserve once, without a burn
* maximum or fixed batch size, and retain its signature,
* amount, and notification value as pending before waiting. The service * amount, and notification value as pending before waiting. The service
* never automatically retries, rebuilds, re-signs, or resubmits a swap or * never automatically retries, rebuilds, re-signs, or resubmits a swap or
* burn because timeout, interruption, or communication failure can leave * burn because timeout, interruption, or communication failure can leave
@@ -85,8 +85,16 @@ public final class EvelynIOUBurnerServiceImpl
* equality qualifies without rounding up or spending the reserve. Three * equality qualifies without rounding up or spending the reserve. Three
* empty input lists permit direct-EVE-only burning.</p> * empty input lists permit direct-EVE-only burning.</p>
* *
* <p>{@code minimumBurnAmount} and {@code eveReserve} must identify EVE by
* its canonical UUID, regardless of supplied name/symbol. The minimum must
* be strictly positive and the reserve non-negative. A new burn uses the
* entire balance above reserve, rounded down to EVE mint precision, only
* when that actual amount is positive and at least the minimum. Equality
* qualifies; fractional remainders stay in the wallet. This policy does
* not suppress independent small buybacks or re-evaluate pending burns.</p>
*
* @param burnerWallet dedicated wallet whose balances are inspected and * @param burnerWallet dedicated wallet whose balances are inspected and
* whose complete EVE balance is burned * whose eligible EVE above reserve is burned
* @param swapService exact-input Jupiter service; normal return already * @param swapService exact-input Jupiter service; normal return already
* means the swap reached {@code CONFIRMED} * means the swap reached {@code CONFIRMED}
* @param solanaBlockChain Solana access used for mint ownership, token * @param solanaBlockChain Solana access used for mint ownership, token
@@ -108,6 +116,10 @@ public final class EvelynIOUBurnerServiceImpl
* @param inputCurrencyMaximumSwapAmounts exactly one strictly positive * @param inputCurrencyMaximumSwapAmounts exactly one strictly positive
* token-denominated buyback maximum per accepted input UUID; * token-denominated buyback maximum per accepted input UUID;
* it does not limit burns * it does not limit burns
* @param minimumBurnAmount strictly positive EVE-denominated minimum for
* the actual rounded amount of each new burn
* @param eveReserve non-negative EVE-denominated amount retained after a
* successful burn; zero is valid
* @param maximumSlippageBps Jupiter slippage limit in basis points from 0 * @param maximumSlippageBps Jupiter slippage limit in basis points from 0
* through 10000 inclusive * through 10000 inclusive
* @param maximumPriceAge strictly positive maximum observation age in * @param maximumPriceAge strictly positive maximum observation age in
@@ -126,7 +138,9 @@ public final class EvelynIOUBurnerServiceImpl
* lacks exactly one usable Solana mint, * lacks exactly one usable Solana mint,
* or minimum/maximum UUIDs do not match inputs, * or minimum/maximum UUIDs do not match inputs,
* a swap bound is non-positive, or a * a swap bound is non-positive, or a
* minimum exceeds its maximum * minimum exceeds its maximum, or the
* burn minimum/reserve does not identify
* EVE or has an invalid numeric value
*/ */
public EvelynIOUBurnerServiceImpl( public EvelynIOUBurnerServiceImpl(
@NotNull SolanaWallet burnerWallet, @NotNull SolanaWallet burnerWallet,
@@ -138,6 +152,8 @@ public final class EvelynIOUBurnerServiceImpl
@NotNull List<MoneyAmount> inputCurrencyMinimumReserves, @NotNull List<MoneyAmount> inputCurrencyMinimumReserves,
@NotNull List<MoneyAmount> inputCurrencyMinimumSwapAmounts, @NotNull List<MoneyAmount> inputCurrencyMinimumSwapAmounts,
@NotNull List<MoneyAmount> inputCurrencyMaximumSwapAmounts, @NotNull List<MoneyAmount> inputCurrencyMaximumSwapAmounts,
@NotNull MoneyAmount minimumBurnAmount,
@NotNull MoneyAmount eveReserve,
int maximumSlippageBps, int maximumSlippageBps,
ΩmilliSecondsΩ maximumPriceAge, ΩmilliSecondsΩ maximumPriceAge,
@NotNull Duration burnFinalizationTimeout, @NotNull Duration burnFinalizationTimeout,
@@ -211,6 +227,12 @@ public final class EvelynIOUBurnerServiceImpl
USDT_ID, USDT_ID,
"USDT" "USDT"
); );
this.minimumBurnAmount = validateEveAmount(
minimumBurnAmount, "minimumBurnAmount", canonicalEve, true
);
this.eveReserve = validateEveAmount(
eveReserve, "eveReserve", canonicalEve, false
);
this.eveUsdtTradingPair = new TradingPair( this.eveUsdtTradingPair = new TradingPair(
canonicalEve, canonicalEve,
canonicalUsdt canonicalUsdt
@@ -519,9 +541,9 @@ public final class EvelynIOUBurnerServiceImpl
private void processCompleteEveBalance() throws InterruptedException { private void processCompleteEveBalance() throws InterruptedException {
ΩAmountΩ eveBalance; ΩAmountΩ eveBalance;
SolanaSPLTokenProgram eveTokenProgram;
try { try {
SolanaSPLTokenProgram eveTokenProgram = eveTokenProgram = resolveSupportedTokenProgram(EVELYN_IOU_MINT);
resolveSupportedTokenProgram(EVELYN_IOU_MINT);
eveBalance = burnerWallet.getSPLTokenBalance( eveBalance = burnerWallet.getSPLTokenBalance(
EVELYN_IOU_MINT, EVELYN_IOU_MINT,
eveTokenProgram eveTokenProgram
@@ -554,17 +576,40 @@ public final class EvelynIOUBurnerServiceImpl
return; return;
} }
ΩAmountΩ availableForBurn = eveBalance.subtract(eveReserve.amount());
if (availableForBurn.signum() <= 0) {
return;
}
ΩAmountΩ amountToBurn;
try {
ΩamountDecimalsΩ decimals = resolveMintDecimals(
EVELYN_IOU_MINT, eveTokenProgram
);
amountToBurn = availableForBurn.setScale(decimals, RoundingMode.DOWN);
} catch (IOException | RuntimeException exception) {
log.warn(
"Could not resolve Evelyn IOU mint precision; burn is "
+ "postponed: failureType={}",
exception.getClass().getName()
);
return;
}
if (amountToBurn.signum() <= 0
|| amountToBurn.compareTo(minimumBurnAmount.amount()) < 0) {
return;
}
ΩPriceΩ evePriceInUsdt = findFreshPrice(eveUsdtTradingPair); ΩPriceΩ evePriceInUsdt = findFreshPrice(eveUsdtTradingPair);
if (evePriceInUsdt == null) { if (evePriceInUsdt == null) {
return; return;
} }
ΩAmountΩ dollarValue = eveBalance.multiply(evePriceInUsdt); ΩAmountΩ dollarValue = amountToBurn.multiply(evePriceInUsdt);
ΩSolanaTransactionSignatureΩ burnSignature; ΩSolanaTransactionSignatureΩ burnSignature;
try { try {
burnSignature = burnerWallet.burnSPLToken( burnSignature = burnerWallet.burnSPLToken(
EVELYN_IOU_MINT, EVELYN_IOU_MINT,
eveBalance amountToBurn
); );
} catch (IOException exception) { } catch (IOException exception) {
burnsSuspended = true; burnsSuspended = true;
@@ -607,7 +652,7 @@ public final class EvelynIOUBurnerServiceImpl
pendingBurn = new PendingBurn( pendingBurn = new PendingBurn(
burnSignature, burnSignature,
eveBalance, amountToBurn,
dollarValue dollarValue
); );
awaitPendingBurn(); awaitPendingBurn();
@@ -826,6 +871,31 @@ public final class EvelynIOUBurnerServiceImpl
return supply.decimals(); return supply.decimals();
} }
private static MoneyAmount validateEveAmount(
MoneyAmount configuredAmount,
String configurationName,
CurrencyType canonicalEve,
boolean strictlyPositive
) {
Objects.requireNonNull(configuredAmount, configurationName);
Objects.requireNonNull(configuredAmount.amount(), configurationName + ".amount");
CurrencyType currency = Objects.requireNonNull(
configuredAmount.currencyType(), configurationName + ".currencyType"
);
UUID currencyId = Objects.requireNonNull(
currency.id(), configurationName + ".currencyType.id"
);
if (!EVE_ID.equals(currencyId)) {
throw new IllegalArgumentException(configurationName + " must identify EVE by UUID");
}
if (configuredAmount.amount().signum() < 0
|| (strictlyPositive && configuredAmount.amount().signum() == 0)) {
throw new IllegalArgumentException(configurationName
+ (strictlyPositive ? " must be greater than zero" : " must be zero or greater"));
}
return new MoneyAmount(configuredAmount.amount(), canonicalEve);
}
private List<MoneyAmount> validateAndCopyInputs( private List<MoneyAmount> validateAndCopyInputs(
List<MoneyAmount> configuredInputs List<MoneyAmount> configuredInputs
) { ) {
@@ -1059,6 +1129,8 @@ public final class EvelynIOUBurnerServiceImpl
private final List<MoneyAmount> inputCurrencyMinimumReserves; private final List<MoneyAmount> inputCurrencyMinimumReserves;
private final Map<UUID, MoneyAmount> inputCurrencyMinimumSwapAmounts; private final Map<UUID, MoneyAmount> inputCurrencyMinimumSwapAmounts;
private final Map<UUID, MoneyAmount> inputCurrencyMaximumSwapAmounts; private final Map<UUID, MoneyAmount> inputCurrencyMaximumSwapAmounts;
private final MoneyAmount minimumBurnAmount;
private final MoneyAmount eveReserve;
private final int maximumSlippageBps; private final int maximumSlippageBps;
private final ΩmilliSecondsΩ maximumPriceAge; private final ΩmilliSecondsΩ maximumPriceAge;
private final Duration maximumPriceAgeDuration; private final Duration maximumPriceAgeDuration;
@@ -329,6 +329,8 @@ public final class NenjimHubImpl implements NenjimHub {
inputCurrencyMinimumReserves, inputCurrencyMinimumReserves,
inputCurrencyMinimumSwapAmounts, inputCurrencyMinimumSwapAmounts,
inputCurrencyMaximumSwapAmounts, inputCurrencyMaximumSwapAmounts,
new MoneyAmount(new ΩAmountΩ("0.1"), cis.resolve(CurrencyTypeIds.EVE_ID)),
new MoneyAmount(new ΩAmountΩ("0.01"), cis.resolve(CurrencyTypeIds.EVE_ID)),
200, 200,
2*1000*60L, 2*1000*60L,
Duration.ofSeconds(10), Duration.ofSeconds(10),