import { BigNumberish , ethers } from 'ethers' ; import { MultiProvider } from '..' ; import { xapps , core } from '@optics-xyz/ts-interface' ; import { BridgeContracts } from './contracts/BridgeContracts' ; import { CoreContracts } from './contracts/CoreContracts' ; import { ResolvedTokenInfo , TokenIdentifier } from './tokens' ; import { canonizeId , evmId } from '../utils' ; import { devDomains , mainnetDomains , mainnetCommunityDomains , OpticsDomain , stagingDomains , stagingCommunityDomains , } from './domains' ; import { TransferMessage } from './messages' ; import { hexlify } from '@ethersproject/bytes' ; type Address = string ; /**
 * The OpticsContext managers connections to Optics core and Bridge contracts.
 * It inherits from the  MultiProvider , and ensures that its contracts
 * always use the latest registered providers and signers.
 *
 * For convenience, we've pre-constructed contexts for mainnet and testnet
 * deployments. These can be imported directly.
 *
 *  example export class OpticsContext extends MultiProvider { private cores : Map < number , CoreContracts > ; private bridges : Map < number , BridgeContracts > ; constructor ( domains , cores , bridges ) { super ( ) ; domains . forEach ( ( domain ) => this . registerDomain ( domain ) ) ; this . cores = new Map ( ) ; this . bridges = new Map ( ) ; cores . forEach ( ( core ) => { this . cores . set ( core . domain , core ) ; } ) ; bridges . forEach ( ( bridge ) => { this . bridges . set ( bridge . domain , bridge ) ; } ) ; } param domains returns static fromDomains ( domains ) { const cores = domains . map ( ( domain ) => CoreContracts . fromObject ( domain ) ) ; const bridges = domains . map ( ( domain ) => BridgeContracts . fromObject ( domain ) ) ; return new OpticsContext ( domains , cores , bridges ) ; } param domain private reconnect ( domain ) { const connection = this . getConnection ( domain ) ; if ( ! connection ) { throw new Error ( `Reconnect failed: no connection for ${ domain }` ) ; } const core = this . cores . get ( domain ) ; if ( core ) { core . connect ( connection ) ; } const bridge = this . bridges . get ( domain ) ; if ( bridge ) { bridge . connect ( connection ) ; } } param nameOrDomain param provider registerProvider ( nameOrDomain , provider ) { const domain = this . resolveDomain ( nameOrDomain ) ; super . registerProvider ( domain , provider ) ; this . reconnect ( domain ) ; } param nameOrDomain param signer registerSigner ( nameOrDomain , signer ) { const domain = this . resolveDomain ( nameOrDomain ) ; super . registerSigner ( domain , signer ) ; this . reconnect ( domain ) ; } param nameOrDomain unregisterSigner ( nameOrDomain ) { const domain = this . resolveDomain ( nameOrDomain ) ; super . unregisterSigner ( domain ) ; this . reconnect ( domain ) ; } /**
     * Clear all signers from all registered domains.
     */ clearSigners ( ) { super . clearSigners ( ) ; this . domainNumbers . forEach ( ( domain ) => this . reconnect ( domain ) ) ; } /**
     * Get the  CoreContracts for a given domain (or undefined)
     *
     *  param nameOrDomain returns a  CoreContracts object (or undefined)
      getCore ( nameOrDomain ) { const domain = this . resolveDomain ( nameOrDomain ) ; return this . cores . get ( domain ) ; } /**
     * Get the  CoreContracts for a given domain (or throw an error)
     *
     *  param nameOrDomain returns a  CoreContracts object
     *  throws if no  CoreContracts object exists on that domain.
      mustGetCore ( nameOrDomain ) { const core = this . getCore ( nameOrDomain ) ; if ( ! core ) { throw new Error ( `Missing core for domain: ${ nameOrDomain }` ) ; } return core ; } /**
     * Get the  BridgeContracts for a given domain (or undefined)
     *
     *  param nameOrDomain returns a  BridgeContracts object (or undefined)
      getBridge ( nameOrDomain ) { const domain = this . resolveDomain ( nameOrDomain ) ; return this . bridges . get ( domain ) ; } /**
     * Get the  BridgeContracts for a given domain (or throw an error)
     *
     *  param nameOrDomain returns a  BridgeContracts object
     *  throws if no  BridgeContracts object exists on that domain.
      mustGetBridge ( nameOrDomain ) { const bridge = this . getBridge ( nameOrDomain ) ; if ( ! bridge ) { throw new Error ( `Missing bridge for domain: ${ nameOrDomain }` ) ; } return bridge ; } param home param remote returns getReplicaFor ( home , remote ) { return this . getCore ( remote ) ?. replicas . get ( this . resolveDomain ( home ) ) ?. contract ; } param home param remote returns throws mustGetReplicaFor ( home , remote ) { const replica = this . getReplicaFor ( home , remote ) ; if ( ! replica ) { throw new Error ( `Missing replica for home ${ home } & remote ${ remote }` ) ; } return replica ; } param nameOrDomain param token returns async resolveRepresentation ( nameOrDomain , token ) { const domain = this . resolveDomain ( nameOrDomain ) ; const bridge = this . getBridge ( domain ) ; const tokenDomain = this . resolveDomain ( token . domain ) ; const tokenId = canonizeId ( token . id ) ; const address = await bridge ?. bridgeRouter [ 'getLocalAddress(uint32,bytes32)' ] ( tokenDomain , tokenId ) ; if ( ! address ) { return ; } let contract = new xapps . BridgeToken__factory ( ) . attach ( evmId ( address ) ) ; const connection = this . getConnection ( domain ) ; if ( connection ) { contract = contract . connect ( connection ) ; } return contract ; } param token returns A  ResolvedTokenInfo object with representation addresses
      async resolveRepresentations ( token ) { const tokens = new Map ( ) ; await Promise . all ( this . domainNumbers . map ( async ( domain ) => { const tok = await this . resolveRepresentation ( domain , token ) ; if ( tok ) { tokens . set ( domain , tok ) ; } } ) ) ; return { domain : this . resolveDomain ( token . domain ) , id : token . id , tokens , } ; } param nameOrDomain param representation returns throws async resolveCanonicalIdentifier ( nameOrDomain , representation ) { const domain = this . resolveDomain ( nameOrDomain ) ; const bridge = this . mustGetBridge ( nameOrDomain ) ; const repr = hexlify ( canonizeId ( representation ) ) ; const canonical = await bridge . bridgeRouter . representationToCanonical ( representation ) ; if ( canonical [ 0 ] !== 0 ) { return { domain : canonical [ 0 ] , id : canonical [ 1 ] , } ; } const local = await bridge . bridgeRouter [ 'getLocalAddress(uint32,bytes32)' ] ( domain , repr ) ; if ( local !== ethers . constants . AddressZero ) { return { domain , id : hexlify ( canonizeId ( local ) ) , } ; } throw new Error ( 'Token not known to the bridge' ) ; } param nameOrDomain param representation returns throws async resolveCanonicalToken ( nameOrDomain , representation ) { const canonicalId = await this . resolveCanonicalIdentifier ( nameOrDomain , representation ) ; if ( ! canonicalId ) { throw new Error ( 'Token seems to not exist' ) ; } const token = await this . resolveRepresentation ( canonicalId . domain , canonicalId ) ; if ( ! token ) { throw new Error ( 'Cannot resolve canonical on its own domain. how did this happen?' ) ; } return token ; } param from param to param token param amount param recipient param overrides returns a  TransferMessage object representing the in-flight
     *          transfer
     *  throws async send ( from , to , token , amount , recipient , overrides = { } ) { const fromBridge = this . mustGetBridge ( from ) ; const bridgeAddress = fromBridge . bridgeRouter . address ; const fromToken = await this . resolveRepresentation ( from , token ) ; if ( ! fromToken ) { throw new Error ( `Token not available on ${ from }` ) ; } const sender = this . getSigner ( from ) ; if ( ! sender ) { throw new Error ( `No signer for ${ from }` ) ; } const senderAddress = await sender . getAddress ( ) ; const approved = await fromToken . allowance ( senderAddress , bridgeAddress ) ; if ( approved . lt ( amount ) ) { const tx = await fromToken . approve ( bridgeAddress , amount , overrides ) ; await tx . wait ( ) ; } const tx = await fromBridge . bridgeRouter . send ( fromToken . address , amount , this . resolveDomain ( to ) , canonizeId ( recipient ) , overrides ) ; const receipt = await tx . wait ( ) ; const message = TransferMessage . singleFromReceipt ( this , from , receipt ) ; if ( ! message ) { throw new Error ( ) ; } return message as TransferMessage ; } param from param to param amount param recipient param overrides returns a  TransferMessage object representing the in-flight
     *          transfer
     *  throws async sendNative ( from , to , amount , recipient , overrides = { } ) { const ethHelper = this . mustGetBridge ( from ) . ethHelper ; if ( ! ethHelper ) { throw new Error ( `No ethHelper for ${ from }` ) ; } const toDomain = this . resolveDomain ( to ) ; overrides . value = amount ; const tx = await ethHelper . sendToEVMLike ( toDomain , recipient , overrides ) ; const receipt = await tx . wait ( ) ; const message = TransferMessage . singleFromReceipt ( this , from , receipt ) ; if ( ! message ) { throw new Error ( ) ; } return message as TransferMessage ; } } export const mainnet = OpticsContext . fromDomains ( mainnetDomains ) ; export const mainnetCommunity = OpticsContext . fromDomains ( mainnetCommunityDomains ) ; export const dev = OpticsContext . fromDomains ( devDomains ) ; export const staging = OpticsContext . fromDomains ( stagingDomains ) ; export const stagingCommunity = OpticsContext . fromDomains ( stagingCommunityDomains ) ;