11/**
22 * @prettier
33 */
4+ import * as t from 'io-ts' ;
5+ import { GetVaultResponse , VaultProtocol } from '@bitgo/public-types' ;
46import {
7+ ConcreteDepositResult ,
8+ MorphoDepositResult ,
59 DefiOperation ,
610 DefiOperationListResult ,
711 DepositResult ,
812 DepositToVaultOptions ,
913 GetOperationOptions ,
14+ GetVaultConfigOptions ,
1015 IDefiVault ,
1116 ListOperationsOptions ,
1217 ResumeDepositOptions ,
1318} from './iDefiVault' ;
1419import { IWallet } from '../wallet' ;
1520import { BitGoBase } from '../bitgoBase' ;
21+ import { decodeWithCodec } from '../utils' ;
1622
1723/**
1824 * Error thrown when a concurrent active deposit already exists for the (wallet, vault) pair.
@@ -47,13 +53,26 @@ export class DefiVault implements IDefiVault {
4753 this . bitgo = wallet . bitgo ;
4854 }
4955
56+ /**
57+ * Fetch vault config from defi-service. Used internally to determine
58+ * which deposit path to take (Concrete vs Morpho).
59+ */
60+ async getVaultConfig ( params : GetVaultConfigOptions ) : Promise < GetVaultResponse > {
61+ if ( ! params . vaultId ) {
62+ throw new Error ( 'vaultId is required' ) ;
63+ }
64+ const raw = await this . bitgo
65+ . get ( this . bitgo . microservicesUrl ( `/api/defi-service/v1/vaults/${ params . vaultId } ` ) )
66+ . result ( ) ;
67+ return decodeWithCodec ( GetVaultResponse , raw , 'getVaultConfig' ) ;
68+ }
69+
5070 /**
5171 * Deposit an amount of underlying asset into a vault.
5272 *
53- * Internally issues two sendMany calls (approve + deposit) and returns the
54- * operationId that links them. If the deposit sendMany fails after
55- * the approve succeeds, the error propagates — the server-side reconciler
56- * handles orphaned approvals.
73+ * Dispatches to the concrete or morpho path based on vault provider.
74+ * The concrete path returns a pendingApproval (custodial wallet).
75+ * The morpho path issues two sendMany calls (approve + deposit).
5776 *
5877 * @param params.vaultId - DeFi-service vault identifier
5978 * @param params.amount - amount in base units of the underlying asset
@@ -68,6 +87,41 @@ export class DefiVault implements IDefiVault {
6887 throw new Error ( 'amount is required' ) ;
6988 }
7089
90+ const config = await this . getVaultConfig ( { vaultId : params . vaultId } ) ;
91+
92+ if ( config . protocol === VaultProtocol . CONCRETE_BTCCX ) {
93+ return this . depositToConcreteVault ( params ) ;
94+ } else if ( config . protocol === VaultProtocol . MORPHO ) {
95+ return this . depositToMorphoVault ( params ) ;
96+ } else {
97+ throw new Error ( `Unsupported vault protocol: ${ config . protocol } ` ) ;
98+ }
99+ }
100+
101+ /**
102+ * Concrete BTC vault deposit path. The client BTC wallet is custodial, so
103+ * sendMany returns a pendingApproval rather than a signed transfer.
104+ * No recipients are sent — WP resolves the escrow destination server-side.
105+ */
106+ private async depositToConcreteVault ( params : DepositToVaultOptions ) : Promise < ConcreteDepositResult > {
107+ const sendManyResult = await this . wallet . sendMany ( {
108+ type : 'defi-deposit' ,
109+ defiParams : {
110+ vaultId : params . vaultId ,
111+ amount : params . amount ,
112+ actionType : 'defi-deposit' ,
113+ } ,
114+ ...( params . walletPassphrase ? { walletPassphrase : params . walletPassphrase } : { } ) ,
115+ } ) ;
116+
117+ return this . extractConcreteDepositResult ( sendManyResult ) ;
118+ }
119+
120+ /**
121+ * Morpho vault deposit path. Issues two sendMany calls (approve + deposit)
122+ * and returns the operationId that links them.
123+ */
124+ private async depositToMorphoVault ( params : DepositToVaultOptions ) : Promise < MorphoDepositResult > {
71125 // TODO(CGD-1709): Re-enable active operation pre-flight check once the
72126 // defi-service operations endpoint is deployed and returning active state.
73127 // const activeOps: DefiOperationListResult = await this.bitgo
@@ -256,6 +310,22 @@ export class DefiVault implements IDefiVault {
256310 return intent ?. operationId as string | undefined ;
257311 }
258312
313+ /**
314+ * Extracts {@link ConcreteDepositResult} from a custodial sendMany response.
315+ * Concrete BTC deposits return a `pendingApproval` instead of a txRequest —
316+ * throws if `pendingApproval.id` is absent, indicating an unexpected shape.
317+ */
318+ private extractConcreteDepositResult ( sendManyResult : Record < string , unknown > ) : ConcreteDepositResult {
319+ const SendManyConcreteResponse = t . type ( {
320+ pendingApproval : t . intersection ( [ t . type ( { id : t . string } ) , t . partial ( { state : t . string } ) ] ) ,
321+ } ) ;
322+ const decoded = decodeWithCodec ( SendManyConcreteResponse , sendManyResult , 'defi-deposit sendMany response' ) ;
323+ return {
324+ pendingApprovalId : decoded . pendingApproval . id ,
325+ state : decoded . pendingApproval . state ?? 'awaitingSignature' ,
326+ } ;
327+ }
328+
259329 private operationsUrl ( ) : string {
260330 return `/api/defi-service/v1/wallets/${ this . wallet . id ( ) } /operations` ;
261331 }
0 commit comments