import { NetworkContext , Node , SolidityValueType } from "@counterfactual/types" ; import { solidityKeccak256 } from "ethers/utils" ; import { NO_MULTISIG_FOR_APP_INSTANCE_ID , NO_PROPOSED_APP_INSTANCE_FOR_APP_INSTANCE_ID , NO_STATE_CHANNEL_FOR_MULTISIG_ADDR } from "./methods/errors" ; import { AppInstance , AppInstanceProposal , StateChannel , StateChannelJSON } from "./models" ; import { getCreate2MultisigAddress , prettyPrintObject } from "./utils" ; interface SharedData { stateChannelsMap : { [ multisigAddress ] : StateChannelJSON ; } ; commitments : { [ specialHash ] : any [ ] ; } ; withdrawals : { [ multisigAddress ] : Node . MinimalTransaction ; } ; } /**
 * A simple ORM around StateChannels and AppInstances stored using the
 * StoreService.
 */ export class Store { public sharedData : SharedData = { stateChannelsMap : { } , commitments : { } , withdrawals : { } } ; constructor ( private readonly storeService , private readonly storeKeyPrefix ) { } public async connectDB ( ) { this . sharedData = ( await this . storeService . get ( this . storeKeyPrefix ) ) || { stateChannelsMap : { } , commitments : { } , withdrawals : { } } ; } public async persistDB ( ) { await this . storeService . set ( [ { path : this . storeKeyPrefix , value : this . sharedData } ] ) ; } /**
     * Returns an object with the keys being the multisig addresses and the
     * values being `StateChannel` instances.
     */ public async getStateChannelsMap ( ) { return new Map ( Object . values ( this . sharedData . stateChannelsMap ) . map ( StateChannel . fromJson ) . map ( sc => [ sc . multisigAddress , sc ] ) ) ; } param multisigAddress public async getStateChannel ( multisigAddress ) { const stateChannelJson = this . sharedData . stateChannelsMap [ multisigAddress ] ; if ( ! stateChannelJson ) { throw Error ( NO_STATE_CHANNEL_FOR_MULTISIG_ADDR ( multisigAddress ) ) ; } return StateChannel . fromJson ( stateChannelJson ) ; } /**
     * Checks if a StateChannel is in the store
     */ public async hasStateChannel ( multisigAddress ) { return ! ! this . sharedData . stateChannelsMap [ multisigAddress ] ; } param appInstanceId public async getMultisigAddressFromAppInstance ( appInstanceId ) { for ( const sc of ( await this . getStateChannelsMap ( ) ) . values ( ) ) { if ( sc . proposedAppInstances . has ( appInstanceId ) || sc . appInstances . has ( appInstanceId ) || ( sc . hasFreeBalance && sc . freeBalance . identityHash === appInstanceId ) ) { return sc . multisigAddress ; } } throw new Error ( NO_MULTISIG_FOR_APP_INSTANCE_ID ) ; } param stateChannel public async saveStateChannel ( stateChannel ) { this . sharedData . stateChannelsMap [ stateChannel . multisigAddress ] = stateChannel . toJson ( ) ; await this . persistDB ( ) ; } param appInstance public async saveAppInstanceState ( appInstanceId , newState ) { const channel = await this . getChannelFromAppInstanceID ( appInstanceId ) ; const updatedChannel = await channel . setState ( appInstanceId , newState ) ; await this . saveStateChannel ( updatedChannel ) ; } /**
     * Returns a list of proposed `AppInstanceProposals`s.
     */ public async getProposedAppInstances ( ) { return [ ... ( await this . getStateChannelsMap ( ) ) . values ( ) ] . reduce ( ( lst , sc ) => [ ... lst , ... sc . proposedAppInstances . values ( ) ] , [ ] as AppInstanceProposal [ ] ) ; } /**
     * Returns the proposed AppInstance with the specified appInstanceId.
     */ public async getAppInstanceProposal ( appInstanceId ) { const multisigAddress = await this . getMultisigAddressFromAppInstance ( appInstanceId ) ; if ( ! multisigAddress ) { throw new Error ( NO_PROPOSED_APP_INSTANCE_FOR_APP_INSTANCE_ID ( appInstanceId ) ) ; } const stateChannel = await this . getStateChannel ( multisigAddress ) ; if ( ! stateChannel . proposedAppInstances . has ( appInstanceId ) ) { throw new Error ( NO_PROPOSED_APP_INSTANCE_FOR_APP_INSTANCE_ID ( appInstanceId ) ) ; } return stateChannel . proposedAppInstances . get ( appInstanceId ) ! ; } param appInstanceId public async getChannelFromAppInstanceID ( appInstanceId ) { return await this . getStateChannel ( await this . getMultisigAddressFromAppInstance ( appInstanceId ) ) ; } public async getWithdrawalCommitment ( multisigAddress ) { return this . sharedData . withdrawals [ multisigAddress ] ; } public async storeWithdrawalCommitment ( multisigAddress , commitment ) { this . sharedData . withdrawals [ multisigAddress ] = commitment ; await this . persistDB ( ) ; } public async setCommitment ( args , commitment ) { this . sharedData . commitments [ solidityKeccak256 ( [ "address" , "uint256" , "bytes" ] , [ commitment . to , commitment . value , commitment . data ] ) ] = args . concat ( [ commitment ] ) ; await this . persistDB ( ) ; } public async getAppInstance ( appInstanceId ) { const channel = await this . getChannelFromAppInstanceID ( appInstanceId ) ; return channel . getAppInstance ( appInstanceId ) ; } public async getMultisigAddressWithCounterparty ( myIdentifier , theirIdentifier , proxyFactoryAddress , minimumViableMultisigAddress ) { const stateChannelsMap = await this . getStateChannelsMap ( ) ; for ( const stateChannel of stateChannelsMap . values ( ) ) { if ( stateChannel . userNeuteredExtendedKeys . sort ( ) . toString ( ) === [ myIdentifier , theirIdentifier ] . sort ( ) . toString ( ) ) { return stateChannel . multisigAddress ; } } return getCreate2MultisigAddress ( [ myIdentifier , theirIdentifier ] , proxyFactoryAddress , minimumViableMultisigAddress ) ; } public async getOrCreateStateChannelBetweenVirtualAppParticipants ( multisigAddress , initiatorXpub , responderXpub ) { try { return await this . getStateChannel ( multisigAddress ) ; } catch ( e ) { if ( e . toString ( ) . includes ( NO_STATE_CHANNEL_FOR_MULTISIG_ADDR ( multisigAddress ) ) ) { const stateChannel = StateChannel . createEmptyChannel ( multisigAddress , [ initiatorXpub , responderXpub ] ) ; await this . saveStateChannel ( stateChannel ) ; return stateChannel ; } throw Error ( prettyPrintObject ( e ) ) ; } } }