import { AppInstanceJson , ConditionalTransferTypes , EventNames , GenericConditionalTransferAppState , GraphBatchedTransferAppAction , GraphSignedTransferAppAction , HashLockTransferAppAction , PublicParams , PublicResults , RequireOnlineApps , SimpleLinkedTransferAppAction , SimpleSignedTransferAppAction , } from "@connext/types" ; import { stringify } from "@connext/utils" ; import { BigNumber } from "ethers" ; import { AbstractController } from "./AbstractController" ; export class ResolveTransferController extends AbstractController { public resolveTransfer = async ( params ) : Promise < PublicResults . ResolveCondition > => { const { conditionType , paymentId } = params ; this . log . info ( `[${ paymentId }] resolveTransfer started: ${ stringify ( params ) }` ) ; const appDefinition = this . connext . appRegistry . find ( ( app ) => app . name === conditionType ) . appDefinitionAddress ; const findApp = ( apps ) => { return apps . find ( ( app ) => { return ( app . appDefinition === appDefinition && app . meta . paymentId === paymentId && ( app . latestState as GenericConditionalTransferAppState ) . coinTransfers [ 1 ] . to === this . connext . signerAddress ) ; } ) ; } ; const emitFailureEvent = ( error ) => { this . connext . emit ( EventNames . CONDITIONAL_TRANSFER_FAILED_EVENT , { error : error . message , paymentId , type : conditionType , } ) ; return ; } ; if ( ! this . hasSecret ( params ) ) { const error = new Error ( `Cannot resolve payment without providing a secret. Params: ${ stringify ( params ) }` ) ; emitFailureEvent ( error ) ; throw error ; } let appIdentityHash ; let amount ; let assetId ; let meta ; let existingReceiverApp = findApp ( await this . connext . getAppInstances ( ) ) ; if ( existingReceiverApp ) { appIdentityHash = existingReceiverApp . identityHash ; this . log . debug ( `[${ paymentId }] Found existing transfer app, proceeding with ${ appIdentityHash }: ${ JSON . stringify ( existingReceiverApp . latestState ) }` ) ; amount = ( existingReceiverApp . latestState as GenericConditionalTransferAppState ) . coinTransfers [ 0 ] . amount ; assetId = existingReceiverApp . outcomeInterpreterParameters [ "tokenAddress" ] ; meta = existingReceiverApp . meta || { } ; } else { try { const requireOnline = RequireOnlineApps . includes ( conditionType ) ; existingReceiverApp = findApp ( await this . connext . getAppInstances ( ) ) ; if ( ! existingReceiverApp ) { if ( requireOnline ) { throw new Error ( `Receiver app has not been installed, channel: ${ stringify ( await this . connext . getStateChannel ( ) ) }` ) ; } this . log . debug ( `[${ paymentId }] Requesting node install app` ) ; const installRes = await this . connext . node . installConditionalTransferReceiverApp ( paymentId , conditionType ) ; appIdentityHash = installRes . appIdentityHash ; amount = installRes . amount ; assetId = installRes . assetId ; meta = installRes . meta ; if ( conditionType === ConditionalTransferTypes . LinkedTransfer && installRes . meta . recipient ) { this . log . warn ( `Returning early from install, unlock will happen through listener` ) ; return ; } } else { appIdentityHash = existingReceiverApp . identityHash ; this . log . debug ( `[${ paymentId }] Found existing transfer app, proceeding with ${ appIdentityHash }: ${ JSON . stringify ( existingReceiverApp . latestState ) }` ) ; amount = ( existingReceiverApp . latestState as GenericConditionalTransferAppState ) . coinTransfers [ 0 ] . amount ; assetId = existingReceiverApp . outcomeInterpreterParameters [ "tokenAddress" ] ; meta = existingReceiverApp . meta ; } } catch ( e ) { emitFailureEvent ( e ) ; throw e ; } } if ( ! appIdentityHash || ! amount || ! assetId || ! meta ) { const message = `Failed to install receiver app properly for ${ paymentId }, missing one of:\n` + `   - appIdentityHash: ${ appIdentityHash }\n` + `   - amount: ${ stringify ( amount ) }\n` + `   - assetId: ${ assetId }\n` + `   - meta: ${ stringify ( meta ) }` ; const e = { message } ; emitFailureEvent ( e as any ) ; throw new Error ( message ) ; } this . log . info ( `[${ paymentId }] Taking action on receiver app: ${ appIdentityHash }` ) ; try { let action ; switch ( conditionType ) { case ConditionalTransferTypes . HashLockTransfer : { const { preImage } = params as PublicParams . ResolveHashLockTransfer ; action = preImage && ( { preImage } as HashLockTransferAppAction ) ; break ; } case ConditionalTransferTypes . GraphBatchedTransfer : { const { requestCID , responseCID , consumerSignature , attestationSignature , totalPaid , } = params as PublicParams . ResolveGraphBatchedTransfer ; action = requestCID && responseCID && consumerSignature && attestationSignature && totalPaid && ( { requestCID , responseCID , consumerSignature , attestationSignature , totalPaid , } as GraphBatchedTransferAppAction ) ; break ; } case ConditionalTransferTypes . GraphTransfer : { const { responseCID , signature } = params as PublicParams . ResolveGraphTransfer ; action = responseCID && signature && ( { responseCID , signature , } as GraphSignedTransferAppAction ) ; break ; } case ConditionalTransferTypes . SignedTransfer : { const { data , signature } = params as PublicParams . ResolveSignedTransfer ; action = data && signature && ( { data , signature } as SimpleSignedTransferAppAction ) ; break ; } case ConditionalTransferTypes . LinkedTransfer : { const { preImage } = params as PublicParams . ResolveLinkedTransfer ; action = preImage && ( { preImage } as SimpleLinkedTransferAppAction ) ; break ; } default : { const c = conditionType ; this . log . error ( `[${ paymentId }] Unsupported conditionType ${ c }` ) ; } } this . log . info ( `[${ paymentId }] Uninstalling transfer app with action ${ appIdentityHash }` ) ; await this . connext . uninstallApp ( appIdentityHash , action ) ; this . log . info ( `[${ paymentId }] Finished uninstalling transfer app ${ appIdentityHash }` ) ; } catch ( e ) { emitFailureEvent ( e ) ; throw e ; } const sender = meta . sender ; const result = { amount , appIdentityHash , assetId , sender , meta , paymentId , } ; this . log . info ( `[${ paymentId }] resolveCondition complete: ${ stringify ( result ) }` ) ; return result ; } ; private hasSecret ( params ) { const { conditionType , paymentId } = params ; switch ( conditionType ) { case ConditionalTransferTypes . HashLockTransfer : { const { preImage } = params as PublicParams . ResolveHashLockTransfer ; return ! ! preImage ; } case ConditionalTransferTypes . GraphTransfer : { const { responseCID , signature } = params as PublicParams . ResolveGraphTransfer ; return ! ! responseCID && ! ! signature ; } case ConditionalTransferTypes . GraphBatchedTransfer : { const { responseCID , consumerSignature , attestationSignature , } = params as PublicParams . ResolveGraphBatchedTransfer ; return ! ! responseCID && ! ! consumerSignature && ! ! attestationSignature ; } case ConditionalTransferTypes . SignedTransfer : { const { data , signature } = params as PublicParams . ResolveSignedTransfer ; return ! ! data && ! ! signature ; } case ConditionalTransferTypes . LinkedTransfer : { const { preImage } = params as PublicParams . ResolveLinkedTransfer ; return ! ! preImage ; } default : { const c = conditionType ; this . log . error ( `[${ paymentId }] Unsupported conditionType ${ c }` ) ; } } throw new Error ( `Invalid condition type: ${ conditionType }` ) ; } }