import {Either, left, right, chain, map} from 'fp-ts/lib/Either'; import {StateVariables, Outcome} from '@statechannels/wallet-core'; import {pipe} from 'fp-ts/lib/function'; import {ChannelId} from '@statechannels/client-api-schema'; import {SignState, signState} from '../protocols/actions'; import {ChannelState, ChannelStateWithSupported} from '../protocols/state'; import {EngineError, Values} from '../errors/engine-error'; import {hasSupportedState, isMyTurn} from './helpers'; type StepResult = Either; type UpdateChannelResult = Either; export interface UpdateChannelHandlerParams { channelId: ChannelId; outcome: Outcome; appData: string; } export class UpdateChannelError extends EngineError { readonly type = EngineError.errors.UpdateChannelError; static readonly reasons = { channelNotFound: 'channel not found', invalidLatestState: 'must have latest state', notInRunningStage: 'channel must be in running state', notMyTurn: 'it is not my turn', } as const; constructor( reason: Values, public readonly data: any = undefined ) { super(reason); } } // The helper functions should be factored out, tested, and reusable const ensureSupportedStateExists = ( cs: ChannelState ): Either => hasSupportedState(cs) ? right(cs) : left(new UpdateChannelError(UpdateChannelError.reasons.invalidLatestState)); function ensureItIsMyTurn(cs: ChannelStateWithSupported): StepResult { if (isMyTurn(cs)) return right(cs); return left(new UpdateChannelError(UpdateChannelError.reasons.notMyTurn)); } function hasRunningTurnNumber(cs: ChannelStateWithSupported): StepResult { if (cs.supported.turnNum < 3) return left(new UpdateChannelError(UpdateChannelError.reasons.notInRunningStage)); return right(cs); } const incrementTurnNumber = (args: UpdateChannelHandlerParams) => ( cs: ChannelStateWithSupported ): StateVariables => ({ ...args, turnNum: cs.supported.turnNum + 1, isFinal: false, }); // END helper functions // TODO: check if the channel is funded and that no challenge exists once that data is part of the ChannelState export function updateChannel( args: UpdateChannelHandlerParams, channelState: ChannelState ): UpdateChannelResult { const signStateVars = (sv: StateVariables): SignState => signState({...sv, channelId: args.channelId}); return pipe( channelState, ensureSupportedStateExists, chain(hasRunningTurnNumber), chain(ensureItIsMyTurn), map(incrementTurnNumber(args)), map(signStateVars) ); }