import { validateLink } from '@ceramicnetwork/blockchain-utils-validation' ; import { Caip10Link } from '@ceramicnetwork/stream-caip10-link' ; import { AnchorStatus , CommitData , CommitType , Context , SignatureStatus , StreamConstructor , StreamHandler , StreamState , StreamUtils , } from '@ceramicnetwork/common' ; export class Caip10LinkHandler implements StreamHandler < Caip10Link > { get type ( ) : number { return Caip10Link . STREAM_TYPE_ID ; } get name ( ) : string { return Caip10Link . STREAM_TYPE_NAME ; } get stream_constructor ( ) : StreamConstructor < Caip10Link > { return Caip10Link ; } param commitData param context param state async applyCommit ( commitData , context , state ? ) { if ( state == null ) { return this . _applyGenesis ( commitData ) ; } if ( StreamUtils . isAnchorCommitData ( commitData ) ) { return this . _applyAnchor ( context , commitData , state ) ; } return this . _applySigned ( commitData , state ) ; } param commitData private async _applyGenesis ( commitData ) { const commit = commitData . commit ; if ( commit . data ) { throw new Error ( 'Caip10Link genesis commit cannot have data' ) ; } const state = { type : Caip10Link . STREAM_TYPE_ID , content : null , next : { content : null , } , metadata : commit . header , signature : SignatureStatus . GENESIS , anchorStatus : AnchorStatus . NOT_REQUESTED , log : [ { cid : commitData . cid , type : CommitType . GENESIS } ] , } ; if ( ! ( state . metadata . controllers && state . metadata . controllers . length === 1 ) ) { throw new Error ( 'Exactly one controller must be specified' ) ; } return state ; } param commitData param state private async _applySigned ( commitData , state ) { const commit = commitData . commit ; let validProof = null ; try { validProof = await validateLink ( commit . data ) ; } catch ( e ) { throw new Error ( 'Error while validating link proof for caip10-link signed commit: ' + e . toString ( ) ) ; } if ( ! validProof ) { throw new Error ( 'Invalid proof for signed commit' ) ; } if ( state . signature !== SignatureStatus . GENESIS && ( ( state . anchorStatus === AnchorStatus . ANCHORED && validProof . timestamp < state . anchorProof . blockTimestamp ) || ( state . anchorStatus !== AnchorStatus . ANCHORED && validProof . timestamp < state . next . metadata . lastUpdate ) ) ) { throw new Error ( 'Invalid commit, proof timestamp too old' ) ; } const account = validProof . account || validProof . address ; let [ address , chainId ] = account . split ( '@' ) ; const addressCaip10 = [ address , chainId ] . join ( '@' ) ; if ( addressCaip10 . toLowerCase ( ) !== state . metadata . controllers [ 0 ] . toLowerCase ( ) ) { throw new Error ( "Address doesn't match stream controller" ) ; } state . log . push ( { cid : commitData . cid , type : CommitType . SIGNED } ) ; return { ... state , signature : SignatureStatus . SIGNED , anchorStatus : AnchorStatus . NOT_REQUESTED , next : { content : validProof . did , metadata : { ... state . metadata , lastUpdate : validProof . timestamp , } , } , } ; } param context param commitData param state private async _applyAnchor ( context , commitData , state ) { state . log . push ( { cid : commitData . cid , type : CommitType . ANCHOR } ) ; let content = state . content ; if ( state . next ?. content ) { content = state . next . content ; } delete state . next ; delete state . anchorScheduledFor ; return { ... state , content , anchorStatus : AnchorStatus . ANCHORED , anchorProof : commitData . proof , } ; } }