import { EventData } from 'web3-eth-contract' ; import Web3 from 'web3' ; import { Big } from '@goldenagellc/web3-blocks' ; import { CTokenSymbol , cTokenSymbols } from './types/CTokens' ; import { CToken } from './contracts/CToken' ; import StatefulBorrower from './StatefulBorrower' ; import StatefulComptroller from './StatefulComptroller' ; import PriceLedger from './PriceLedger' ; import ILiquidationCandidate from './types/ILiquidationCandidate' ; export default class StatefulBorrowers { private readonly provider : Web3 ; private readonly cTokens : { [ _ in CTokenSymbol ] : CToken ; } ; private readonly borrowers : { [ address ] : StatefulBorrower ; } = { } ; private readonly borrowIndices : { - readonly [ _ in CTokenSymbol ] : Big ; } = { cBAT : new Big ( '0' ) , cCOMP : new Big ( '0' ) , cDAI : new Big ( '0' ) , cETH : new Big ( '0' ) , cREP : new Big ( '0' ) , cSAI : new Big ( '0' ) , cUNI : new Big ( '0' ) , cUSDC : new Big ( '0' ) , cUSDT : new Big ( '0' ) , cLINK : new Big ( '0' ) , cTUSD : new Big ( '0' ) , cWBTC : new Big ( '0' ) , cWBTC2 : new Big ( '0' ) , cZRX : new Big ( '0' ) , } ; private lastScan : number = Date . now ( ) ; constructor ( provider , cTokens ) { this . provider = provider ; this . cTokens = cTokens ; } public async init ( ) { const block = await this . provider . eth . getBlockNumber ( ) ; await Promise . all ( this . fetchBorrowIndices ( block ) ) ; this . subscribe ( block ) ; } public async push ( addresses ) { const block = await this . provider . eth . getBlockNumber ( ) ; const promises = < Promise < void > [ ] > [ ] ; addresses . forEach ( ( address ) => { this . borrowers [ address ] = new StatefulBorrower ( address , this . provider , this . cTokens ) ; promises . push ( ... this . borrowers [ address ] . fetchAll ( block ) ) ; } ) ; return Promise . all ( promises ) ; } public async scan ( comptroller , priceLedger ) { const exchangeRateArray = await Promise . all ( this . fetchExchangeRates ( ) ) ; const exchangeRates = Object . fromEntries ( cTokenSymbols . map ( ( symbol , i ) => [ symbol , exchangeRateArray [ i ] ] ) ) as { [ _ in CTokenSymbol ] : Big ; } ; const candidates = [ ] ; Object . keys ( this . borrowers ) . forEach ( ( address ) => { const borrower = this . borrowers [ address ] ; const info = borrower . expectedRevenue ( comptroller , priceLedger , exchangeRates , this . borrowIndices ) ; if ( info !== null && info . health . lt ( '1' ) ) { const postable = priceLedger . getPostableFormat ( info . symbols , info . edges ) ; if ( postable === null ) return ; candidates . push ( { address : address , repayCToken : info . repayCToken , seizeCToken : info . seizeCToken , pricesToReport : postable , expectedRevenue : info . revenueETH . div ( '1e+6' ) . toNumber ( ) , } ) ; } } ) ; this . lastScan = Date . now ( ) ; return candidates ; } private fetchBorrowIndices ( block ) { return cTokenSymbols . map ( async ( symbol ) => { this . borrowIndices [ symbol ] = await this . cTokens [ symbol ] . borrowIndex ( ) ( this . provider , block ) ; } ) ; } private fetchExchangeRates ( ) { return cTokenSymbols . map ( ( symbol ) => this . cTokens [ symbol ] . exchangeRateStored ( ) ( this . provider ) ) ; } private subscribe ( block ) { cTokenSymbols . forEach ( ( symbol ) => { const subscribeTo = this . cTokens [ symbol ] . bindTo ( this . provider ) . subscribeTo ; const respondToMint = ( ev ) => { const minter = ev . returnValues . minter ; if ( minter in this . borrowers ) this . borrowers [ minter ] . onMint ( ev ) ; } ; const respondToRedeem = ( ev ) => { const redeemer = ev . returnValues . redeemer ; if ( redeemer in this . borrowers ) this . borrowers [ redeemer ] . onRedeem ( ev ) ; } ; const respondToBorrow = ( ev ) => { const borrower = ev . returnValues . borrower ; if ( borrower in this . borrowers ) this . borrowers [ borrower ] . onBorrow ( ev ) ; else { console . log ( `Added borrower ${ borrower . slice ( 0 , 6 ) } on-the-fly.` ) ; this . push ( [ borrower ] ) ; } } ; const respondToRepay = ( ev ) => { const borrower = ev . returnValues . borrower ; if ( borrower in this . borrowers ) this . borrowers [ borrower ] . onRepayBorrow ( ev ) ; } ; const respondToLiquidate = ( ev ) => { const borrower = ev . returnValues . borrower ; if ( borrower in this . borrowers ) this . borrowers [ borrower ] . onLiquidateBorrow ( ev ) ; } ; const respondToTransfer = ( ev ) => { const from = ev . returnValues . from ; if ( from in this . borrowers ) this . borrowers [ from ] . onTransfer ( ev ) ; const to = ev . returnValues . to ; if ( to in this . borrowers ) this . borrowers [ to ] . onTransfer ( ev ) ; } ; subscribeTo . Mint ( block ) . on ( 'data' , respondToMint ) . on ( 'changed' , respondToMint ) . on ( 'error' , console . error ) ; subscribeTo . Redeem ( block ) . on ( 'data' , respondToRedeem ) . on ( 'changed' , respondToRedeem ) . on ( 'error' , console . error ) ; subscribeTo . Borrow ( block ) . on ( 'data' , respondToBorrow ) . on ( 'changed' , respondToBorrow ) . on ( 'error' , console . error ) ; subscribeTo . RepayBorrow ( block ) . on ( 'data' , respondToRepay ) . on ( 'changed' , respondToRepay ) . on ( 'error' , console . error ) ; subscribeTo . LiquidateBorrow ( block ) . on ( 'data' , ( ev ) => { console . log ( `Last scan occured ${ Date . now ( ) - this . lastScan }ms before liquidation.` ) ; respondToLiquidate ( ev ) ; } ) . on ( 'changed' , respondToLiquidate ) . on ( 'error' , console . error ) ; subscribeTo . Transfer ( block ) . on ( 'data' , respondToTransfer ) . on ( 'changed' , respondToTransfer ) . on ( 'error' , console . error ) ; subscribeTo . AccrueInterest ( block ) . on ( 'data' , ( ev ) => { this . borrowIndices [ symbol ] = new Big ( ev . returnValues . borrowIndex ) ; } ) . on ( 'error' , console . error ) ; } ) ; } }