import { BigNumber } from '@ethersproject/bignumber' ; import { arrayify , hexlify } from '@ethersproject/bytes' ; import { TransactionReceipt } from '@ethersproject/abstract-provider' ; import { core } from '@optics-xyz/ts-interface' ; import { OpticsContext } from '..' ; import { delay } from '../../utils' ; import { DispatchEvent , AnnotatedDispatch , AnnotatedUpdate , AnnotatedProcess , UpdateTypes , UpdateArgs , ProcessTypes , ProcessArgs , AnnotatedLifecycleEvent , Annotated , DispatchTypes , } from '../events' ; import { queryAnnotatedEvents } from '..' ; import { keccak256 } from 'ethers/lib/utils' ; export type ParsedMessage = { from : number ; sender : string ; nonce : number ; destination : number ; recipient : string ; body : string ; } ; export type OpticsStatus = { status : MessageStatus ; events : AnnotatedLifecycleEvent [ ] ; } ; export enum MessageStatus { Dispatched = 0 , Included = 1 , Relayed = 2 , Processed = 3 } export enum ReplicaMessageStatus { None = 0 , Proven , Processed } export type EventCache = { homeUpdate ? : AnnotatedUpdate ; replicaUpdate ? : AnnotatedUpdate ; process ? : AnnotatedProcess ; } ; param message returns export function parseMessage ( message ) { const buf = Buffer . from ( arrayify ( message ) ) ; const from = buf . readUInt32BE ( 0 ) ; const sender = hexlify ( buf . slice ( 4 , 36 ) ) ; const nonce = buf . readUInt32BE ( 36 ) ; const destination = buf . readUInt32BE ( 40 ) ; const recipient = hexlify ( buf . slice ( 44 , 76 ) ) ; const body = hexlify ( buf . slice ( 76 ) ) ; return { from , sender , nonce , destination , recipient , body } ; } /**
 * A deserialized Optics message.
 */ export class OpticsMessage { readonly dispatch : AnnotatedDispatch ; readonly message : ParsedMessage ; readonly home : core . Home ; readonly replica : core . Replica ; readonly context : OpticsContext ; protected cache : EventCache ; constructor ( context , dispatch ) { this . context = context ; this . message = parseMessage ( dispatch . event . args . message ) ; this . dispatch = dispatch ; this . home = context . mustGetCore ( this . message . from ) . home ; this . replica = context . mustGetReplicaFor ( this . message . from , this . message . destination ) ; this . cache = { } ; } /**
     * The receipt of the TX that dispatched this message
     */ get receipt ( ) : TransactionReceipt { return this . dispatch . receipt ; } param context the  OpticsContext object to use
     *  param nameOrDomain param receipt returns an array of  OpticsMessage objects
      static fromReceipt ( context , nameOrDomain , receipt ) { const messages = [ ] ; const home = new core . Home__factory ( ) . interface ; for ( const log of receipt . logs ) { try { const parsed = home . parseLog ( log ) ; if ( parsed . name === 'Dispatch' ) { const dispatch = parsed as unknown as DispatchEvent ; dispatch . getBlock = ( ) => { return context . mustGetProvider ( nameOrDomain ) . getBlock ( log . blockHash ) ; } ; dispatch . getTransaction = ( ) => { return context . mustGetProvider ( nameOrDomain ) . getTransaction ( log . transactionHash ) ; } ; dispatch . getTransactionReceipt = ( ) => { return context . mustGetProvider ( nameOrDomain ) . getTransactionReceipt ( log . transactionHash ) ; } ; const annotated = new Annotated < DispatchTypes , DispatchEvent > ( context . resolveDomain ( nameOrDomain ) , receipt , dispatch , true ) ; annotated . event . blockNumber = annotated . receipt . blockNumber ; const message = new OpticsMessage ( context , annotated ) ; messages . push ( message ) ; } } catch ( e ) { continue ; } } return messages ; } param context the  OpticsContext object to use
     *  param nameOrDomain param receipt returns an array of  OpticsMessage objects
     *  throws static singleFromReceipt ( context , nameOrDomain , receipt ) { const messages = OpticsMessage . fromReceipt ( context , nameOrDomain , receipt ) ; if ( messages . length !== 1 ) { throw new Error ( 'Expected single Dispatch in transaction' ) ; } return messages [ 0 ] ; } param context the  OpticsContext object to use
     *  param nameOrDomain param receipt returns an array of  OpticsMessage objects
     *  throws static async fromTransactionHash ( context , nameOrDomain , transactionHash ) { const provider = context . mustGetProvider ( nameOrDomain ) ; const receipt = await provider . getTransactionReceipt ( transactionHash ) ; if ( ! receipt ) { throw new Error ( `No receipt for ${ transactionHash } on ${ nameOrDomain }` ) ; } return OpticsMessage . fromReceipt ( context , nameOrDomain , receipt ) ; } param context the  OpticsContext object to use
     *  param nameOrDomain param receipt returns an array of  OpticsMessage objects
     *  throws static async singleFromTransactionHash ( context , nameOrDomain , transactionHash ) { const provider = context . mustGetProvider ( nameOrDomain ) ; const receipt = await provider . getTransactionReceipt ( transactionHash ) ; if ( ! receipt ) { throw new Error ( `No receipt for ${ transactionHash } on ${ nameOrDomain }` ) ; } return OpticsMessage . singleFromReceipt ( context , nameOrDomain , receipt ) ; } returns An  AnnotatedUpdate (if any)
      async getHomeUpdate ( ) { if ( this . cache . homeUpdate ) { return this . cache . homeUpdate ; } const updateFilter = this . home . filters . Update ( undefined , this . committedRoot ) ; const updateLogs = await queryAnnotatedEvents < UpdateTypes , UpdateArgs > ( this . context , this . origin , this . home , updateFilter ) ; if ( updateLogs . length === 1 ) { this . cache . homeUpdate = updateLogs [ 0 ] ; } else if ( updateLogs . length > 1 ) { throw new Error ( 'multiple home updates for same root' ) ; } return this . cache . homeUpdate ; } returns An  AnnotatedUpdate (if any)
      async getReplicaUpdate ( ) { if ( this . cache . replicaUpdate ) { return this . cache . replicaUpdate ; } const updateFilter = this . replica . filters . Update ( undefined , this . committedRoot ) ; const updateLogs = await queryAnnotatedEvents < UpdateTypes , UpdateArgs > ( this . context , this . destination , this . replica , updateFilter ) ; if ( updateLogs . length === 1 ) { this . cache . replicaUpdate = updateLogs [ 0 ] ; } else if ( updateLogs . length > 1 ) { throw new Error ( 'multiple replica updates for same root' ) ; } return this . cache . replicaUpdate ; } returns An  AnnotatedProcess (if any)
      async getProcess ( ) { if ( this . cache . process ) { return this . cache . process ; } const processFilter = this . replica . filters . Process ( this . leaf ) ; const processLogs = await queryAnnotatedEvents < ProcessTypes , ProcessArgs > ( this . context , this . destination , this . replica , processFilter ) ; if ( processLogs . length === 1 ) { this . cache . process = processLogs [ 0 ] ; } else if ( processLogs . length > 1 ) { throw new Error ( 'multiple replica process for same message' ) ; } return this . cache . process ; } returns An array of  AnnotatedLifecycleEvent objects
      async events ( ) { const events = [ this . dispatch ] ; const homeUpdate = await this . getHomeUpdate ( ) ; if ( ! homeUpdate ) { return { status : MessageStatus . Dispatched , events , } ; } events . push ( homeUpdate ) ; const replicaUpdate = await this . getReplicaUpdate ( ) ; if ( ! replicaUpdate ) { return { status : MessageStatus . Included , events , } ; } events . push ( replicaUpdate ) ; const process = await this . getProcess ( ) ; if ( ! process ) { return { status : MessageStatus . Relayed , events , } ; } events . push ( process ) ; return { status : MessageStatus . Processed , events , } ; } returns async confirmAt ( ) { const update = await this . getHomeUpdate ( ) ; if ( ! update ) { return BigNumber . from ( 0 ) ; } const { newRoot } = update . event . args ; return this . replica . confirmAt ( newRoot ) ; } returns The  ReplicaMessageStatus corresponding to the solidity
     * status of the message.
      async replicaStatus ( ) { return this . replica . messages ( this . leaf ) ; } returns async delivered ( ) { const status = await this . replicaStatus ( ) ; return status === ReplicaMessageStatus . Processed ; } param opts async wait ( opts ? ) { const interval = opts ?. pollTime ?? 5000 ; for ( ; ; ) { if ( await this . delivered ( ) ) { return ; } await delay ( interval ) ; } } /**
     * The domain from which the message was sent
     */ get from ( ) : number { return this . message . from ; } /**
     * The domain from which the message was sent. Alias for `from`
     */ get origin ( ) : number { return this . from ; } /**
     * The identifier for the sender of this message
     */ get sender ( ) : string { return this . message . sender ; } /**
     * The domain nonce for this message
     */ get nonce ( ) : number { return this . message . nonce ; } /**
     * The destination domain for this message
     */ get destination ( ) : number { return this . message . destination ; } /**
     * The identifer for the recipient for this message
     */ get recipient ( ) : string { return this . message . recipient ; } /**
     * The message body
     */ get body ( ) : string { return this . message . body ; } /**
     * The keccak256 hash of the message body
     */ get bodyHash ( ) : string { return keccak256 ( this . body ) ; } /**
     * The hash of the transaction that dispatched this message
     */ get transactionHash ( ) : string { return this . dispatch . event . transactionHash ; } /**
     * The messageHash committed to the tree in the Home contract.
     */ get leaf ( ) : string { return this . dispatch . event . args . messageHash ; } /**
     * The index of the leaf in the contract.
     */ get leafIndex ( ) : BigNumber { return this . dispatch . event . args . leafIndex ; } /**
     * The destination and nonceof this message.
     */ get destinationAndNonce ( ) : BigNumber { return this . dispatch . event . args . destinationAndNonce ; } /**
     * The committed root when this message was dispatched.
     */ get committedRoot ( ) : string { return this . dispatch . event . args . committedRoot ; } }