Skip to content

feat: add Boltz/Lightning.space adapter for Citrea cBTC liquidity#3266

Draft
TaprootFreak wants to merge 12 commits intodevelopfrom
feat/boltz-liquidity-adapter
Draft

feat: add Boltz/Lightning.space adapter for Citrea cBTC liquidity#3266
TaprootFreak wants to merge 12 commits intodevelopfrom
feat/boltz-liquidity-adapter

Conversation

@TaprootFreak
Copy link
Collaborator

@TaprootFreak TaprootFreak commented Feb 25, 2026

Summary

  • Integrate Lightning.space (Boltz) as additional liquidity source for Citrea/cBTC
  • Enables flexible BTC → cBTC chain swaps for any amount within Boltz pair limits
  • Complements the fixed-amount (10 BTC) Clementine Bridge as onFail fallback
  • Full swap lifecycle: fetch pairs → create swap → send BTC → helpMeClaim → poll until claimed
  • Accounts for Boltz fees in outputAmount (claimDetails.amount vs lockupDetails.amount)

What changed

File Change
src/config/config.ts Fix Boltz API URL to lightning.space/v1
src/integration/blockchain/boltz/boltz-client.ts Fix API paths (/swap/v2/..., /claim/...), add getChainPairs(), helpMeClaim(), add pairHash/referralId/refundPublicKey to createChainSwap()
src/integration/blockchain/boltz/boltz.service.ts Service wrapper creating BoltzClient from config
src/integration/blockchain/boltz/boltz.module.ts NestJS module exporting BoltzService
src/integration/blockchain/blockchain.module.ts Import/export BoltzModule
src/subdomains/core/liquidity-management/adapters/actions/boltz.adapter.ts Complete implementation: inject BitcoinService/BitcoinFeeService, generate secp256k1 refund keys, send BTC to lockup, state machine (btc_sentclaiming → done), store preimage + refundPrivateKey for claiming/refunds
src/subdomains/core/liquidity-management/enums/index.ts Add BOLTZ enum value
src/subdomains/core/liquidity-management/factories/liquidity-action-integration.factory.ts Register BoltzAdapter
src/subdomains/core/liquidity-management/liquidity-management.module.ts Add BoltzAdapter provider
migration/1772100000000-AddBoltzLiquidityAction.js Create Boltz action, wire as onFail for Clementine, activate Rule 320

Flow

1. getChainPairs() → extract pairHash for BTC→cBTC
2. Generate preimage + SHA256 preimageHash
3. Generate secp256k1 key pair → refundPublicKey (for BTC refund on failure)
4. createChainSwap(preimageHash, claimAddress, amount, pairHash, referralId, refundPublicKey)
5. sendBtcToAddress(lockupDetails.lockupAddress, lockupDetails.amount)
6. Poll status every 10s:
   - btc_sent: wait for transaction.server.confirmed → helpMeClaim(preimage, 0x+preimageHash)
   - claiming: wait for transaction.claimed → outputAmount = claimDetails.amount

Migration (auto-runs on deploy)

migration/1772100000000-AddBoltzLiquidityAction.js performs:

  1. Create action: INSERT INTO liquidity_management_action (system, command, tag) VALUES ('Boltz', 'deposit', 'cBTC')
  2. Wire fallback: UPDATE liquidity_management_action SET onFailId = <boltz_id> WHERE id = 236 (Clementine)
  3. Activate rule: UPDATE liquidity_management_rule SET status = 'Active', minimal = 0, optimal = 0.1, maximal = 0.5, reactivationTime = 10 WHERE id = 320

Strategy: Clementine (Action 236) stays primary — fee-free but requires 10 BTC. When Clementine fails (e.g. balance < 10 BTC), the pipeline follows onFail to Boltz, which handles any amount within pair limits (with fees).

Rule 320 (cBTC deficit) → Clementine deposit (10 BTC, no fees)
                              → onFail → Boltz deposit (flexible amount, with fees)

Rollback via down() reverts all three steps.

Test plan

  • npx tsc --noEmit passes
  • npx eslint — 0 errors, 0 warnings in changed files
  • Verify chain swap creation via Lightning.space API
  • Verify BTC sending to lockup address
  • Verify helpMeClaim triggers cBTC claiming
  • Verify swap status polling and completion detection
  • Verify outputAmount reflects Boltz fees (claimDetails.amount, not lockupDetails.amount)
  • Verify refundPublicKey enables BTC refund path on swap failure

Integrate Lightning.space (Boltz) as an additional liquidity source for
Citrea/cBTC, enabling flexible BTC → cBTC reverse swaps for amounts
smaller than the fixed 10 BTC Clementine Bridge.
Use invoice.settled as success status for reverse swaps (not
transaction.claimed which is for submarine swaps). Add complete
SwapUpdateEvent enum matching boltz-backend. Simplify createReverseSwap
params since EVM chains don't need preimageHash/claimPublicKey.
BTC onchain -> cBTC onchain is a Chain Swap (not a Reverse Swap which
is Lightning -> onchain). Switch to POST /v2/swap/chain with
preimageHash, claimAddress, userLockAmount. Use correct final events:
transaction.claimed for success.
@TaprootFreak TaprootFreak marked this pull request as draft February 25, 2026 18:49
Previously the Boltz adapter created a chain swap but never sent BTC to
the lockup address and never called helpMeClaim, leaving swaps stuck at
swap.created. This implements the full flow:

- Fix API URL (lightning.space/v1) and endpoint paths (/swap/v2/...)
- Add getChainPairs() for pairHash and helpMeClaim() for server-side claiming
- Send BTC to lockup address via BitcoinService after swap creation
- Add state machine (btc_sent → claiming → done) in checkDepositCompletion
- Store preimage/preimageHash in correlation data for claiming
@github-actions
Copy link

github-actions bot commented Feb 26, 2026

❌ Security: 1 critical vulnerabilities

…from response

The helpMeClaim endpoint expects preimageHash with 0x prefix (verified
against JuiceSwap frontend's prefix0x() call). Also use the actual
lockup amount from the Boltz response instead of the requested amount
for defensive correctness.
The outputAmount was incorrectly set to userLockAmountSats (BTC sent)
instead of claimAmountSats (cBTC received after Boltz fees). Now stores
swap.claimDetails.amount in correlation data and uses it for the final
outputAmount calculation.
return-await is required inside try-catch blocks per
@typescript-eslint/return-await rule.
Creates Boltz deposit action and wires it as onFail fallback for
Clementine (Action 236). Activates Rule 320 (Citrea cBTC) with
thresholds: minimal=0, optimal=0.1, maximal=0.5.

Strategy: Clementine stays primary (fee-free, 10 BTC fixed). When it
fails (e.g. balance < 10 BTC), Boltz handles flexible amounts as
fallback.
Generate secp256k1 key pair and send compressed public key as
refundPublicKey in createChainSwap request. Store private key in
correlation data for potential refund signing if swap fails.
Without this status, a failed lockup would leave the swap polling
indefinitely. Verified against JuiceSwap bapp which includes
lockupFailed in its swapStatusFailed list.
Fetch dynamic min/max limits from Boltz chain pairs API and reject
orders where the amount falls outside the allowed range. Prevents
invalid swap creation and allows the order to retry later.
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.

1 participant