import { DEFAULT_APP_TIMEOUT , SWAP_STATE_TIMEOUT } from "@connext/apps" ; import { DefaultApp , EventNames , MethodParams , PublicParams , PublicResults , SimpleSwapAppState , SimpleTwoPartySwapAppName , Address , CONVENTION_FOR_ETH_ASSET_ID , } from "@connext/types" ; import { calculateExchangeWad , delay , getAddressFromAssetId , getAddressError , notGreaterThan , notLessThanOrEqualTo , notPositive , toBN , stringify , fromWad , toWad , } from "@connext/utils" ; import { ERC20 } from "@connext/contracts" ; import { BigNumber , constants , Contract } from "ethers" ; import { AbstractController } from "./AbstractController" ; const { AddressZero , Zero } = constants ; export class SwapController extends AbstractController { public async swap ( params ) { this . log . info ( `swap started: ${ stringify ( params ) }` ) ; const amount = toBN ( params . amount ) ; const { swapRate } = params ; const toTokenAddress = getAddressFromAssetId ( params . toAssetId ) ; const fromTokenAddress = getAddressFromAssetId ( params . fromAssetId ) ; const preSwapFromBal = await this . connext . getFreeBalance ( fromTokenAddress ) ; const userBal = preSwapFromBal [ this . connext . signerAddress ] ; this . throwIfAny ( getAddressError ( fromTokenAddress ) , getAddressError ( toTokenAddress ) , notLessThanOrEqualTo ( amount , userBal ) , notGreaterThan ( amount , Zero ) , notPositive ( toWad ( swapRate ) ) , notLessThanOrEqualTo ( amount , toBN ( preSwapFromBal [ this . connext . signerAddress ] ) ) ) ; const network = await this . ethProvider . getNetwork ( ) ; const appInfo = ( await this . connext . getAppRegistry ( { name : SimpleTwoPartySwapAppName , chainId : network . chainId , } ) ) as DefaultApp ; this . log . debug ( `Requesting collateral` ) ; const tx = await this . connext . requestCollateral ( toTokenAddress ) as any ; if ( tx && tx . hash ) { await this . ethProvider . waitForTransaction ( tx . hash ) ; await this . connext . waitFor ( EventNames . UNINSTALL_EVENT , 10000 ) ; } else { await delay ( 5000 ) ; } this . log . debug ( `Installing swap app` ) ; const appIdentityHash = await this . swapAppInstall ( amount , toTokenAddress , fromTokenAddress , swapRate , appInfo ) ; this . log . debug ( `Swap app installed: ${ appIdentityHash }, uninstalling` ) ; try { await this . connext . uninstallApp ( appIdentityHash ) ; } catch ( e ) { const msg = `Failed to uninstall swap: ${ e . stack || e . message }` ; this . log . error ( msg ) ; throw new Error ( msg ) ; } const res = await this . connext . getChannel ( ) ; this . log . info ( `swap from ${ fromTokenAddress } to ${ toTokenAddress } completed: ${ stringify ( res ) }` ) ; return res as PublicResults . Swap ; } private swapAppInstall = async ( initiatorDeposit , toTokenAddress , fromTokenAddress , swapRate , appInfo ) : Promise < string > => { const getDecimals = async ( tokenAddress ) : Promise < number > => { let decimals = 18 ; if ( tokenAddress !== CONVENTION_FOR_ETH_ASSET_ID ) { try { const token = new Contract ( tokenAddress , ERC20 . abi , this . connext . ethProvider ) ; decimals = await token . functions . decimals ( ) ; this . log . info ( `Retrieved decimals for ${ tokenAddress } from token contract: ${ decimals }` ) ; } catch ( error ) { this . log . warn ( `Could not retrieve decimals from token ${ tokenAddress }, defaulting to 18` ) ; } } return decimals ; } ; const fromDecimals = await getDecimals ( fromTokenAddress ) ; const toDecimals = await getDecimals ( toTokenAddress ) ; const responderDeposit = calculateExchangeWad ( initiatorDeposit , fromDecimals , swapRate , toDecimals ) ; this . log . debug ( `Swapping ${ fromWad ( initiatorDeposit , fromDecimals ) } ${ toTokenAddress === AddressZero ? "ETH" : "Tokens" } for ${ fromWad ( responderDeposit , toDecimals ) } ${ fromTokenAddress === AddressZero ? "ETH" : "Tokens" }` ) ; const initialState = { coinTransfers : [ [ { amount : initiatorDeposit , to : this . connext . signerAddress , } , ] , [ { amount : responderDeposit , to : this . connext . nodeSignerAddress , } , ] , ] , } ; const { actionEncoding , appDefinitionAddress : appDefinition , stateEncoding } = appInfo ; const params = { abiEncodings : { actionEncoding , stateEncoding , } , appDefinition , initialState , initiatorDeposit , initiatorDepositAssetId : fromTokenAddress , multisigAddress : this . connext . multisigAddress , outcomeType : appInfo . outcomeType , responderIdentifier : this . connext . nodeIdentifier , responderDeposit , responderDepositAssetId : toTokenAddress , defaultTimeout : DEFAULT_APP_TIMEOUT , stateTimeout : SWAP_STATE_TIMEOUT , } ; this . log . debug ( `Installing app with params: ${ stringify ( params ) }` ) ; const appIdentityHash = await this . proposeAndInstallLedgerApp ( params ) ; return appIdentityHash ; } ; }