90: Support SyrupUSDC in the Evelyn IOU burner with per-currency swap limits
This commit is contained in:
@@ -16,31 +16,39 @@ The system SHALL expose an empty lifecycle-free `EvelynIOUBurnerService` under `
|
||||
- **THEN** it uses 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.
|
||||
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 processing. The constructor SHALL also accept a non-null `List<MoneyAmount>` named `inputCurrencyMaximumSwapAmounts`, defensively copy its values, and match each maximum to an accepted input by canonical currency UUID, independently of maximum-list order or supplied metadata. Each maximum SHALL be expressed in that same currency's own units. Empty reserves and maxima lists 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
|
||||
- **WHEN** multiple configured entries have spendable balances and usable Solana data
|
||||
- **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
|
||||
|
||||
#### Scenario: Maximum list order is independent
|
||||
- **WHEN** maxima are supplied in a different order from the reserve list or carry stale display metadata for known UUIDs
|
||||
- **THEN** each input receives the maximum for its UUID and reserve-list priority is preserved
|
||||
|
||||
#### Scenario: Caller changes its configuration lists
|
||||
- **WHEN** a caller changes either supplied list after construction
|
||||
- **THEN** the accepted currencies, their priority, reserves and maxima remain as validated during construction
|
||||
|
||||
### 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.
|
||||
Construction SHALL require all injected services, both input configuration lists, 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 maximum to be strictly positive; each UUID to be known to the injected Currency Identity Service; UUIDs to be unique within each list; the maximum UUID set to exactly equal the accepted reserve UUID set; and EVE not to be an input currency. It SHALL require 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
|
||||
- **WHEN** every dependency and value is valid, reserves are non-negative, every accepted input has exactly one positive maximum, UUIDs are distinct and known within each list, 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
|
||||
- **WHEN** a reserve is negative or null, a token maximum is null or 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
|
||||
@@ -51,8 +59,16 @@ Every configured non-SOL UUID SHALL resolve through reverse CIS lookup to exactl
|
||||
- **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
|
||||
|
||||
#### Scenario: Maximum configuration is incomplete or ambiguous
|
||||
- **WHEN** a maximum is missing for an accepted input, duplicated by UUID, or supplied for an unaccepted currency
|
||||
- **THEN** construction fails before a worker or transaction is created
|
||||
|
||||
#### Scenario: Invalid maximum identity or value
|
||||
- **WHEN** a maximum entry, amount, currency or UUID is null, or its UUID is unknown or resolves to an invalid canonical identity
|
||||
- **THEN** construction fails with a diagnostic identifying the invalid maximum configuration
|
||||
|
||||
### 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.
|
||||
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, 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
|
||||
@@ -97,7 +113,7 @@ At the start of an iteration, if a prior burn signature is pending, the service
|
||||
### 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.
|
||||
For each entry the service SHALL subtract its configured reserve and skip non-positive spendable balance. Missing or unusable balance, mint account, supported program, or mint metadata 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
|
||||
@@ -111,34 +127,6 @@ For each entry the service SHALL subtract its configured reserve and skip non-po
|
||||
- **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.
|
||||
|
||||
@@ -225,3 +213,37 @@ The EVE amount SHALL use plain decimal notation without unnecessary trailing zer
|
||||
#### 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
|
||||
|
||||
### 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 SHALL never exceed either bound through rounding. Input selection SHALL require no input/USDC Ticker observation. Maxima SHALL limit buybacks only; EVE burns SHALL remain uncapped. 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, 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, 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, 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 and the mint supports six decimals
|
||||
- **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
|
||||
|
||||
### Requirement: Production burner accepts USDT and SyrupUSDC
|
||||
The production Hub SHALL configure USDT first with reserve 0.1 and maximum input 0.25 USDT, and SyrupUSDC second with reserve 0.095 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 and 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 two exact reserves and maxima as MoneyAmount values in USDT-then-SyrupUSDC priority and retains the other existing production arguments
|
||||
|
||||
Reference in New Issue
Block a user