Skip to content

Remove BTC purchase mechanism on Scrypt#3743

Open
TaprootFreak wants to merge 2 commits into
developfrom
feat/remove-scrypt-btc-purchase-3739
Open

Remove BTC purchase mechanism on Scrypt#3743
TaprootFreak wants to merge 2 commits into
developfrom
feat/remove-scrypt-btc-purchase-3739

Conversation

@TaprootFreak
Copy link
Copy Markdown
Collaborator

Why

On 2026-05-21, buy_crypto #123090 routed a 570'000 EUR -> BTC trade onto Scrypt because Binance had insufficient BTC (Rule 192) and the Binance USDT refill pipeline (Rule 210) is Inactive. We paid 66'487 EUR/BTC on Scrypt vs. Kraken VWAP @ 13:44 UTC of 66'218 EUR/BTC -- a +0.41% premium, ~2'257 EUR more than a hypothetical Kraken-VWAP fill.

In our 6-month history Scrypt BTC/EUR averages a ~0.6% spread over reference, while Scrypt USDT/EUR is ~0.13%. We should not be buying BTC on Scrypt when a cheaper route exists.

The previous attempt (PR #3740) was DB-only -- a future LM rule misconfiguration could re-introduce the same incident. This PR enforces the policy structurally in code so it cannot happen again.

Closes #3739

What

Code (src/subdomains/core/liquidity-management/adapters/actions/scrypt.adapter.ts)

  • Remove the sell-if-deficit command from ScryptAdapter (enum entry, command registration, completion check, param validation, the sellIfDeficit method, and its param helpers)
  • Remove now-unused constructor dependencies and imports (LiquidityManagementRuleRepository, LiquidityBalanceRepository, BuyCryptoService, PriceCurrency, forwardRef, Inject)
  • Add a structural guard in sell() that throws OrderNotProcessableException when tradeAsset === 'BTC' -- fails fast before any balance/price logic
  • Add a structural guard in buy() that throws OrderNotProcessableException when targetAssetEntity.dexName === 'BTC' -- same fail-fast semantics

These guards make it impossible for any LM rule misconfiguration to acquire BTC via Scrypt.

Migration (migration/1779381590531-RouteScryptEurViaUsdt.js)

  • up(): re-point Rule 313 (Scrypt/EUR redundancy) from Action 261 to Action 233 (Scrypt sell USDT, same action already used by Rule 312 for CHF); then delete Action 261 (Scrypt sell-if-deficit BTC)
  • down(): re-insert Action 261 with original params via IDENTITY_INSERT and re-point Rule 313 back to 261
  • Rule 314 (Scrypt/BTC withdraw) is kept as cleanup path for any residual BTC sitting on Scrypt

Prerequisite (out of scope, blocks safe deploy)

Rule 210 (Binance/USDT) is Inactive as of 2026-05-21 -- this is exactly why the 2026-05-21 trade fell back to Scrypt in the first place. It must be re-activated before deploying this PR, otherwise large BTC buy_crypto orders will pile up on MissingLiquidity once the Scrypt BTC path is cut. Verify with:

SELECT id, status, [minimal], optimal FROM liquidity_management_rule WHERE id = 210;

Acceptance criteria

  • New migration committed in migration/ with a timestamp > 1775745823000 (1779381590531)
  • Migration's up() reroutes Rule 313 to Action 233 and deletes Action 261
  • Migration's down() restores the previous state
  • Code-level structural guards in sell() and buy() prevent BTC acquisition via Scrypt
  • sell-if-deficit command fully removed from ScryptAdapter
  • Lint, format, build green locally
  • Verification queries below produce expected results on DEV
  • Draft PR opened against develop on DFXswiss/api
  • Linked to Stop buying BTC on Scrypt — route EUR via USDT instead #3739

Verification queries

Before merge

-- Confirm action 261 has no incoming references
SELECT id, system, command, onSuccessId, onFailId
FROM liquidity_management_action
WHERE onSuccessId = 261 OR onFailId = 261;
-- Expected: [] empty

-- Confirm Rule 313 currently points at 261
SELECT id, context, [maximal], redundancyStartActionId
FROM liquidity_management_rule WHERE id = 313;
-- Expected: redundancyStartActionId = 261

After deploy

-- Rule 313 now points at 233
SELECT id, redundancyStartActionId FROM liquidity_management_rule WHERE id = 313;
-- Expected: 233

-- Action 261 is gone
SELECT id FROM liquidity_management_action WHERE id = 261;
-- Expected: [] empty

-- No new Scrypt BTC/EUR trades after migration timestamp
SELECT TOP 5 id, created, symbol, cost, [order]
FROM exchange_tx
WHERE exchange = 'Scrypt' AND symbol = 'BTC/EUR' AND created > '<deploy-date>'
ORDER BY id DESC;
-- Expected: [] empty (any rows mean fallback wasn't fully cut)

End-to-end smoke test on DEV

  1. Deposit 1'000 EUR to the DEV Scrypt EUR account
  2. Watch liquidity_management_pipeline -- confirm new pipeline targets Rule 313 with action 233 (not 261)
  3. Confirm exchange_tx shows a USDT/EUR Scrypt trade, not BTC/EUR
  4. Confirm Rule 315 (Scrypt/USDT redundancy) then triggers Action 231 to withdraw USDT to Tron/Binance

Re-point Rule 313 (Scrypt/EUR redundancy) from Action 261
(Scrypt sell-if-deficit BTC) to Action 233 (Scrypt sell USDT),
matching the existing CHF route (Rule 312). Remove the now
unreferenced Action 261.

Scrypt's BTC/EUR pricing is structurally worse than its USDT
pairs (~0.6% spread vs ~0.13%). Incident on 2026-05-21 saw a
570k EUR buy_crypto routed to Scrypt BTC/EUR at +0.41% premium
over Kraken VWAP because Rule 210 (Binance USDT refill) was
Inactive. Routing EUR via USDT eliminates the costly BTC fill.

Rule 314 (Scrypt/BTC withdraw) is retained as cleanup path
for any residual BTC sitting on Scrypt.

Closes #3739
Structurally remove the ability to acquire BTC via Scrypt. Scrypt's
BTC/EUR spreads are materially worse than its USDT/EUR pairs
(~0.6% vs ~0.13%). BTC acquisition must route through Binance USDT.

- Drop the `sell-if-deficit` command from ScryptAdapter (enum entry,
  command registration, completion check, param validation, the
  `sellIfDeficit` method itself and its param helpers)
- Drop now-unused constructor dependencies and imports
  (LiquidityManagementRuleRepository, LiquidityBalanceRepository,
  BuyCryptoService, PriceCurrency, forwardRef/Inject)
- Add structural guards in `sell()` (tradeAsset === 'BTC') and `buy()`
  (targetAsset.dexName === 'BTC') that throw OrderNotProcessableException
  before any balance/price logic — no future LM rule misconfiguration
  can re-introduce BTC acquisition via Scrypt

Relates to #3739
@TaprootFreak TaprootFreak marked this pull request as ready for review May 21, 2026 18:25
@TaprootFreak TaprootFreak requested a review from davidleomay as a code owner May 21, 2026 18:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stop buying BTC on Scrypt — route EUR via USDT instead

1 participant