151 lines
9.0 KiB
Markdown
151 lines
9.0 KiB
Markdown
# Evelyn liquidity proceeds
|
||
|
||
`com.r35157.evelyn.service.liquidity.EvelynLiquidityService` is a domain-only
|
||
Nenjim component. `impl.ref.EvelynLiquidityServiceImpl` additionally implements
|
||
`NenjimProcess`; NenjimHub owns startup. The optional process ID is
|
||
`evelyn.liquidity.prod`.
|
||
|
||
The owner creates/funds EVE/SyrupUSDC CLMM positions manually (for example,
|
||
10 EVE each) using the dedicated liquidity wallet. This service withdraws,
|
||
converts and pays proceeds; it never creates, refills, reranges, closes or burns
|
||
positions, NFTs or source token accounts. A falling price leaves nonqualifying
|
||
positions intact, including positions whose inventory has returned to EVE.
|
||
|
||
## Deployment
|
||
|
||
Copy `conf/evelyn-liquidity.conf.example` to `conf/evelyn-liquidity.conf` and
|
||
replace every placeholder. The local runtime file is intentionally ignored by
|
||
Git. Both begin with `FORMAT_VERSION=1`; the example is never loaded as a
|
||
fallback. Unknown, duplicate and missing settings fail validation.
|
||
|
||
| Setting | Contract |
|
||
| --- | --- |
|
||
| `LIQUIDITY_WALLET` | Dedicated public Solana wallet address |
|
||
| `SIGNER_KEY_NAME` | Existing external jup key name controlling exactly that address |
|
||
| `CLMM_POOL` | Existing EVE/SyrupUSDC Raydium CLMM pool address |
|
||
| `HARVEST_DISTANCE_PERCENT` | `0 <= h < 100`; `10` means ten percent |
|
||
| `ITERATION_INTERVAL_SECONDS` | Positive integer delay after an iteration completes; example `300` |
|
||
| `USDC_RECIPIENT` | Recipient wallet owner address, not a token account; must differ from the liquidity wallet |
|
||
| `MINIMUM_SYRUPUSDC_RESERVE` | Nonnegative SyrupUSDC amount; example `0` |
|
||
| `MINIMUM_USDC_RESERVE` | Nonnegative USDC amount; example `0` |
|
||
| `MAXIMUM_SWAP_SLIPPAGE_BPS` | Explicit Jupiter limit in `[0,10000]` basis points |
|
||
| `MAXIMUM_WITHDRAWAL_SLIPPAGE_BPS` | Explicit CLMM limit in `[0,10000)` basis points |
|
||
|
||
The existing SolanaWallet implementation checks the external jup version and
|
||
key/address binding during construction. No key material belongs in these files.
|
||
Fund the wallet with SOL for fees and any destination token-account rent.
|
||
Reserve amounts must be exactly representable at the corresponding mint's
|
||
precision; `0.01` SyrupUSDC and `0.5` USDC are permitted operating choices.
|
||
No hidden positive reserve is added.
|
||
|
||
Only after configuring and funding the wallet, explicitly add
|
||
`evelyn.liquidity.prod` to the existing `[AUTO_START]` section of private
|
||
`conf/NenjimHub.conf`. Keep Registry first and Journal second, with no blank line
|
||
inside the active section. Preserve all existing process selections and their
|
||
order. This process requires no Ticker, Evelyn status, JavaFX or burner process.
|
||
The checked-in Hub example continues to select only Registry and Journal.
|
||
|
||
Deployment configuration and the dedicated signer/wallet, Jupiter and direct
|
||
Raydium dependencies are constructed only when the selected entry is reached.
|
||
Registry IDs are `solana.wallet.evelyn-liquidity-prod`,
|
||
`jupiter.swap.evelyn-liquidity-prod`, `raydium.service.evelyn-liquidity-prod`
|
||
and `evelyn.liquidity.prod`. Jupiter signs with this wallet. Currency/mint
|
||
identities come from CIS; token programs and precision come from chain metadata.
|
||
|
||
## Iteration and recovery
|
||
|
||
A single worker runs immediately on startup and then with fixed delay after
|
||
completion. Every iteration follows the same three independent steps:
|
||
|
||
1. Discover the wallet's position NFTs. For each owned, positive-liquidity
|
||
position in the configured pool, read fresh on-chain pool state in
|
||
SyrupUSDC per EVE after discovery and any earlier withdrawals. Withdraw complete
|
||
liquidity when its normalized **upper** boundary satisfies
|
||
`U <= P * (1 - h/100)`. Equality qualifies. Both token orientations use
|
||
exact squared Q64.64 comparisons, without display-price rounding. At
|
||
`P=15, h=10`, `13–13.5` qualifies and `13.5–14` does not. Withdrawals retain
|
||
the position and NFT, and return pool-token fees to the same wallet.
|
||
Each position uses its own fresh state for both eligibility and withdrawal
|
||
minimums; the price is not reused across positions.
|
||
2. Read current SyrupUSDC balance and swap the full balance above its reserve
|
||
into USDC through the confirmed exact-input Jupiter operation.
|
||
3. Read current USDC balance and send the full balance above its reserve to
|
||
the recipient owner, creating its ATA at the sender's expense if needed.
|
||
The transfer submits once and is awaited at `CONFIRMED`.
|
||
|
||
Missing/zero balances and balances equal to their reserves are skipped. Amounts
|
||
are rounded down to mint precision. An unavailable or unusable pool price stops
|
||
the remaining withdrawals in that iteration while retaining steps 2 and 3. EVE returned as principal/fees and
|
||
other reward tokens remain in the wallet and never enter the conversion/payout
|
||
pipeline. All available SyrupUSDC/USDC is eligible, including manual deposits.
|
||
|
||
An ordinary position failure does not block other readable positions. An
|
||
ordinary step failure does not block later independent steps. Logs identify
|
||
steps, position NFTs, liquidity/token amounts and returned signatures. Unknown
|
||
outcomes are logged with signatures when available. Subsequent iterations and
|
||
restarts reread chain state; there is no service journal, attribution, pending
|
||
signature gate, immediate retry or transaction replay. Empty positions are
|
||
skipped and can be manually reused. Shutdown interrupts the current operation
|
||
and prevents following steps; the reference implementation can also be closed
|
||
by its local owner through `AutoCloseable`.
|
||
|
||
## Withdrawal support
|
||
|
||
The transaction follows the official
|
||
[decrease_liquidity_v2 accounts](https://github.com/raydium-io/raydium-clmm/blob/master/programs/amm/src/instructions/decrease_liquidity_v2.rs)
|
||
and [decrease/minimum-output behavior](https://github.com/raydium-io/raydium-clmm/blob/master/programs/amm/src/instructions/decrease_liquidity.rs),
|
||
also checked against the
|
||
[official SDK encoder](https://github.com/raydium-io/raydium-sdk-V2/blob/master/src/raydium/clmm/instrument.ts).
|
||
Withdrawal and closure are separate actions in
|
||
[Raydium's liquidity documentation](https://github.com/raydium-io/raydium-docs-v1/blob/main/user-flows/add-remove-liquidity.mdx).
|
||
|
||
Raydium validates pool/personal-position discriminators, the owned NFT holding,
|
||
current complete liquidity, mint precision, vaults and tick arrays before signing.
|
||
For every pool or reward mint, existing owner token accounts are reused after
|
||
validating their mint, owner, token program and initialized state, including
|
||
empty accounts and accounts outside the owner's ATA. Only when no owner holding
|
||
exists does the withdrawal use or idempotently create its ATA. A failed holdings
|
||
read aborts rather than creating an additional account. This preserves the
|
||
existing wallet's single-account-per-mint balance/transfer contract. The
|
||
transaction requests a 600,000 compute-unit limit.
|
||
The bitmap extension and initialized reward triplets are included when required.
|
||
The integer principal math matches Raydium's
|
||
[tick algorithm](https://github.com/raydium-io/raydium-clmm/blob/master/programs/amm/src/libraries/tick_math.rs).
|
||
Minimum principal amounts are floored after applying explicit withdrawal
|
||
slippage. An amount too small to protect at least one raw unit is rejected.
|
||
No withdrawal always substitutes zero for both minimums.
|
||
Price movement after the per-position read remains possible before execution;
|
||
principal minimums protect received amounts, not the harvest-distance threshold.
|
||
|
||
The current EVE mint uses Token-2022 metadata pointer/token metadata extensions;
|
||
SyrupUSDC uses legacy SPL. Read-only mainnet inspection on 2026-10-04 found the
|
||
EVE/SyrupUSDC pool `EpZXeeShz64DK8HfKnjWPTkmhN7dZe77SjvzNDX8CARq` with decimals
|
||
8/6, tick spacing 60 and no initialized rewards. Deployment still requires an
|
||
explicit pool setting and repeats validation. Mint close-authority and metadata/
|
||
group extensions are supported; other transfer-affecting extensions, including
|
||
transfer fees/hooks, fail before signing. The withdrawal encoder supports legacy
|
||
transactions fitting Solana's packet limit and rejects larger account sets.
|
||
|
||
## Offline verification
|
||
|
||
The Gradle JUnit fixtures exercise exact threshold equality, inversion, raw
|
||
principal amounts, owner/pool filtering, reserves, independent failures,
|
||
restart recovery and shutdown. Captured serialized transactions verify account
|
||
order, full liquidity, minimum output, NFT retention, bitmap/reward accounts and
|
||
one submission/confirmation attempt. Review regression fixtures also cover price
|
||
changes during discovery or earlier withdrawals in both token orientations,
|
||
quote consistency, and reuse/validation of existing pool and reward token accounts
|
||
under both token programs. All financial dependencies are controlled;
|
||
no verification submits live withdrawals, swaps or transfers.
|
||
|
||
```sh
|
||
./gradlew compileJava compileTestJava test \
|
||
--tests '*RaydiumClmm*Test' --tests '*EvelynLiquidityServiceImplTest' \
|
||
--tests '*NenjimHubLiquidityConfigurationTest'
|
||
openspec validate 89-add-evelyn-liquidity-service --strict
|
||
git diff --check
|
||
```
|
||
|
||
Stop the process/JVM and remove its explicit Hub selection to disable operation.
|
||
Existing positions and wallet balances remain the recovery state.
|