94: Buy back Evelyn IOU tokens only below the Evelyn reference price

This commit is contained in:
2026-10-07 08:21:52 +02:00
parent 00a7f13c6f
commit e283f0b8f2
22 changed files with 649 additions and 17 deletions
@@ -51,7 +51,15 @@ import java.util.List;
* below that input's minimum and continue to later inputs in the same
* iteration; equality qualifies. Never round upward or use the reserve
* to reach a minimum. Input minima do not suppress EVE burns.</li>
* <li>Attempt no more than one exact-input Jupiter swap. Normal return is
* <li>Immediately before a new purchase, require a fresh canonical
* EVE/USDT price strictly below the corresponding Evelyn status service's
* reference price at the current decision instant. Equality, higher price,
* unavailable reference or unusable price data skips all new purchases
* for this iteration and continues to independent EVE burn processing.
* A price skip does not suspend subsequent iterations. Pending transaction
* reconciliation is independent of this new-purchase condition.</li>
* <li>When the price condition permits, attempt no more than one exact-input
* Jupiter swap. Normal return is
* already {@code CONFIRMED}; it is not awaited a second time. A definitive
* failure or proven pre-submission error permits a later iteration to try
* again. A known-signature timeout or communication error retains pending
@@ -60,11 +68,13 @@ import java.util.List;
* unknown swap is rebuilt, re-signed or resubmitted automatically.</li>
* <li>Reload the complete visible Evelyn IOU balance, including tokens
* 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, including a price-condition skip, does not suppress this step,
* and the input buyback limit never caps
* 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>
* later iteration and send no burn notification. Equality qualifies;
* never round upward or consume reserve to reach the minimum.</li>
* <li>Require a positive, sufficiently fresh canonical EVE/USDT price and
* calculate the actual eligible burn amount's dollar value, excluding
* the retained reserve, before submitting a burn.
@@ -7,6 +7,7 @@ import com.r35157.assetaz.services.ticker.PriceObservation;
import com.r35157.assetaz.services.ticker.TickerService;
import com.r35157.assetaz.valuetypes.CurrencyType;
import com.r35157.cryptowallet.solana.SolanaWallet;
import com.r35157.evelyn.service.status.EvelynStatusService;
import com.r35157.libs.jupiter.JupiterTransactionOutcomeException;
import com.r35157.libs.jupiter.swap.JupiterSwapService;
import com.r35157.libs.jupiter.swap.JupiterSwapSubmissionException;
@@ -93,13 +94,23 @@ public final class EvelynIOUBurnerServiceImpl
* qualifies; fractional remainders stay in the wallet. This policy does
* not suppress independent small buybacks or re-evaluate pending burns.</p>
*
* <p>A fully eligible new buyback additionally requires a fresh canonical
* EVE/USDT price strictly below the corresponding status service's
* reference price at the current decision instant. Equality, unavailable
* or invalid prices skip purchases only, without suspending later
* iterations or suppressing independent EVE burn processing. Pending
* burn priority and read-only swap reconciliation remain unchanged.</p>
*
* @param burnerWallet dedicated wallet whose balances are inspected and
* whose eligible EVE above reserve is burned
* @param swapService exact-input Jupiter service; normal return already
* means the swap reached {@code CONFIRMED}
* @param solanaBlockChain Solana access used for mint ownership, token
* precision, and burn finalization
* @param tickerService latest-price source for EVE/USDT before burning
* @param tickerService latest-price source for EVE/USDT before new
* buybacks and independently before burning
* @param evelynStatusService corresponding Evelyn instance's reference
* price source for new buyback decisions
* @param currencyIdentityService authoritative resolver for AssetAZ UUIDs
* and Solana mint references
* @param notificationService fully bound synchronous destination for the
@@ -147,6 +158,7 @@ public final class EvelynIOUBurnerServiceImpl
@NotNull JupiterSwapService swapService,
@NotNull SolanaBlockChain solanaBlockChain,
@NotNull TickerService tickerService,
@NotNull EvelynStatusService evelynStatusService,
@NotNull CurrencyIdentityService currencyIdentityService,
@NotNull BoundNotificationService notificationService,
@NotNull List<MoneyAmount> inputCurrencyMinimumReserves,
@@ -175,6 +187,10 @@ public final class EvelynIOUBurnerServiceImpl
tickerService,
"tickerService"
);
this.evelynStatusService = Objects.requireNonNull(
evelynStatusService,
"evelynStatusService"
);
this.currencyIdentityService = Objects.requireNonNull(
currencyIdentityService,
"currencyIdentityService"
@@ -436,6 +452,9 @@ public final class EvelynIOUBurnerServiceImpl
continue;
}
if (!isBuybackBelowReferencePrice()) {
return;
}
attemptSwap(
canonicalCurrency,
inputMint,
@@ -445,6 +464,70 @@ public final class EvelynIOUBurnerServiceImpl
}
}
private boolean isBuybackBelowReferencePrice() {
ΩPriceΩ actualPrice = findFreshPrice(eveUsdtTradingPair);
if (actualPrice == null) {
log.warn(
"Skipping buyback as no fresh valid EVE/USDT price "
+ "is available; nextAction=burn "
+ "evaluation and price recheck next iteration"
);
return false;
}
Instant decisionInstant = Instant.now();
AssetPrice referencePrice;
try {
referencePrice = evelynStatusService.getReferencePrice(
decisionInstant
);
} catch (RuntimeException exception) {
log.warn(
"Skipping buyback as reference price is unavailable "
+ "(market={} USDT/EVE); decisionInstant={}, "
+ "failureType={}; "
+ "nextAction=burn evaluation and price recheck "
+ "next iteration",
actualPrice.stripTrailingZeros().toPlainString(),
decisionInstant,
exception.getClass().getName()
);
return false;
}
if (referencePrice == null
|| !eveUsdtTradingPair.equals(referencePrice.tradingPair())
|| referencePrice.price() == null
|| referencePrice.price().signum() <= 0) {
log.warn(
"Skipping buyback as reference price is missing or "
+ "invalid (market={} USDT/EVE); tradingPair={}, "
+ "decisionInstant={}; nextAction=burn evaluation "
+ "and price recheck next iteration",
actualPrice.stripTrailingZeros().toPlainString(),
eveUsdtTradingPair,
decisionInstant
);
return false;
}
if (actualPrice.compareTo(referencePrice.price()) >= 0) {
log.info(
"Skipping buyback as EVE price is at or above reference "
+ "(market={} >= reference={} USDT/EVE); "
+ "tradingPair={}, "
+ "decisionInstant={}; nextAction=burn evaluation "
+ "and price recheck next iteration",
actualPrice.stripTrailingZeros().toPlainString(),
referencePrice.price().stripTrailingZeros().toPlainString(),
eveUsdtTradingPair,
decisionInstant
);
return false;
}
return true;
}
private void attemptSwap(
CurrencyType canonicalCurrency,
ΩSPLMintAddressΩ inputMint,
@@ -1124,6 +1207,7 @@ public final class EvelynIOUBurnerServiceImpl
private final JupiterSwapService swapService;
private final SolanaBlockChain solanaBlockChain;
private final TickerService tickerService;
private final EvelynStatusService evelynStatusService;
private final CurrencyIdentityService currencyIdentityService;
private final BoundNotificationService notificationService;
private final List<MoneyAmount> inputCurrencyMinimumReserves;
@@ -324,6 +324,10 @@ public final class NenjimHubImpl implements NenjimHub {
tickerService,
EVELYN_IOU_BURNER_PROD_ID,
TICKER_SERVICE_ID),
requireProcessDependency(
evelynStatusServiceProd,
EVELYN_IOU_BURNER_PROD_ID,
EVELYN_STATUS_PROD_ID),
cis,
discordAssetAZTokenNotificationService,
inputCurrencyMinimumReserves,