import { BN , Idl , Program , Provider } from '@project-serum/anchor' ; import { ASSOCIATED_TOKEN_PROGRAM_ID , Token , TOKEN_PROGRAM_ID , } from '@solana/spl-token' ; import { MarketsAccount , StateAccount , CurveHistoryAccount , DepositHistoryAccount , FundingPaymentHistoryAccount , FundingRateHistoryAccount , IWallet , LiquidationHistoryAccount , PositionDirection , TradeHistoryAccount , UserAccount , Market , } from './types' ; import * as anchor from '@project-serum/anchor' ; import clearingHouseIDL from './idl/clearing_house.json' ; import { Connection , PublicKey , TransactionSignature , Keypair , ConfirmOptions , Transaction , TransactionInstruction , } from '@solana/web3.js' ; import { MockUSDCFaucet } from './mockUSDCFaucet' ; import { EventEmitter } from 'events' ; import StrictEventEmitter from 'strict-event-emitter-types' ; import { getClearingHouseStateAccountPublicKey , getUserAccountPublicKey , getUserAccountPublicKeyAndNonce , } from './addresses' ; import { ClearingHouseAccountSubscriber , ClearingHouseAccountEvents , ClearingHouseAccountTypes , } from './accounts/types' ; import { DefaultClearingHouseAccountSubscriber } from './accounts/defaultClearingHouseAccountSubscriber' ; import { TxSender } from './tx/types' ; import { DefaultTxSender } from './tx/defaultTxSender' ; import { wrapInTx } from './tx/utils' ; /**
 * # ClearingHouse
 * This class is the main way to interact with Drift Protocol. It allows you to subscribe to the various accounts where the Market's state is stored, as well as: opening positions, liquidating, settling funding, depositing & withdrawing, and more.
 *
 * The default way to construct a ClearingHouse instance is using the  from method. This will create an instance using the static  DefaultClearingHouseAccountSubscriber , which will use a websocket for each state account subscription.
 * Alternatively, if you want to implement your own method of subscribing to the state accounts on the blockchain, you can implement a  ClearingHouseAccountSubscriber and use it in the  ClearingHouse . constructor export class ClearingHouse { connection : Connection ; wallet : IWallet ; public program : Program ; provider : Provider ; opts ? : ConfirmOptions ; accountSubscriber : ClearingHouseAccountSubscriber ; eventEmitter : StrictEventEmitter < EventEmitter , ClearingHouseAccountEvents > ; isSubscribed = false ; txSender : TxSender ; public static from ( connection , wallet , clearingHouseProgramId , opts = Provider . defaultOptions ( ) ) { const provider = new Provider ( connection , wallet , opts ) ; const program = new Program ( clearingHouseIDL as Idl , clearingHouseProgramId , provider ) ; const accountSubscriber = new DefaultClearingHouseAccountSubscriber ( program ) ; const txSender = new DefaultTxSender ( provider ) ; return new ClearingHouse ( connection , wallet , program , accountSubscriber , txSender , opts ) ; } public constructor ( connection , wallet , program , accountSubscriber , txSender , opts ) { this . connection = connection ; this . wallet = wallet ; this . opts = opts ; this . program = program ; this . accountSubscriber = accountSubscriber ; this . eventEmitter = this . accountSubscriber . eventEmitter ; this . txSender = txSender ; } param optionalSubscriptions returns public async subscribe ( optionalSubscriptions ? ) { this . isSubscribed = await this . accountSubscriber . subscribe ( optionalSubscriptions ) ; return this . isSubscribed ; } returns public async subscribeToAll ( ) { return this . subscribe ( [ 'curveHistoryAccount' , 'depositHistoryAccount' , 'fundingPaymentHistoryAccount' , 'fundingRateHistoryAccount' , 'liquidationHistoryAccount' , 'tradeHistoryAccount' , ] ) ; } /**
     *	Forces the accountSubscriber to fetch account updates from rpc
     */ public async fetchAccounts ( ) { await this . accountSubscriber . fetch ( ) ; } /**
     * Unsubscribe from all currently subscribed state accounts
     */ public async unsubscribe ( ) { await this . accountSubscriber . unsubscribe ( ) ; this . isSubscribed = false ; } statePublicKey ? : PublicKey ; public async getStatePublicKey ( ) { if ( this . statePublicKey ) { return this . statePublicKey ; } this . statePublicKey = await getClearingHouseStateAccountPublicKey ( this . program . programId ) ; return this . statePublicKey ; } public getStateAccount ( ) { return this . accountSubscriber . getStateAccount ( ) ; } public getMarketsAccount ( ) { return this . accountSubscriber . getMarketsAccount ( ) ; } public getMarket ( marketIndex ) { if ( marketIndex instanceof BN ) { marketIndex = marketIndex . toNumber ( ) ; } return this . getMarketsAccount ( ) . markets [ marketIndex ] ; } public getFundingPaymentHistoryAccount ( ) { return this . accountSubscriber . getFundingPaymentHistoryAccount ( ) ; } public getFundingRateHistoryAccount ( ) { return this . accountSubscriber . getFundingRateHistoryAccount ( ) ; } public getTradeHistoryAccount ( ) { return this . accountSubscriber . getTradeHistoryAccount ( ) ; } public getLiquidationHistoryAccount ( ) { return this . accountSubscriber . getLiquidationHistoryAccount ( ) ; } public getDepositHistoryAccount ( ) { return this . accountSubscriber . getDepositHistoryAccount ( ) ; } public getCurveHistoryAccount ( ) { return this . accountSubscriber . getCurveHistoryAccount ( ) ; } param newWallet public updateWallet ( newWallet ) { const newProvider = new Provider ( this . connection , newWallet , this . opts ) ; const newProgram = new Program ( clearingHouseIDL as Idl , this . program . programId , newProvider ) ; const newTxSender = new DefaultTxSender ( newProvider ) ; this . wallet = newWallet ; this . provider = newProvider ; this . program = newProgram ; this . txSender = newTxSender ; this . userAccountPublicKey = undefined ; this . userAccount = undefined ; } public async initializeUserAccount ( ) { const [ userPositionsAccount , userAccountPublicKey , initializeUserAccountIx , ] = await this . getInitializeUserInstructions ( ) ; const tx = new Transaction ( ) . add ( initializeUserAccountIx ) ; const txSig = await this . txSender . send ( tx , [ userPositionsAccount ] , this . opts ) ; return [ txSig , userAccountPublicKey ] ; } async getInitializeUserInstructions ( ) { const [ userPublicKey , userAccountNonce ] = await getUserAccountPublicKeyAndNonce ( this . program . programId , this . wallet . publicKey ) ; const remainingAccounts = [ ] ; const optionalAccounts = { whitelistToken : false , } ; const state = this . getStateAccount ( ) ; if ( state . whitelistMint ) { optionalAccounts . whitelistToken = true ; const associatedTokenPublicKey = await Token . getAssociatedTokenAddress ( ASSOCIATED_TOKEN_PROGRAM_ID , TOKEN_PROGRAM_ID , state . whitelistMint , this . wallet . publicKey ) ; remainingAccounts . push ( { pubkey : associatedTokenPublicKey , isWritable : false , isSigner : false , } ) ; } const userPositions = new Keypair ( ) ; const initializeUserAccountIx = await this . program . instruction . initializeUser ( userAccountNonce , optionalAccounts , { accounts : { user : userPublicKey , authority : this . wallet . publicKey , rent : anchor . web3 . SYSVAR_RENT_PUBKEY , systemProgram : anchor . web3 . SystemProgram . programId , userPositions : userPositions . publicKey , state : await this . getStatePublicKey ( ) , } , remainingAccounts : remainingAccounts , } ) ; return [ userPositions , userPublicKey , initializeUserAccountIx ] ; } userAccountPublicKey ? : PublicKey ; returns public async getUserAccountPublicKey ( ) { if ( this . userAccountPublicKey ) { return this . userAccountPublicKey ; } this . userAccountPublicKey = await getUserAccountPublicKey ( this . program . programId , this . wallet . publicKey ) ; return this . userAccountPublicKey ; } userAccount ? : UserAccount ; public async getUserAccount ( ) { if ( this . userAccount ) { return this . userAccount ; } this . userAccount = ( await this . program . account . user . fetch ( await this . getUserAccountPublicKey ( ) ) ) as UserAccount ; return this . userAccount ; } public async depositCollateral ( amount , collateralAccountPublicKey , userPositionsAccountPublicKey ? ) { const depositCollateralIx = await this . getDepositCollateralInstruction ( amount , collateralAccountPublicKey , userPositionsAccountPublicKey ) ; const tx = new Transaction ( ) . add ( depositCollateralIx ) ; return await this . txSender . send ( tx ) ; } async getDepositCollateralInstruction ( amount , collateralAccountPublicKey , userPositionsAccountPublicKey ? ) { const userAccountPublicKey = await this . getUserAccountPublicKey ( ) ; if ( ! userPositionsAccountPublicKey ) { userPositionsAccountPublicKey = ( await this . getUserAccount ( ) ) . positions ; } const state = this . getStateAccount ( ) ; return await this . program . instruction . depositCollateral ( amount , { accounts : { state : await this . getStatePublicKey ( ) , user : userAccountPublicKey , collateralVault : state . collateralVault , userCollateralAccount : collateralAccountPublicKey , authority : this . wallet . publicKey , tokenProgram : TOKEN_PROGRAM_ID , markets : state . markets , fundingPaymentHistory : state . fundingPaymentHistory , depositHistory : state . depositHistory , userPositions : userPositionsAccountPublicKey , } , } ) ; } param amount param collateralAccountPublicKey returns public async initializeUserAccountAndDepositCollateral ( amount , collateralAccountPublicKey ) { const [ userPositionsAccount , userAccountPublicKey , initializeUserAccountIx , ] = await this . getInitializeUserInstructions ( ) ; const depositCollateralIx = await this . getDepositCollateralInstruction ( amount , collateralAccountPublicKey , userPositionsAccount . publicKey ) ; const tx = new Transaction ( ) . add ( initializeUserAccountIx ) . add ( depositCollateralIx ) ; const txSig = await this . program . provider . send ( tx , [ userPositionsAccount ] ) ; return [ txSig , userAccountPublicKey ] ; } public async initializeUserAccountForDevnet ( mockUSDCFaucet , amount ) { const [ associateTokenPublicKey , createAssociatedAccountIx , mintToIx ] = await mockUSDCFaucet . createAssociatedTokenAccountAndMintToInstructions ( this . wallet . publicKey , amount ) ; const [ userPositionsAccount , userAccountPublicKey , initializeUserAccountIx , ] = await this . getInitializeUserInstructions ( ) ; const depositCollateralIx = await this . getDepositCollateralInstruction ( amount , associateTokenPublicKey , userPositionsAccount . publicKey ) ; const tx = new Transaction ( ) . add ( createAssociatedAccountIx ) . add ( mintToIx ) . add ( initializeUserAccountIx ) . add ( depositCollateralIx ) ; const txSig = await this . program . provider . send ( tx , [ userPositionsAccount ] ) ; return [ txSig , userAccountPublicKey ] ; } public async deleteUser ( ) { const userAccountPublicKey = await this . getUserAccountPublicKey ( ) ; const user = await this . program . account . user . fetch ( userAccountPublicKey ) ; const deleteUserTx = await this . program . transaction . deleteUser ( { accounts : { user : userAccountPublicKey , userPositions : user . positions , authority : this . wallet . publicKey , } , } ) ; return this . txSender . send ( deleteUserTx , [ ] , this . opts ) ; } public async withdrawCollateral ( amount , collateralAccountPublicKey ) { return this . txSender . send ( wrapInTx ( await this . getWithdrawCollateralIx ( amount , collateralAccountPublicKey ) ) , [ ] , this . opts ) ; } public async getWithdrawCollateralIx ( amount , collateralAccountPublicKey ) { const userAccountPublicKey = await this . getUserAccountPublicKey ( ) ; const user = await this . program . account . user . fetch ( userAccountPublicKey ) ; const state = this . getStateAccount ( ) ; return await this . program . instruction . withdrawCollateral ( amount , { accounts : { state : await this . getStatePublicKey ( ) , user : userAccountPublicKey , collateralVault : state . collateralVault , collateralVaultAuthority : state . collateralVaultAuthority , insuranceVault : state . insuranceVault , insuranceVaultAuthority : state . insuranceVaultAuthority , userCollateralAccount : collateralAccountPublicKey , authority : this . wallet . publicKey , tokenProgram : TOKEN_PROGRAM_ID , markets : state . markets , userPositions : user . positions , fundingPaymentHistory : state . fundingPaymentHistory , depositHistory : state . depositHistory , } , } ) ; } public async openPosition ( direction , amount , marketIndex , limitPrice ? , discountToken ? , referrer ? ) { return await this . txSender . send ( wrapInTx ( await this . getOpenPositionIx ( direction , amount , marketIndex , limitPrice , discountToken , referrer ) ) , [ ] , this . opts ) ; } public async getOpenPositionIx ( direction , amount , marketIndex , limitPrice ? , discountToken ? , referrer ? ) { const userAccountPublicKey = await this . getUserAccountPublicKey ( ) ; const userAccount = await this . getUserAccount ( ) ; if ( limitPrice == undefined ) { limitPrice = new BN ( 0 ) ; } const optionalAccounts = { discountToken : false , referrer : false , } ; const remainingAccounts = [ ] ; if ( discountToken ) { optionalAccounts . discountToken = true ; remainingAccounts . push ( { pubkey : discountToken , isWritable : false , isSigner : false , } ) ; } if ( referrer ) { optionalAccounts . referrer = true ; remainingAccounts . push ( { pubkey : referrer , isWritable : true , isSigner : false , } ) ; } const priceOracle = this . getMarketsAccount ( ) . markets [ marketIndex . toNumber ( ) ] . amm . oracle ; const state = this . getStateAccount ( ) ; return await this . program . instruction . openPosition ( direction , amount , marketIndex , limitPrice , optionalAccounts , { accounts : { state : await this . getStatePublicKey ( ) , user : userAccountPublicKey , authority : this . wallet . publicKey , markets : state . markets , userPositions : userAccount . positions , tradeHistory : state . tradeHistory , fundingPaymentHistory : state . fundingPaymentHistory , fundingRateHistory : state . fundingRateHistory , oracle : priceOracle , } , remainingAccounts : remainingAccounts , } ) ; } /**
     * Close an entire position. If you want to reduce a position, use the  openPosition method in the opposite direction of the current position.
     *  param marketIndex param discountToken param referrer returns public async closePosition ( marketIndex , discountToken ? , referrer ? ) { return await this . txSender . send ( wrapInTx ( await this . getClosePositionIx ( marketIndex , discountToken , referrer ) ) , [ ] , this . opts ) ; } public async getClosePositionIx ( marketIndex , discountToken ? , referrer ? ) { const userAccountPublicKey = await this . getUserAccountPublicKey ( ) ; const userAccount = await this . getUserAccount ( ) ; const priceOracle = this . getMarketsAccount ( ) . markets [ marketIndex . toNumber ( ) ] . amm . oracle ; const optionalAccounts = { discountToken : false , referrer : false , } ; const remainingAccounts = [ ] ; if ( discountToken ) { optionalAccounts . discountToken = true ; remainingAccounts . push ( { pubkey : discountToken , isWritable : false , isSigner : false , } ) ; } if ( referrer ) { optionalAccounts . referrer = true ; remainingAccounts . push ( { pubkey : referrer , isWritable : true , isSigner : false , } ) ; } const state = this . getStateAccount ( ) ; return await this . program . instruction . closePosition ( marketIndex , optionalAccounts , { accounts : { state : await this . getStatePublicKey ( ) , user : userAccountPublicKey , authority : this . wallet . publicKey , markets : state . markets , userPositions : userAccount . positions , tradeHistory : state . tradeHistory , fundingPaymentHistory : state . fundingPaymentHistory , fundingRateHistory : state . fundingRateHistory , oracle : priceOracle , } , remainingAccounts : remainingAccounts , } ) ; } public async liquidate ( liquidateeUserAccountPublicKey ) { return this . txSender . send ( wrapInTx ( await this . getLiquidateIx ( liquidateeUserAccountPublicKey ) ) , [ ] , this . opts ) ; } public async getLiquidateIx ( liquidateeUserAccountPublicKey ) { const userAccountPublicKey = await this . getUserAccountPublicKey ( ) ; const liquidateeUserAccount = await this . program . account . user . fetch ( liquidateeUserAccountPublicKey ) ; const liquidateePositions = await this . program . account . userPositions . fetch ( liquidateeUserAccount . positions ) ; const markets = this . getMarketsAccount ( ) ; const remainingAccounts = [ ] ; for ( const position of liquidateePositions . positions ) { if ( ! position . baseAssetAmount . eq ( new BN ( 0 ) ) ) { const market = markets . markets [ position . marketIndex . toNumber ( ) ] ; remainingAccounts . push ( { pubkey : market . amm . oracle , isWritable : false , isSigner : false , } ) ; } } const state = this . getStateAccount ( ) ; return await this . program . instruction . liquidate ( { accounts : { state : await this . getStatePublicKey ( ) , authority : this . wallet . publicKey , user : liquidateeUserAccountPublicKey , liquidator : userAccountPublicKey , collateralVault : state . collateralVault , collateralVaultAuthority : state . collateralVaultAuthority , insuranceVault : state . insuranceVault , insuranceVaultAuthority : state . insuranceVaultAuthority , tokenProgram : TOKEN_PROGRAM_ID , markets : state . markets , userPositions : liquidateeUserAccount . positions , tradeHistory : state . tradeHistory , liquidationHistory : state . liquidationHistory , fundingPaymentHistory : state . fundingPaymentHistory , } , remainingAccounts : remainingAccounts , } ) ; } public async updateFundingRate ( oracle , marketIndex ) { return this . txSender . send ( wrapInTx ( await this . getUpdateFundingRateIx ( oracle , marketIndex ) ) , [ ] , this . opts ) ; } public async getUpdateFundingRateIx ( oracle , marketIndex ) { const state = this . getStateAccount ( ) ; return await this . program . instruction . updateFundingRate ( marketIndex , { accounts : { state : await this . getStatePublicKey ( ) , markets : state . markets , oracle : oracle , fundingRateHistory : state . fundingRateHistory , } , } ) ; } public async settleFundingPayment ( userAccount , userPositionsAccount ) { return this . txSender . send ( wrapInTx ( await this . getSettleFundingPaymentIx ( userAccount , userPositionsAccount ) ) , [ ] , this . opts ) ; } public async getSettleFundingPaymentIx ( userAccount , userPositionsAccount ) { const state = this . getStateAccount ( ) ; return await this . program . instruction . settleFundingPayment ( { accounts : { state : await this . getStatePublicKey ( ) , markets : state . markets , user : userAccount , userPositions : userPositionsAccount , fundingPaymentHistory : state . fundingPaymentHistory , } , } ) ; } public triggerEvent ( eventName , data ? ) { this . eventEmitter . emit ( eventName , data ) ; } }