@@ -20,6 +20,7 @@ import { Blockchain } from 'src/integration/blockchain/shared/enums/blockchain.e
2020import { isAsset } from 'src/shared/models/active' ;
2121import { Asset , AssetType } from 'src/shared/models/asset/asset.entity' ;
2222import { AssetService } from 'src/shared/models/asset/asset.service' ;
23+ import { SettingService } from 'src/shared/models/setting/setting.service' ;
2324import { DfxLogger } from 'src/shared/services/dfx-logger' ;
2425import { LiquidityManagementOrder } from '../../entities/liquidity-management-order.entity' ;
2526import { LiquidityManagementSystem } from '../../enums' ;
@@ -34,9 +35,9 @@ export enum ClementineBridgeCommands {
3435}
3536
3637/**
37- * Correlation ID format for tracking operations:
38- * - Deposit: clementine:deposit:{depositAddress}:{btcTxId }
39- * - Withdraw: clementine:withdraw:{step}:{signerAddress}:{destinationAddress}:{withdrawalUtxo}:{optimisticSig}:{operatorSig }
38+ * Correlation ID format for tracking operations (base64-encoded JSON after prefix) :
39+ * - Deposit: clementine:deposit:{base64(DepositCorrelationData) }
40+ * - Withdraw: clementine:withdraw:{base64(WithdrawCorrelationData) }
4041 *
4142 * Withdrawal steps: dust_sent, scanning, signatures_generated, sent_to_bridge, waiting_optimistic, sent_to_operators
4243 */
@@ -72,6 +73,12 @@ const BITCOIN_RELAY_CONFIRMATIONS: Record<ClementineNetwork, number> = {
7273 [ ClementineNetwork . TESTNET4 ] : 100 ,
7374} ;
7475
76+ interface DepositCorrelationData {
77+ depositAddress : string ;
78+ citreaAddress : string ;
79+ btcTxId ?: string ;
80+ }
81+
7582interface WithdrawCorrelationData {
7683 step : string ;
7784 signerAddress : string ;
@@ -107,6 +114,7 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter {
107114 private readonly assetService : AssetService ,
108115 private readonly bitcoinFeeService : BitcoinFeeService ,
109116 private readonly bitcoinTestnet4FeeService : BitcoinTestnet4FeeService ,
117+ private readonly settingService : SettingService ,
110118 ) {
111119 super ( LiquidityManagementSystem . CLEMENTINE_BRIDGE ) ;
112120
@@ -152,6 +160,10 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter {
152160 /**
153161 * Deposit BTC to receive cBTC on Citrea
154162 * Note: Clementine uses a fixed bridge amount of 10 BTC
163+ *
164+ * Deposit is a two-step process:
165+ * 1. Generate deposit address (returns pending_confirmation)
166+ * 2. After manual approval, send BTC to deposit address
155167 */
156168 private async deposit ( order : LiquidityManagementOrder ) : Promise < CorrelationId > {
157169 const {
@@ -189,19 +201,21 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter {
189201 const { depositAddress } = await this . clementineClient . depositStart ( this . recoveryTaprootAddress , citreaAddress ) ;
190202
191203 this . logger . info (
192- `Deposit address generated: ${ depositAddress } , recovery: ${ this . recoveryTaprootAddress } , citrea: ${ citreaAddress } ` ,
204+ `Deposit address generated: ${ depositAddress } , recovery: ${ this . recoveryTaprootAddress } , citrea: ${ citreaAddress } . Waiting for manual approval before sending BTC. ` ,
193205 ) ;
194206
195207 // Update order with fixed amount
196208 order . inputAmount = CLEMENTINE_BRIDGE_AMOUNT_BTC ;
197209 order . inputAsset = bitcoinAsset . name ;
198210 order . outputAsset = citreaAsset . name ;
199211
200- // Send BTC to the deposit address
201- const btcTxId = await this . sendBtcToAddress ( depositAddress , CLEMENTINE_BRIDGE_AMOUNT_BTC ) ;
212+ // Store deposit data - BTC will be sent after manual approval
213+ const correlationData : DepositCorrelationData = {
214+ depositAddress,
215+ citreaAddress,
216+ } ;
202217
203- // Store deposit address and txId in correlation ID for status checks
204- return `${ CORRELATION_PREFIX . DEPOSIT } ${ depositAddress } :${ btcTxId } ` ;
218+ return `${ CORRELATION_PREFIX . DEPOSIT } ${ this . encodeDepositCorrelation ( correlationData ) } ` ;
205219 }
206220
207221 /**
@@ -291,21 +305,44 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter {
291305 }
292306
293307 try {
294- // Extract deposit address and BTC txId from correlation ID
295- const correlationData = order . correlationId . replace ( CORRELATION_PREFIX . DEPOSIT , '' ) ;
296- const [ depositAddress , btcTxId ] = correlationData . split ( ':' ) ;
308+ const correlationData = this . decodeDepositCorrelation (
309+ order . correlationId . replace ( CORRELATION_PREFIX . DEPOSIT , '' ) ,
310+ ) ;
297311
298- // Step 1: Verify the Bitcoin transaction has enough confirmations for block relay
299- if ( btcTxId && ! ( await this . isBtcTxRelayConfirmed ( btcTxId ) ) ) {
312+ this . logger . verbose (
313+ `Deposit check: address=${ correlationData . depositAddress } , btcTxId=${ correlationData . btcTxId ?? 'pending' } ` ,
314+ ) ;
315+
316+ // Step 1: If no BTC sent yet, wait for manual approval via Setting
317+ if ( ! correlationData . btcTxId ) {
318+ const isConfirmed = await this . isDepositApproved ( correlationData . depositAddress ) ;
319+ if ( ! isConfirmed ) {
320+ this . logger . verbose (
321+ `Deposit ${ correlationData . depositAddress } : waiting for manual approval (add to 'clementineApprovedDeposits' setting)` ,
322+ ) ;
323+ return false ;
324+ }
325+
326+ await this . removeDepositApproval ( correlationData . depositAddress ) ;
327+
328+ const btcTxId = await this . sendBtcToAddress ( correlationData . depositAddress , CLEMENTINE_BRIDGE_AMOUNT_BTC ) ;
329+ correlationData . btcTxId = btcTxId ;
330+ order . correlationId = `${ CORRELATION_PREFIX . DEPOSIT } ${ this . encodeDepositCorrelation ( correlationData ) } ` ;
331+ this . logger . info ( `Deposit ${ correlationData . depositAddress } : approved and BTC sent, txId=${ btcTxId } ` ) ;
332+ return false ;
333+ }
334+
335+ // Step 2: Verify the Bitcoin transaction has enough confirmations for block relay
336+ if ( correlationData . btcTxId && ! ( await this . isBtcTxRelayConfirmed ( correlationData . btcTxId ) ) ) {
300337 this . logger . verbose (
301- `Deposit ${ depositAddress } : BTC TX not yet confirmed (need ${ BITCOIN_RELAY_CONFIRMATIONS [ this . network ] } +)` ,
338+ `Deposit ${ correlationData . depositAddress } : BTC TX not yet confirmed (need ${ BITCOIN_RELAY_CONFIRMATIONS [ this . network ] } +)` ,
302339 ) ;
303340 return false ;
304341 }
305342
306- // Step 2 : Check Clementine deposit status
307- const depositStatus = await this . clementineClient . depositStatus ( depositAddress ) ;
308- this . logger . verbose ( `Deposit ${ depositAddress } : Clementine status = ${ depositStatus . status } ` ) ;
343+ // Step 3 : Check Clementine deposit status
344+ const depositStatus = await this . clementineClient . depositStatus ( correlationData . depositAddress ) ;
345+ this . logger . verbose ( `Deposit ${ correlationData . depositAddress } : Clementine status = ${ depositStatus . status } ` ) ;
309346
310347 if ( depositStatus . status === 'failed' ) {
311348 throw new OrderFailedException ( `Clementine deposit failed: ${ depositStatus . errorMessage ?? 'Unknown error' } ` ) ;
@@ -322,6 +359,17 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter {
322359 }
323360 }
324361
362+ private async isDepositApproved ( depositAddress : string ) : Promise < boolean > {
363+ const approvedDeposits = await this . settingService . getObj < string [ ] > ( 'clementineApprovedDeposits' , [ ] ) ;
364+ return approvedDeposits . includes ( depositAddress ) ;
365+ }
366+
367+ private async removeDepositApproval ( depositAddress : string ) : Promise < void > {
368+ const approvedDeposits = await this . settingService . getObj < string [ ] > ( 'clementineApprovedDeposits' , [ ] ) ;
369+ const updated = approvedDeposits . filter ( ( addr ) => addr !== depositAddress ) ;
370+ await this . settingService . setObj ( 'clementineApprovedDeposits' , updated ) ;
371+ }
372+
325373 private async checkWithdrawCompletion ( order : LiquidityManagementOrder ) : Promise < boolean > {
326374 const {
327375 pipeline : {
@@ -589,6 +637,14 @@ export class ClementineBridgeAdapter extends LiquidityActionAdapter {
589637 return true ;
590638 }
591639
640+ private encodeDepositCorrelation ( data : DepositCorrelationData ) : string {
641+ return Buffer . from ( JSON . stringify ( data ) ) . toString ( 'base64' ) ;
642+ }
643+
644+ private decodeDepositCorrelation ( encoded : string ) : DepositCorrelationData {
645+ return JSON . parse ( Buffer . from ( encoded , 'base64' ) . toString ( 'utf-8' ) ) ;
646+ }
647+
592648 private encodeWithdrawCorrelation ( data : WithdrawCorrelationData ) : string {
593649 return Buffer . from ( JSON . stringify ( data ) ) . toString ( 'base64' ) ;
594650 }
0 commit comments