import { State as NitroState , SignedState as NitroSignedState , Outcome as NitroOutcome , AllocationItem as NitroAllocationItem , signState as signNitroState , hashState as hashNitroState , getStateSignerAddress as getNitroSignerAddress , getChannelId , convertAddressToBytes32 } from '@statechannels/nitro-protocol' ; import * as _ from 'lodash' ; import { Wallet , utils } from 'ethers' ; import { State , ChannelConstants , Outcome , AllocationItem , SignedState , Destination , SimpleAllocation , SignatureEntry , makeAddress , Address , Hashed } from './types' ; import { BN } from './bignumber' ; import { makeDestination } from './' ; export function toNitroState ( state ) { const { channelNonce , participants , chainId } = state ; const channel = { channelNonce , chainId , participants : participants . map ( x => x . signingAddress ) } ; return { ... _ . pick ( state , 'appData' , 'isFinal' , 'challengeDuration' , 'appDefinition' , 'turnNum' ) , outcome : convertToNitroOutcome ( state . outcome ) , channel } ; } export function fromNitroSignedState ( nitroSignedState ) { const state = fromNitroState ( nitroSignedState . state ) ; const signature = utils . joinSignature ( nitroSignedState . signature ) ; const signatures = [ { signature , signer : getSignerAddress ( state , signature ) } ] ; return { ... state , signatures } ; } export function fromNitroState ( state ) { const { appData , isFinal , outcome , challengeDuration , appDefinition , channel , turnNum } = state ; return { appDefinition : makeAddress ( appDefinition ) , isFinal , appData , outcome : fromNitroOutcome ( outcome ) , turnNum : turnNum , challengeDuration : challengeDuration , channelNonce : Number ( channel . channelNonce ) , chainId : channel . chainId , participants : channel . participants . map ( x => ( { signingAddress : makeAddress ( x ) , participantId : x , destination : x . padStart ( 64 , '0' ) as Destination } ) ) } ; } export function toNitroSignedState ( signedState ) { const state = toNitroState ( signedState ) ; const { signatures } = signedState ; return signatures . map ( sig => ( { state , signature : utils . splitSignature ( sig . signature ) } ) ) ; } export function calculateChannelId ( channelConstants ) { const { chainId , channelNonce , participants } = channelConstants ; const addresses = participants . map ( p => p . signingAddress ) ; return getChannelId ( { chainId , channelNonce , participants : addresses } ) ; } export function createSignatureEntry ( state , privateKey ) { const { address } = new Wallet ( privateKey ) ; const nitroState = toNitroState ( state ) ; const { signature } = signNitroState ( nitroState , privateKey ) ; return { signature : utils . joinSignature ( signature ) , signer : makeAddress ( address ) } ; } export function signState ( state , privateKey ) { const nitroState = toNitroState ( state ) ; const { signature } = signNitroState ( nitroState , privateKey ) ; return utils . joinSignature ( signature ) ; } export function hashState ( state ) { const nitroState = toNitroState ( state ) ; return hashNitroState ( nitroState ) ; } export function getSignerAddress ( state , signature ) { const nitroState = toNitroState ( state ) ; return makeAddress ( getNitroSignerAddress ( { state : nitroState , signature : utils . splitSignature ( signature ) } ) ) ; } export function statesEqual ( left , right ) { return hashState ( left ) === hashState ( right ) ; } function simpleAllocationsEqual ( left , right ) { return ( left . asset === right . asset && left . allocationItems . length === right . allocationItems . length && _ . every ( left . allocationItems , ( value , index ) => value . destination === right . allocationItems [ index ] . destination && BN . eq ( value . amount , right . allocationItems [ index ] . amount ) ) ) ; } export function outcomesEqual ( left , right ? ) { if ( left . type === 'SimpleAllocation' && right ?. type === 'SimpleAllocation' ) { return simpleAllocationsEqual ( left , right ) ; } if ( left . type === 'SimpleGuarantee' && right ?. type === 'SimpleGuarantee' ) { return _ . isEqual ( left , right ) ; } if ( left . type === 'MixedAllocation' && right ?. type === 'MixedAllocation' ) { return ( left . simpleAllocations . length === right . simpleAllocations . length && _ . every ( left . simpleAllocations , ( _ , index ) => simpleAllocationsEqual ( left . simpleAllocations [ index ] , right . simpleAllocations [ index ] ) ) ) ; } return false ; } export const firstState = ( outcome , { channelNonce , chainId , challengeDuration , appDefinition , participants } , appData ? ) : State => ( { appData : appData || '0x' , isFinal : false , turnNum : 0 , chainId : chainId || '0x01' , channelNonce , challengeDuration , appDefinition , participants , outcome } ) ; function convertToNitroAllocationItems ( allocationItems ) { return allocationItems . map ( a => ( { amount : a . amount , destination : a . destination . length === 42 ? convertAddressToBytes32 ( a . destination ) : a . destination } ) ) ; } function convertFromNitroAllocationItems ( allocationItems ) { return allocationItems . map ( a => ( { amount : BN . from ( a . amount ) , destination : makeDestination ( a . destination ) } ) ) ; } export function convertToNitroOutcome ( outcome ) { switch ( outcome . type ) { case 'SimpleAllocation' : return [ { asset : outcome . asset , allocationItems : convertToNitroAllocationItems ( outcome . allocationItems ) } ] ; case 'SimpleGuarantee' : return [ { asset : outcome . asset , guarantee : { targetChannelId : outcome . targetChannelId , destinations : outcome . destinations } } ] ; case 'MixedAllocation' : console . warn ( 'NOTE: MixedAllocation is using 0th-indexed allocation only' ) ; return outcome . simpleAllocations . map ( convertToNitroOutcome ) [ 0 ] ; } } export function fromNitroOutcome ( outcome ) { const [ singleOutcomeItem ] = outcome ; if ( typeof singleOutcomeItem [ 'allocationItems' ] !== 'undefined' ) { return { type : 'SimpleAllocation' , asset : makeAddress ( singleOutcomeItem . asset ) , allocationItems : convertFromNitroAllocationItems ( singleOutcomeItem [ 'allocationItems' ] ) } ; } if ( typeof singleOutcomeItem [ 'guarantee' ] !== 'undefined' ) { return { type : 'SimpleGuarantee' , asset : makeAddress ( singleOutcomeItem . asset ) , targetChannelId : singleOutcomeItem [ 'guarantee' ] . targetChannelId , destinations : singleOutcomeItem [ 'guarantee' ] . destinations } ; } return { type : 'MixedAllocation' , simpleAllocations : [ ] } ; } export function nextState ( state , outcome ) { if ( state . outcome . type !== outcome . type ) { throw new Error ( 'Attempting to change outcome type' ) ; } return { ... state , turnNum : state . turnNum + 1 , outcome } ; } export const addHash = < T extends State = State > ( s ) : T & Hashed => ( { ... s , stateHash : hashState ( s ) } ) ;