2022-11-30 23:44:23 +03:00
/ * *
2023-06-02 00:52:58 +03:00
* The available providers should suffice for most developers purposes ,
* but the [ [ AbstractProvider ] ] class has many features which enable
* sub - classing it for specific purposes .
2022-11-30 23:44:23 +03:00
*
* @ _section : api / providers / abstract - provider : Subclassing Provider [ abstract - provider ]
* /
2022-09-05 23:57:11 +03:00
// @TODO
// Event coalescence
// When we register an event with an async value (e.g. address is a Signer
// or ENS name), we need to add it immeidately for the Event API, but also
// need time to resolve the address. Upon resolving the address, we need to
// migrate the listener to the static event. We also need to maintain a map
// of Signer/ENS name to address so we can sync respond to listenerCount.
2023-02-02 12:05:47 +03:00
import { getAddress , resolveAddress } from "../address/index.js" ;
2023-06-02 00:52:58 +03:00
import { ZeroAddress } from "../constants/index.js" ;
2023-02-02 12:05:47 +03:00
import { Contract } from "../contract/index.js" ;
import { namehash } from "../hash/index.js" ;
2022-10-20 12:03:32 +03:00
import { Transaction } from "../transaction/index.js" ;
2023-02-16 16:19:59 +03:00
import { concat , dataLength , dataSlice , hexlify , isHexString , getBigInt , getBytes , getNumber , isCallException , isError , makeError , assert , assertArgument , FetchRequest , toBeArray , toQuantity , defineProperties , EventPayload , resolveProperties , toUtf8String } from "../utils/index.js" ;
2022-09-05 23:57:11 +03:00
import { EnsResolver } from "./ens-resolver.js" ;
2022-11-30 23:44:23 +03:00
import { formatBlock , formatLog , formatTransactionReceipt , formatTransactionResponse } from "./format.js" ;
2022-09-05 23:57:11 +03:00
import { Network } from "./network.js" ;
2022-09-27 10:45:27 +03:00
import { copyRequest , Block , FeeData , Log , TransactionReceipt , TransactionResponse } from "./provider.js" ;
2023-11-27 14:11:49 +03:00
import { PollingBlockSubscriber , PollingBlockTagSubscriber , PollingEventSubscriber , PollingOrphanSubscriber , PollingTransactionSubscriber } from "./subscriber-polling.js" ;
2022-09-05 23:57:11 +03:00
// Constants
const BN _2 = BigInt ( 2 ) ;
const MAX _CCIP _REDIRECTS = 10 ;
2022-09-27 10:45:27 +03:00
function isPromise ( value ) {
return ( value && typeof ( value . then ) === "function" ) ;
}
2022-09-05 23:57:11 +03:00
function getTag ( prefix , value ) {
return prefix + ":" + JSON . stringify ( value , ( k , v ) => {
2022-11-09 10:57:02 +03:00
if ( v == null ) {
return "null" ;
}
2022-09-05 23:57:11 +03:00
if ( typeof ( v ) === "bigint" ) {
return ` bigint: ${ v . toString ( ) } ` ;
}
if ( typeof ( v ) === "string" ) {
return v . toLowerCase ( ) ;
}
// Sort object keys
if ( typeof ( v ) === "object" && ! Array . isArray ( v ) ) {
const keys = Object . keys ( v ) ;
keys . sort ( ) ;
return keys . reduce ( ( accum , key ) => {
accum [ key ] = v [ key ] ;
return accum ;
} , { } ) ;
}
return v ;
} ) ;
}
2023-06-02 00:52:58 +03:00
/ * *
* An * * UnmanagedSubscriber * * is useful for events which do not require
* any additional management , such as ` ` "debug" ` ` which only requires
* emit in synchronous event loop triggered calls .
* /
2022-09-05 23:57:11 +03:00
export class UnmanagedSubscriber {
2023-06-02 00:52:58 +03:00
/ * *
* The name fof the event .
* /
2022-09-05 23:57:11 +03:00
name ;
2023-06-02 00:52:58 +03:00
/ * *
* Create a new UnmanagedSubscriber with % % name % % .
* /
2022-09-05 23:57:11 +03:00
constructor ( name ) { defineProperties ( this , { name } ) ; }
start ( ) { }
stop ( ) { }
pause ( dropWhilePaused ) { }
resume ( ) { }
}
function copy ( value ) {
return JSON . parse ( JSON . stringify ( value ) ) ;
}
function concisify ( items ) {
items = Array . from ( ( new Set ( items ) ) . values ( ) ) ;
items . sort ( ) ;
return items ;
}
async function getSubscription ( _event , provider ) {
if ( _event == null ) {
throw new Error ( "invalid event" ) ;
}
// Normalize topic array info an EventFilter
if ( Array . isArray ( _event ) ) {
_event = { topics : _event } ;
}
if ( typeof ( _event ) === "string" ) {
switch ( _event ) {
case "block" :
case "debug" :
2023-04-25 14:04:48 +03:00
case "error" :
2023-11-27 14:11:49 +03:00
case "finalized" :
case "network" :
case "pending" :
case "safe" : {
2022-09-05 23:57:11 +03:00
return { type : _event , tag : _event } ;
}
}
}
if ( isHexString ( _event , 32 ) ) {
const hash = _event . toLowerCase ( ) ;
return { type : "transaction" , tag : getTag ( "tx" , { hash } ) , hash } ;
}
if ( _event . orphan ) {
const event = _event ;
// @TODO: Should lowercase and whatnot things here instead of copy...
return { type : "orphan" , tag : getTag ( "orphan" , event ) , filter : copy ( event ) } ;
}
if ( ( _event . address || _event . topics ) ) {
const event = _event ;
const filter = {
topics : ( ( event . topics || [ ] ) . map ( ( t ) => {
if ( t == null ) {
return null ;
}
if ( Array . isArray ( t ) ) {
return concisify ( t . map ( ( t ) => t . toLowerCase ( ) ) ) ;
}
return t . toLowerCase ( ) ;
} ) )
} ;
if ( event . address ) {
const addresses = [ ] ;
const promises = [ ] ;
const addAddress = ( addr ) => {
if ( isHexString ( addr ) ) {
addresses . push ( addr ) ;
}
else {
promises . push ( ( async ( ) => {
addresses . push ( await resolveAddress ( addr , provider ) ) ;
} ) ( ) ) ;
}
} ;
if ( Array . isArray ( event . address ) ) {
event . address . forEach ( addAddress ) ;
}
else {
addAddress ( event . address ) ;
}
if ( promises . length ) {
await Promise . all ( promises ) ;
}
filter . address = concisify ( addresses . map ( ( a ) => a . toLowerCase ( ) ) ) ;
}
return { filter , tag : getTag ( "event" , filter ) , type : "event" } ;
}
2022-11-09 10:57:02 +03:00
assertArgument ( false , "unknown ProviderEvent" , "event" , _event ) ;
2022-09-05 23:57:11 +03:00
}
function getTime ( ) { return ( new Date ( ) ) . getTime ( ) ; }
2023-06-14 04:47:44 +03:00
const defaultOptions = {
2023-07-29 09:37:03 +03:00
cacheTimeout : 250 ,
pollingInterval : 4000
2023-06-14 04:47:44 +03:00
} ;
2023-06-02 00:52:58 +03:00
/ * *
* An * * AbstractProvider * * provides a base class for other sub - classes to
* implement the [ [ Provider ] ] API by normalizing input arguments and
* formatting output results as well as tracking events for consistent
* behaviour on an eventually - consistent network .
* /
2022-09-05 23:57:11 +03:00
export class AbstractProvider {
# subs ;
# plugins ;
// null=unpaused, true=paused+dropWhilePaused, false=paused
# pausedState ;
2023-06-07 05:42:28 +03:00
# destroyed ;
2022-09-05 23:57:11 +03:00
# networkPromise ;
# anyNetwork ;
# performCache ;
2022-09-27 10:45:27 +03:00
// The most recent block number if running an event or -1 if no "block" event
# lastBlockNumber ;
2022-09-05 23:57:11 +03:00
# nextTimer ;
# timers ;
# disableCcipRead ;
2023-06-14 04:47:44 +03:00
# options ;
2023-06-02 00:52:58 +03:00
/ * *
* Create a new * * AbstractProvider * * connected to % % network % % , or
* use the various network detection capabilities to discover the
* [ [ Network ] ] if necessary .
* /
2023-06-14 04:47:44 +03:00
constructor ( _network , options ) {
this . # options = Object . assign ( { } , defaultOptions , options || { } ) ;
2022-09-05 23:57:11 +03:00
if ( _network === "any" ) {
this . # anyNetwork = true ;
this . # networkPromise = null ;
}
else if ( _network ) {
const network = Network . from ( _network ) ;
this . # anyNetwork = false ;
this . # networkPromise = Promise . resolve ( network ) ;
setTimeout ( ( ) => { this . emit ( "network" , network , null ) ; } , 0 ) ;
}
else {
this . # anyNetwork = false ;
this . # networkPromise = null ;
}
2022-09-27 10:45:27 +03:00
this . # lastBlockNumber = - 1 ;
2022-09-05 23:57:11 +03:00
this . # performCache = new Map ( ) ;
this . # subs = new Map ( ) ;
this . # plugins = new Map ( ) ;
this . # pausedState = null ;
2023-06-07 05:42:28 +03:00
this . # destroyed = false ;
2023-02-02 12:05:47 +03:00
this . # nextTimer = 1 ;
2022-09-05 23:57:11 +03:00
this . # timers = new Map ( ) ;
this . # disableCcipRead = false ;
}
2023-07-29 09:37:03 +03:00
get pollingInterval ( ) { return this . # options . pollingInterval ; }
2023-06-02 00:52:58 +03:00
/ * *
* Returns ` ` this ` ` , to allow an * * AbstractProvider * * to implement
* the [ [ ContractRunner ] ] interface .
* /
2022-09-05 23:57:11 +03:00
get provider ( ) { return this ; }
2023-06-02 00:52:58 +03:00
/ * *
* Returns all the registered plug - ins .
* /
2022-09-05 23:57:11 +03:00
get plugins ( ) {
return Array . from ( this . # plugins . values ( ) ) ;
}
2023-06-02 00:52:58 +03:00
/ * *
* Attach a new plug - in .
* /
2022-09-05 23:57:11 +03:00
attachPlugin ( plugin ) {
if ( this . # plugins . get ( plugin . name ) ) {
throw new Error ( ` cannot replace existing plugin: ${ plugin . name } ` ) ;
}
2022-11-09 10:57:02 +03:00
this . # plugins . set ( plugin . name , plugin . connect ( this ) ) ;
2022-09-05 23:57:11 +03:00
return this ;
}
2023-06-02 00:52:58 +03:00
/ * *
* Get a plugin by name .
* /
2022-09-05 23:57:11 +03:00
getPlugin ( name ) {
return ( this . # plugins . get ( name ) ) || null ;
}
2023-06-02 00:52:58 +03:00
/ * *
* Prevent any CCIP - read operation , regardless of whether requested
* in a [ [ call ] ] using ` ` enableCcipRead ` ` .
* /
2022-09-05 23:57:11 +03:00
get disableCcipRead ( ) { return this . # disableCcipRead ; }
2022-11-30 23:44:23 +03:00
set disableCcipRead ( value ) { this . # disableCcipRead = ! ! value ; }
2022-09-05 23:57:11 +03:00
// Shares multiple identical requests made during the same 250ms
async # perform ( req ) {
2023-06-14 04:47:44 +03:00
const timeout = this . # options . cacheTimeout ;
// Caching disabled
if ( timeout < 0 ) {
return await this . _perform ( req ) ;
}
2022-09-05 23:57:11 +03:00
// Create a tag
const tag = getTag ( req . method , req ) ;
let perform = this . # performCache . get ( tag ) ;
if ( ! perform ) {
perform = this . _perform ( req ) ;
this . # performCache . set ( tag , perform ) ;
setTimeout ( ( ) => {
if ( this . # performCache . get ( tag ) === perform ) {
this . # performCache . delete ( tag ) ;
}
2023-06-14 04:47:44 +03:00
} , timeout ) ;
2022-09-05 23:57:11 +03:00
}
return await perform ;
}
2023-06-02 00:52:58 +03:00
/ * *
* Resolves to the data for executing the CCIP - read operations .
* /
2022-09-05 23:57:11 +03:00
async ccipReadFetch ( tx , calldata , urls ) {
if ( this . disableCcipRead || urls . length === 0 || tx . to == null ) {
return null ;
}
const sender = tx . to . toLowerCase ( ) ;
const data = calldata . toLowerCase ( ) ;
const errorMessages = [ ] ;
for ( let i = 0 ; i < urls . length ; i ++ ) {
const url = urls [ i ] ;
// URL expansion
const href = url . replace ( "{sender}" , sender ) . replace ( "{data}" , data ) ;
// If no {data} is present, use POST; otherwise GET
//const json: string | null = (url.indexOf("{data}") >= 0) ? null: JSON.stringify({ data, sender });
//const result = await fetchJson({ url: href, errorPassThrough: true }, json, (value, response) => {
// value.status = response.statusCode;
// return value;
//});
const request = new FetchRequest ( href ) ;
if ( url . indexOf ( "{data}" ) === - 1 ) {
request . body = { data , sender } ;
}
2022-11-09 10:57:02 +03:00
this . emit ( "debug" , { action : "sendCcipReadFetchRequest" , request , index : i , urls } ) ;
2022-09-05 23:57:11 +03:00
let errorMessage = "unknown error" ;
const resp = await request . send ( ) ;
try {
const result = resp . bodyJson ;
if ( result . data ) {
2022-11-09 10:57:02 +03:00
this . emit ( "debug" , { action : "receiveCcipReadFetchResult" , request , result } ) ;
2022-09-05 23:57:11 +03:00
return result . data ;
}
if ( result . message ) {
errorMessage = result . message ;
}
2022-11-09 10:57:02 +03:00
this . emit ( "debug" , { action : "receiveCcipReadFetchError" , request , result } ) ;
2022-09-05 23:57:11 +03:00
}
catch ( error ) { }
// 4xx indicates the result is not present; stop
2022-11-09 10:57:02 +03:00
assert ( resp . statusCode < 400 || resp . statusCode >= 500 , ` response not found during CCIP fetch: ${ errorMessage } ` , "OFFCHAIN_FAULT" , { reason : "404_MISSING_RESOURCE" , transaction : tx , info : { url , errorMessage } } ) ;
2022-09-05 23:57:11 +03:00
// 5xx indicates server issue; try the next url
errorMessages . push ( errorMessage ) ;
}
2022-11-09 10:57:02 +03:00
assert ( false , ` error encountered during CCIP fetch: ${ errorMessages . map ( ( m ) => JSON . stringify ( m ) ) . join ( ", " ) } ` , "OFFCHAIN_FAULT" , {
2022-09-05 23:57:11 +03:00
reason : "500_SERVER_ERROR" ,
transaction : tx , info : { urls , errorMessages }
} ) ;
}
2023-06-02 00:52:58 +03:00
/ * *
* Provides the opportunity for a sub - class to wrap a block before
* returning it , to add additional properties or an alternate
* sub - class of [ [ Block ] ] .
* /
2022-09-27 10:45:27 +03:00
_wrapBlock ( value , network ) {
return new Block ( formatBlock ( value ) , this ) ;
}
2023-06-02 00:52:58 +03:00
/ * *
* Provides the opportunity for a sub - class to wrap a log before
* returning it , to add additional properties or an alternate
* sub - class of [ [ Log ] ] .
* /
2022-09-27 10:45:27 +03:00
_wrapLog ( value , network ) {
return new Log ( formatLog ( value ) , this ) ;
}
2023-06-02 00:52:58 +03:00
/ * *
* Provides the opportunity for a sub - class to wrap a transaction
* receipt before returning it , to add additional properties or an
* alternate sub - class of [ [ TransactionReceipt ] ] .
* /
2022-09-27 10:45:27 +03:00
_wrapTransactionReceipt ( value , network ) {
return new TransactionReceipt ( formatTransactionReceipt ( value ) , this ) ;
}
2023-06-02 00:52:58 +03:00
/ * *
* Provides the opportunity for a sub - class to wrap a transaction
* response before returning it , to add additional properties or an
* alternate sub - class of [ [ TransactionResponse ] ] .
* /
2022-09-27 10:45:27 +03:00
_wrapTransactionResponse ( tx , network ) {
2023-03-20 19:53:37 +03:00
return new TransactionResponse ( formatTransactionResponse ( tx ) , this ) ;
2022-09-05 23:57:11 +03:00
}
2023-06-02 00:52:58 +03:00
/ * *
* Resolves to the Network , forcing a network detection using whatever
* technique the sub - class requires .
*
* Sub - classes * * must * * override this .
* /
2022-09-05 23:57:11 +03:00
_detectNetwork ( ) {
2022-11-09 10:57:02 +03:00
assert ( false , "sub-classes must implement this" , "UNSUPPORTED_OPERATION" , {
2022-09-05 23:57:11 +03:00
operation : "_detectNetwork"
} ) ;
}
2023-06-02 00:52:58 +03:00
/ * *
* Sub - classes should use this to perform all built - in operations . All
* methods sanitizes and normalizes the values passed into this .
*
* Sub - classes * * must * * override this .
* /
2022-09-05 23:57:11 +03:00
async _perform ( req ) {
2022-11-09 10:57:02 +03:00
assert ( false , ` unsupported method: ${ req . method } ` , "UNSUPPORTED_OPERATION" , {
2022-09-05 23:57:11 +03:00
operation : req . method ,
info : req
} ) ;
}
// State
async getBlockNumber ( ) {
2022-09-27 10:45:27 +03:00
const blockNumber = getNumber ( await this . # perform ( { method : "getBlockNumber" } ) , "%response" ) ;
if ( this . # lastBlockNumber >= 0 ) {
this . # lastBlockNumber = blockNumber ;
}
return blockNumber ;
2022-09-05 23:57:11 +03:00
}
2023-06-02 00:52:58 +03:00
/ * *
* Returns or resolves to the address for % % address % % , resolving ENS
* names and [ [ Addressable ] ] objects and returning if already an
* address .
* /
2022-09-05 23:57:11 +03:00
_getAddress ( address ) {
return resolveAddress ( address , this ) ;
}
2023-06-02 00:52:58 +03:00
/ * *
* Returns or resolves to a valid block tag for % % blockTag % % , resolving
* negative values and returning if already a valid block tag .
* /
2022-09-05 23:57:11 +03:00
_getBlockTag ( blockTag ) {
if ( blockTag == null ) {
return "latest" ;
}
switch ( blockTag ) {
case "earliest" :
return "0x0" ;
2023-11-27 14:11:49 +03:00
case "finalized" :
2022-09-05 23:57:11 +03:00
case "latest" :
case "pending" :
case "safe" :
return blockTag ;
}
if ( isHexString ( blockTag ) ) {
2022-11-09 10:57:02 +03:00
if ( isHexString ( blockTag , 32 ) ) {
2022-09-05 23:57:11 +03:00
return blockTag ;
}
return toQuantity ( blockTag ) ;
}
2023-03-04 04:25:07 +03:00
if ( typeof ( blockTag ) === "bigint" ) {
blockTag = getNumber ( blockTag , "blockTag" ) ;
}
2022-09-05 23:57:11 +03:00
if ( typeof ( blockTag ) === "number" ) {
if ( blockTag >= 0 ) {
return toQuantity ( blockTag ) ;
}
2022-09-27 10:45:27 +03:00
if ( this . # lastBlockNumber >= 0 ) {
return toQuantity ( this . # lastBlockNumber + blockTag ) ;
}
2022-09-05 23:57:11 +03:00
return this . getBlockNumber ( ) . then ( ( b ) => toQuantity ( b + blockTag ) ) ;
}
2022-11-09 10:57:02 +03:00
assertArgument ( false , "invalid blockTag" , "blockTag" , blockTag ) ;
2022-09-05 23:57:11 +03:00
}
2023-06-02 00:52:58 +03:00
/ * *
* Returns or resolves to a filter for % % filter % % , resolving any ENS
* names or [ [ Addressable ] ] object and returning if already a valid
* filter .
* /
2022-09-27 10:45:27 +03:00
_getFilter ( filter ) {
// Create a canonical representation of the topics
const topics = ( filter . topics || [ ] ) . map ( ( t ) => {
if ( t == null ) {
return null ;
}
if ( Array . isArray ( t ) ) {
return concisify ( t . map ( ( t ) => t . toLowerCase ( ) ) ) ;
}
return t . toLowerCase ( ) ;
} ) ;
const blockHash = ( "blockHash" in filter ) ? filter . blockHash : undefined ;
const resolve = ( _address , fromBlock , toBlock ) => {
let address = undefined ;
switch ( _address . length ) {
case 0 : break ;
case 1 :
address = _address [ 0 ] ;
break ;
default :
_address . sort ( ) ;
address = _address ;
}
if ( blockHash ) {
if ( fromBlock != null || toBlock != null ) {
throw new Error ( "invalid filter" ) ;
}
}
const filter = { } ;
if ( address ) {
filter . address = address ;
}
if ( topics . length ) {
filter . topics = topics ;
}
if ( fromBlock ) {
filter . fromBlock = fromBlock ;
}
if ( toBlock ) {
filter . toBlock = toBlock ;
}
if ( blockHash ) {
filter . blockHash = blockHash ;
}
return filter ;
} ;
// Addresses could be async (ENS names or Addressables)
let address = [ ] ;
if ( filter . address ) {
if ( Array . isArray ( filter . address ) ) {
for ( const addr of filter . address ) {
address . push ( this . _getAddress ( addr ) ) ;
}
}
else {
address . push ( this . _getAddress ( filter . address ) ) ;
}
}
let fromBlock = undefined ;
if ( "fromBlock" in filter ) {
fromBlock = this . _getBlockTag ( filter . fromBlock ) ;
}
let toBlock = undefined ;
if ( "toBlock" in filter ) {
toBlock = this . _getBlockTag ( filter . toBlock ) ;
}
if ( address . filter ( ( a ) => ( typeof ( a ) !== "string" ) ) . length ||
( fromBlock != null && typeof ( fromBlock ) !== "string" ) ||
( toBlock != null && typeof ( toBlock ) !== "string" ) ) {
return Promise . all ( [ Promise . all ( address ) , fromBlock , toBlock ] ) . then ( ( result ) => {
return resolve ( result [ 0 ] , result [ 1 ] , result [ 2 ] ) ;
} ) ;
}
return resolve ( address , fromBlock , toBlock ) ;
}
2023-06-02 00:52:58 +03:00
/ * *
* Returns or resovles to a transaction for % % request % % , resolving
* any ENS names or [ [ Addressable ] ] and returning if already a valid
* transaction .
* /
2022-09-27 10:45:27 +03:00
_getTransactionRequest ( _request ) {
const request = copyRequest ( _request ) ;
const promises = [ ] ;
[ "to" , "from" ] . forEach ( ( key ) => {
if ( request [ key ] == null ) {
return ;
}
2023-10-10 03:27:56 +03:00
const addr = resolveAddress ( request [ key ] , this ) ;
2022-09-27 10:45:27 +03:00
if ( isPromise ( addr ) ) {
promises . push ( ( async function ( ) { request [ key ] = await addr ; } ) ( ) ) ;
}
else {
request [ key ] = addr ;
}
} ) ;
if ( request . blockTag != null ) {
const blockTag = this . _getBlockTag ( request . blockTag ) ;
if ( isPromise ( blockTag ) ) {
promises . push ( ( async function ( ) { request . blockTag = await blockTag ; } ) ( ) ) ;
}
else {
request . blockTag = blockTag ;
}
}
if ( promises . length ) {
return ( async function ( ) {
await Promise . all ( promises ) ;
return request ;
} ) ( ) ;
}
return request ;
}
2022-09-05 23:57:11 +03:00
async getNetwork ( ) {
// No explicit network was set and this is our first time
if ( this . # networkPromise == null ) {
// Detect the current network (shared with all calls)
2023-12-19 12:53:50 +03:00
const detectNetwork = ( async ( ) => {
try {
const network = await this . _detectNetwork ( ) ;
this . emit ( "network" , network , null ) ;
return network ;
2022-09-05 23:57:11 +03:00
}
2023-12-19 12:53:50 +03:00
catch ( error ) {
if ( this . # networkPromise === detectNetwork ) {
this . # networkPromise = null ;
}
throw error ;
}
} ) ( ) ;
2022-09-05 23:57:11 +03:00
this . # networkPromise = detectNetwork ;
2022-09-27 10:45:27 +03:00
return ( await detectNetwork ) . clone ( ) ;
2022-09-05 23:57:11 +03:00
}
const networkPromise = this . # networkPromise ;
const [ expected , actual ] = await Promise . all ( [
networkPromise ,
this . _detectNetwork ( ) // The actual connected network
] ) ;
if ( expected . chainId !== actual . chainId ) {
if ( this . # anyNetwork ) {
// The "any" network can change, so notify listeners
this . emit ( "network" , actual , expected ) ;
// Update the network if something else hasn't already changed it
if ( this . # networkPromise === networkPromise ) {
this . # networkPromise = Promise . resolve ( actual ) ;
}
}
else {
// Otherwise, we do not allow changes to the underlying network
2022-11-09 10:57:02 +03:00
assert ( false , ` network changed: ${ expected . chainId } => ${ actual . chainId } ` , "NETWORK_ERROR" , {
2022-09-05 23:57:11 +03:00
event : "changed"
} ) ;
}
}
2022-09-27 10:45:27 +03:00
return expected . clone ( ) ;
2022-09-05 23:57:11 +03:00
}
async getFeeData ( ) {
2023-07-29 09:37:03 +03:00
const network = await this . getNetwork ( ) ;
const getFeeDataFunc = async ( ) => {
2023-11-27 14:11:49 +03:00
const { _block , gasPrice , priorityFee } = await resolveProperties ( {
2023-07-29 09:37:03 +03:00
_block : this . # getBlock ( "latest" , false ) ,
gasPrice : ( ( async ( ) => {
try {
2023-11-27 14:11:49 +03:00
const value = await this . # perform ( { method : "getGasPrice" } ) ;
return getBigInt ( value , "%response" ) ;
}
catch ( error ) { }
return null ;
} ) ( ) ) ,
priorityFee : ( ( async ( ) => {
try {
const value = await this . # perform ( { method : "getPriorityFee" } ) ;
return getBigInt ( value , "%response" ) ;
2023-07-29 09:37:03 +03:00
}
catch ( error ) { }
return null ;
} ) ( ) )
} ) ;
2023-08-15 07:58:04 +03:00
let maxFeePerGas = null ;
let maxPriorityFeePerGas = null ;
2023-07-29 09:37:03 +03:00
// These are the recommended EIP-1559 heuristics for fee data
const block = this . _wrapBlock ( _block , network ) ;
if ( block && block . baseFeePerGas ) {
2023-11-27 14:11:49 +03:00
maxPriorityFeePerGas = ( priorityFee != null ) ? priorityFee : BigInt ( "1000000000" ) ;
2023-07-29 09:37:03 +03:00
maxFeePerGas = ( block . baseFeePerGas * BN _2 ) + maxPriorityFeePerGas ;
}
return new FeeData ( gasPrice , maxFeePerGas , maxPriorityFeePerGas ) ;
} ;
// Check for a FeeDataNetWorkPlugin
const plugin = network . getPlugin ( "org.ethers.plugins.network.FetchUrlFeeDataPlugin" ) ;
if ( plugin ) {
const req = new FetchRequest ( plugin . url ) ;
const feeData = await plugin . processFunc ( getFeeDataFunc , this , req ) ;
return new FeeData ( feeData . gasPrice , feeData . maxFeePerGas , feeData . maxPriorityFeePerGas ) ;
}
return await getFeeDataFunc ( ) ;
2022-09-05 23:57:11 +03:00
}
async estimateGas ( _tx ) {
2022-09-27 10:45:27 +03:00
let tx = this . _getTransactionRequest ( _tx ) ;
if ( isPromise ( tx ) ) {
tx = await tx ;
}
2022-09-16 05:58:45 +03:00
return getBigInt ( await this . # perform ( {
2022-09-27 10:45:27 +03:00
method : "estimateGas" , transaction : tx
2022-09-05 23:57:11 +03:00
} ) , "%response" ) ;
}
async # call ( tx , blockTag , attempt ) {
2022-11-09 10:57:02 +03:00
assert ( attempt < MAX _CCIP _REDIRECTS , "CCIP read exceeded maximum redirections" , "OFFCHAIN_FAULT" , {
reason : "TOO_MANY_REDIRECTS" ,
transaction : Object . assign ( { } , tx , { blockTag , enableCcipRead : true } )
} ) ;
2022-09-27 10:45:27 +03:00
// This came in as a PerformActionTransaction, so to/from are safe; we can cast
2022-09-05 23:57:11 +03:00
const transaction = copyRequest ( tx ) ;
try {
return hexlify ( await this . _perform ( { method : "call" , transaction , blockTag } ) ) ;
}
catch ( error ) {
// CCIP Read OffchainLookup
2022-10-20 12:03:32 +03:00
if ( ! this . disableCcipRead && isCallException ( error ) && error . data && attempt >= 0 && blockTag === "latest" && transaction . to != null && dataSlice ( error . data , 0 , 4 ) === "0x556f1830" ) {
2022-09-05 23:57:11 +03:00
const data = error . data ;
const txSender = await resolveAddress ( transaction . to , this ) ;
// Parse the CCIP Read Arguments
let ccipArgs ;
try {
ccipArgs = parseOffchainLookup ( dataSlice ( error . data , 4 ) ) ;
}
catch ( error ) {
2022-11-09 10:57:02 +03:00
assert ( false , error . message , "OFFCHAIN_FAULT" , {
reason : "BAD_DATA" , transaction , info : { data }
2022-09-05 23:57:11 +03:00
} ) ;
}
// Check the sender of the OffchainLookup matches the transaction
2022-11-09 10:57:02 +03:00
assert ( ccipArgs . sender . toLowerCase ( ) === txSender . toLowerCase ( ) , "CCIP Read sender mismatch" , "CALL_EXCEPTION" , {
action : "call" ,
data ,
reason : "OffchainLookup" ,
transaction : transaction ,
invocation : null ,
revert : {
signature : "OffchainLookup(address,string[],bytes,bytes4,bytes)" ,
name : "OffchainLookup" ,
args : ccipArgs . errorArgs
}
} ) ;
2022-09-05 23:57:11 +03:00
const ccipResult = await this . ccipReadFetch ( transaction , ccipArgs . calldata , ccipArgs . urls ) ;
2022-11-09 10:57:02 +03:00
assert ( ccipResult != null , "CCIP Read failed to fetch data" , "OFFCHAIN_FAULT" , {
reason : "FETCH_FAILED" , transaction , info : { data : error . data , errorArgs : ccipArgs . errorArgs }
} ) ;
const tx = {
2022-09-05 23:57:11 +03:00
to : txSender ,
2022-11-09 10:57:02 +03:00
data : concat ( [ ccipArgs . selector , encodeBytes ( [ ccipResult , ccipArgs . extraData ] ) ] )
} ;
this . emit ( "debug" , { action : "sendCcipReadCall" , transaction : tx } ) ;
try {
const result = await this . # call ( tx , blockTag , attempt + 1 ) ;
this . emit ( "debug" , { action : "receiveCcipReadCallResult" , transaction : Object . assign ( { } , tx ) , result } ) ;
return result ;
}
catch ( error ) {
this . emit ( "debug" , { action : "receiveCcipReadCallError" , transaction : Object . assign ( { } , tx ) , error } ) ;
throw error ;
}
2022-09-05 23:57:11 +03:00
}
throw error ;
}
}
2022-09-27 10:45:27 +03:00
async # checkNetwork ( promise ) {
const { value } = await resolveProperties ( {
network : this . getNetwork ( ) ,
value : promise
} ) ;
return value ;
}
2022-09-05 23:57:11 +03:00
async call ( _tx ) {
2022-09-27 10:45:27 +03:00
const { tx , blockTag } = await resolveProperties ( {
tx : this . _getTransactionRequest ( _tx ) ,
blockTag : this . _getBlockTag ( _tx . blockTag )
} ) ;
return await this . # checkNetwork ( this . # call ( tx , blockTag , _tx . enableCcipRead ? 0 : - 1 ) ) ;
2022-09-05 23:57:11 +03:00
}
// Account
async # getAccountValue ( request , _address , _blockTag ) {
let address = this . _getAddress ( _address ) ;
let blockTag = this . _getBlockTag ( _blockTag ) ;
if ( typeof ( address ) !== "string" || typeof ( blockTag ) !== "string" ) {
[ address , blockTag ] = await Promise . all ( [ address , blockTag ] ) ;
}
2022-09-27 10:45:27 +03:00
return await this . # checkNetwork ( this . # perform ( Object . assign ( request , { address , blockTag } ) ) ) ;
2022-09-05 23:57:11 +03:00
}
async getBalance ( address , blockTag ) {
2022-09-16 05:58:45 +03:00
return getBigInt ( await this . # getAccountValue ( { method : "getBalance" } , address , blockTag ) , "%response" ) ;
2022-09-05 23:57:11 +03:00
}
async getTransactionCount ( address , blockTag ) {
2022-09-16 05:58:45 +03:00
return getNumber ( await this . # getAccountValue ( { method : "getTransactionCount" } , address , blockTag ) , "%response" ) ;
2022-09-05 23:57:11 +03:00
}
async getCode ( address , blockTag ) {
return hexlify ( await this . # getAccountValue ( { method : "getCode" } , address , blockTag ) ) ;
}
2022-09-30 05:57:27 +03:00
async getStorage ( address , _position , blockTag ) {
2022-09-16 05:58:45 +03:00
const position = getBigInt ( _position , "position" ) ;
2022-09-30 05:57:27 +03:00
return hexlify ( await this . # getAccountValue ( { method : "getStorage" , position } , address , blockTag ) ) ;
2022-09-05 23:57:11 +03:00
}
// Write
async broadcastTransaction ( signedTx ) {
2022-10-20 12:03:32 +03:00
const { blockNumber , hash , network } = await resolveProperties ( {
blockNumber : this . getBlockNumber ( ) ,
hash : this . _perform ( {
method : "broadcastTransaction" ,
signedTransaction : signedTx
} ) ,
network : this . getNetwork ( )
} ) ;
const tx = Transaction . from ( signedTx ) ;
if ( tx . hash !== hash ) {
throw new Error ( "@TODO: the returned hash did not match" ) ;
}
return this . _wrapTransactionResponse ( tx , network ) . replaceableTransaction ( blockNumber ) ;
2022-09-05 23:57:11 +03:00
}
async # getBlock ( block , includeTransactions ) {
2022-09-27 10:45:27 +03:00
// @TODO: Add CustomBlockPlugin check
2022-09-05 23:57:11 +03:00
if ( isHexString ( block , 32 ) ) {
return await this . # perform ( {
method : "getBlock" , blockHash : block , includeTransactions
} ) ;
}
let blockTag = this . _getBlockTag ( block ) ;
if ( typeof ( blockTag ) !== "string" ) {
blockTag = await blockTag ;
}
return await this . # perform ( {
method : "getBlock" , blockTag , includeTransactions
} ) ;
}
// Queries
2022-11-30 23:44:23 +03:00
async getBlock ( block , prefetchTxs ) {
2022-09-27 10:45:27 +03:00
const { network , params } = await resolveProperties ( {
network : this . getNetwork ( ) ,
2022-11-30 23:44:23 +03:00
params : this . # getBlock ( block , ! ! prefetchTxs )
2022-09-27 10:45:27 +03:00
} ) ;
2022-09-05 23:57:11 +03:00
if ( params == null ) {
return null ;
}
2023-03-20 19:53:37 +03:00
return this . _wrapBlock ( params , network ) ;
2022-09-05 23:57:11 +03:00
}
async getTransaction ( hash ) {
2022-09-27 10:45:27 +03:00
const { network , params } = await resolveProperties ( {
network : this . getNetwork ( ) ,
params : this . # perform ( { method : "getTransaction" , hash } )
} ) ;
if ( params == null ) {
return null ;
}
2023-03-20 19:53:37 +03:00
return this . _wrapTransactionResponse ( params , network ) ;
2022-09-05 23:57:11 +03:00
}
async getTransactionReceipt ( hash ) {
2022-09-27 10:45:27 +03:00
const { network , params } = await resolveProperties ( {
network : this . getNetwork ( ) ,
params : this . # perform ( { method : "getTransactionReceipt" , hash } )
} ) ;
if ( params == null ) {
2022-09-05 23:57:11 +03:00
return null ;
}
// Some backends did not backfill the effectiveGasPrice into old transactions
// in the receipt, so we look it up manually and inject it.
2022-09-27 10:45:27 +03:00
if ( params . gasPrice == null && params . effectiveGasPrice == null ) {
2022-09-05 23:57:11 +03:00
const tx = await this . # perform ( { method : "getTransaction" , hash } ) ;
2022-09-27 10:45:27 +03:00
if ( tx == null ) {
throw new Error ( "report this; could not find tx or effectiveGasPrice" ) ;
}
params . effectiveGasPrice = tx . gasPrice ;
2022-09-05 23:57:11 +03:00
}
2023-03-20 19:53:37 +03:00
return this . _wrapTransactionReceipt ( params , network ) ;
2022-09-05 23:57:11 +03:00
}
async getTransactionResult ( hash ) {
2022-09-27 10:45:27 +03:00
const { result } = await resolveProperties ( {
network : this . getNetwork ( ) ,
result : this . # perform ( { method : "getTransactionResult" , hash } )
} ) ;
2022-09-05 23:57:11 +03:00
if ( result == null ) {
return null ;
}
return hexlify ( result ) ;
}
// Bloom-filter Queries
async getLogs ( _filter ) {
2022-09-27 10:45:27 +03:00
let filter = this . _getFilter ( _filter ) ;
if ( isPromise ( filter ) ) {
filter = await filter ;
}
const { network , params } = await resolveProperties ( {
2022-09-05 23:57:11 +03:00
network : this . getNetwork ( ) ,
2022-09-27 10:45:27 +03:00
params : this . # perform ( { method : "getLogs" , filter } )
2022-09-05 23:57:11 +03:00
} ) ;
2023-03-20 19:53:37 +03:00
return params . map ( ( p ) => this . _wrapLog ( p , network ) ) ;
2022-09-05 23:57:11 +03:00
}
// ENS
_getProvider ( chainId ) {
2022-11-09 10:57:02 +03:00
assert ( false , "provider cannot connect to target network" , "UNSUPPORTED_OPERATION" , {
2022-09-05 23:57:11 +03:00
operation : "_getProvider()"
} ) ;
}
async getResolver ( name ) {
return await EnsResolver . fromName ( this , name ) ;
}
async getAvatar ( name ) {
const resolver = await this . getResolver ( name ) ;
if ( resolver ) {
return await resolver . getAvatar ( ) ;
}
return null ;
}
async resolveName ( name ) {
const resolver = await this . getResolver ( name ) ;
if ( resolver ) {
return await resolver . getAddress ( ) ;
}
return null ;
}
async lookupAddress ( address ) {
2023-02-02 12:05:47 +03:00
address = getAddress ( address ) ;
const node = namehash ( address . substring ( 2 ) . toLowerCase ( ) + ".addr.reverse" ) ;
try {
const ensAddr = await EnsResolver . getEnsAddress ( this ) ;
const ensContract = new Contract ( ensAddr , [
"function resolver(bytes32) view returns (address)"
] , this ) ;
const resolver = await ensContract . resolver ( node ) ;
2023-06-02 00:52:58 +03:00
if ( resolver == null || resolver === ZeroAddress ) {
2023-02-02 12:05:47 +03:00
return null ;
}
const resolverContract = new Contract ( resolver , [
"function name(bytes32) view returns (string)"
] , this ) ;
const name = await resolverContract . name ( node ) ;
2023-02-16 16:19:59 +03:00
// Failed forward resolution
2023-02-02 12:05:47 +03:00
const check = await this . resolveName ( name ) ;
if ( check !== address ) {
2023-02-16 16:19:59 +03:00
return null ;
2023-02-02 12:05:47 +03:00
}
return name ;
}
catch ( error ) {
2023-02-16 16:19:59 +03:00
// No data was returned from the resolver
if ( isError ( error , "BAD_DATA" ) && error . value === "0x" ) {
return null ;
}
// Something reerted
if ( isError ( error , "CALL_EXCEPTION" ) ) {
return null ;
}
throw error ;
2023-02-02 12:05:47 +03:00
}
return null ;
2022-09-05 23:57:11 +03:00
}
2022-11-09 10:57:02 +03:00
async waitForTransaction ( hash , _confirms , timeout ) {
const confirms = ( _confirms != null ) ? _confirms : 1 ;
2022-09-05 23:57:11 +03:00
if ( confirms === 0 ) {
return this . getTransactionReceipt ( hash ) ;
}
return new Promise ( async ( resolve , reject ) => {
let timer = null ;
const listener = ( async ( blockNumber ) => {
try {
const receipt = await this . getTransactionReceipt ( hash ) ;
if ( receipt != null ) {
if ( blockNumber - receipt . blockNumber + 1 >= confirms ) {
resolve ( receipt ) ;
2022-11-09 10:57:02 +03:00
//this.off("block", listener);
2022-09-05 23:57:11 +03:00
if ( timer ) {
clearTimeout ( timer ) ;
timer = null ;
}
return ;
}
}
}
catch ( error ) {
console . log ( "EEE" , error ) ;
}
this . once ( "block" , listener ) ;
} ) ;
if ( timeout != null ) {
timer = setTimeout ( ( ) => {
if ( timer == null ) {
return ;
}
timer = null ;
this . off ( "block" , listener ) ;
2022-09-16 05:58:45 +03:00
reject ( makeError ( "timeout" , "TIMEOUT" , { reason : "timeout" } ) ) ;
2022-09-05 23:57:11 +03:00
} , timeout ) ;
}
listener ( await this . getBlockNumber ( ) ) ;
} ) ;
}
async waitForBlock ( blockTag ) {
2023-02-02 12:05:47 +03:00
assert ( false , "not implemented yet" , "NOT_IMPLEMENTED" , {
operation : "waitForBlock"
} ) ;
2022-09-05 23:57:11 +03:00
}
2023-06-02 00:52:58 +03:00
/ * *
* Clear a timer created using the [ [ _setTimeout ] ] method .
* /
2022-09-05 23:57:11 +03:00
_clearTimeout ( timerId ) {
const timer = this . # timers . get ( timerId ) ;
if ( ! timer ) {
return ;
}
if ( timer . timer ) {
clearTimeout ( timer . timer ) ;
}
this . # timers . delete ( timerId ) ;
}
2023-06-02 00:52:58 +03:00
/ * *
* Create a timer that will execute % % func % % after at least % % timeout % %
* ( in ms ) . If % % timeout % % is unspecified , then % % func % % will execute
* in the next event loop .
*
* [ Pausing ] ( AbstractProvider - paused ) the provider will pause any
* associated timers .
* /
2022-11-30 23:44:23 +03:00
_setTimeout ( _func , timeout ) {
if ( timeout == null ) {
timeout = 0 ;
}
2022-09-05 23:57:11 +03:00
const timerId = this . # nextTimer ++ ;
const func = ( ) => {
this . # timers . delete ( timerId ) ;
_func ( ) ;
} ;
if ( this . paused ) {
this . # timers . set ( timerId , { timer : null , func , time : timeout } ) ;
}
else {
const timer = setTimeout ( func , timeout ) ;
this . # timers . set ( timerId , { timer , func , time : getTime ( ) } ) ;
}
return timerId ;
}
2023-06-02 00:52:58 +03:00
/ * *
* Perform % % func % % on each subscriber .
* /
2022-09-05 23:57:11 +03:00
_forEachSubscriber ( func ) {
for ( const sub of this . # subs . values ( ) ) {
func ( sub . subscriber ) ;
}
}
2023-06-02 00:52:58 +03:00
/ * *
* Sub - classes may override this to customize subscription
* implementations .
* /
2022-09-05 23:57:11 +03:00
_getSubscriber ( sub ) {
switch ( sub . type ) {
case "debug" :
2023-04-25 14:04:48 +03:00
case "error" :
2022-09-05 23:57:11 +03:00
case "network" :
return new UnmanagedSubscriber ( sub . type ) ;
2023-07-29 09:37:03 +03:00
case "block" : {
const subscriber = new PollingBlockSubscriber ( this ) ;
subscriber . pollingInterval = this . pollingInterval ;
return subscriber ;
}
2023-11-27 14:11:49 +03:00
case "safe" :
case "finalized" :
return new PollingBlockTagSubscriber ( this , sub . type ) ;
2022-09-05 23:57:11 +03:00
case "event" :
return new PollingEventSubscriber ( this , sub . filter ) ;
case "transaction" :
return new PollingTransactionSubscriber ( this , sub . hash ) ;
case "orphan" :
return new PollingOrphanSubscriber ( this , sub . filter ) ;
}
throw new Error ( ` unsupported event: ${ sub . type } ` ) ;
}
2023-06-02 00:52:58 +03:00
/ * *
* If a [ [ Subscriber ] ] fails and needs to replace itself , this
* method may be used .
*
* For example , this is used for providers when using the
* ` ` eth _getFilterChanges ` ` method , which can return null if state
* filters are not supported by the backend , allowing the Subscriber
* to swap in a [ [ PollingEventSubscriber ] ] .
* /
2022-09-05 23:57:11 +03:00
_recoverSubscriber ( oldSub , newSub ) {
for ( const sub of this . # subs . values ( ) ) {
if ( sub . subscriber === oldSub ) {
if ( sub . started ) {
sub . subscriber . stop ( ) ;
}
sub . subscriber = newSub ;
if ( sub . started ) {
newSub . start ( ) ;
}
if ( this . # pausedState != null ) {
newSub . pause ( this . # pausedState ) ;
}
break ;
}
}
}
async # hasSub ( event , emitArgs ) {
let sub = await getSubscription ( event , this ) ;
// This is a log that is removing an existing log; we actually want
// to emit an orphan event for the removed log
if ( sub . type === "event" && emitArgs && emitArgs . length > 0 && emitArgs [ 0 ] . removed === true ) {
sub = await getSubscription ( { orphan : "drop-log" , log : emitArgs [ 0 ] } , this ) ;
}
return this . # subs . get ( sub . tag ) || null ;
}
async # getSub ( event ) {
const subscription = await getSubscription ( event , this ) ;
// Prevent tampering with our tag in any subclass' _getSubscriber
const tag = subscription . tag ;
let sub = this . # subs . get ( tag ) ;
if ( ! sub ) {
const subscriber = this . _getSubscriber ( subscription ) ;
const addressableMap = new WeakMap ( ) ;
const nameMap = new Map ( ) ;
sub = { subscriber , tag , addressableMap , nameMap , started : false , listeners : [ ] } ;
this . # subs . set ( tag , sub ) ;
}
return sub ;
}
async on ( event , listener ) {
const sub = await this . # getSub ( event ) ;
sub . listeners . push ( { listener , once : false } ) ;
if ( ! sub . started ) {
sub . subscriber . start ( ) ;
sub . started = true ;
if ( this . # pausedState != null ) {
sub . subscriber . pause ( this . # pausedState ) ;
}
}
return this ;
}
async once ( event , listener ) {
const sub = await this . # getSub ( event ) ;
sub . listeners . push ( { listener , once : true } ) ;
if ( ! sub . started ) {
sub . subscriber . start ( ) ;
sub . started = true ;
if ( this . # pausedState != null ) {
sub . subscriber . pause ( this . # pausedState ) ;
}
}
return this ;
}
async emit ( event , ... args ) {
const sub = await this . # hasSub ( event , args ) ;
2023-02-02 12:05:47 +03:00
// If there is not subscription or if a recent emit removed
// the last of them (which also deleted the sub) do nothing
if ( ! sub || sub . listeners . length === 0 ) {
2022-09-05 23:57:11 +03:00
return false ;
}
;
const count = sub . listeners . length ;
sub . listeners = sub . listeners . filter ( ( { listener , once } ) => {
const payload = new EventPayload ( this , ( once ? null : listener ) , event ) ;
try {
listener . call ( this , ... args , payload ) ;
}
catch ( error ) { }
return ! once ;
} ) ;
2022-11-09 10:57:02 +03:00
if ( sub . listeners . length === 0 ) {
if ( sub . started ) {
sub . subscriber . stop ( ) ;
}
this . # subs . delete ( sub . tag ) ;
}
2022-09-05 23:57:11 +03:00
return ( count > 0 ) ;
}
async listenerCount ( event ) {
if ( event ) {
const sub = await this . # hasSub ( event ) ;
if ( ! sub ) {
return 0 ;
}
return sub . listeners . length ;
}
let total = 0 ;
for ( const { listeners } of this . # subs . values ( ) ) {
total += listeners . length ;
}
return total ;
}
async listeners ( event ) {
if ( event ) {
const sub = await this . # hasSub ( event ) ;
if ( ! sub ) {
return [ ] ;
}
return sub . listeners . map ( ( { listener } ) => listener ) ;
}
let result = [ ] ;
for ( const { listeners } of this . # subs . values ( ) ) {
result = result . concat ( listeners . map ( ( { listener } ) => listener ) ) ;
}
return result ;
}
async off ( event , listener ) {
const sub = await this . # hasSub ( event ) ;
if ( ! sub ) {
return this ;
}
if ( listener ) {
const index = sub . listeners . map ( ( { listener } ) => listener ) . indexOf ( listener ) ;
if ( index >= 0 ) {
sub . listeners . splice ( index , 1 ) ;
}
}
if ( ! listener || sub . listeners . length === 0 ) {
if ( sub . started ) {
sub . subscriber . stop ( ) ;
}
this . # subs . delete ( sub . tag ) ;
}
return this ;
}
async removeAllListeners ( event ) {
if ( event ) {
const { tag , started , subscriber } = await this . # getSub ( event ) ;
if ( started ) {
subscriber . stop ( ) ;
}
this . # subs . delete ( tag ) ;
}
else {
for ( const [ tag , { started , subscriber } ] of this . # subs ) {
if ( started ) {
subscriber . stop ( ) ;
}
this . # subs . delete ( tag ) ;
}
}
return this ;
}
// Alias for "on"
async addListener ( event , listener ) {
return await this . on ( event , listener ) ;
}
// Alias for "off"
async removeListener ( event , listener ) {
return this . off ( event , listener ) ;
}
2023-06-07 05:42:28 +03:00
/ * *
* If this provider has been destroyed using the [ [ destroy ] ] method .
*
* Once destroyed , all resources are reclaimed , internal event loops
* and timers are cleaned up and no further requests may be sent to
* the provider .
* /
get destroyed ( ) {
return this . # destroyed ;
}
2023-06-02 00:52:58 +03:00
/ * *
* Sub - classes may use this to shutdown any sockets or release their
2023-06-07 05:42:28 +03:00
* resources and reject any pending requests .
2023-06-02 00:52:58 +03:00
*
* Sub - classes * * must * * call ` ` super . destroy ( ) ` ` .
* /
2022-12-31 00:35:04 +03:00
destroy ( ) {
2022-09-05 23:57:11 +03:00
// Stop all listeners
this . removeAllListeners ( ) ;
// Shut down all tiemrs
for ( const timerId of this . # timers . keys ( ) ) {
this . _clearTimeout ( timerId ) ;
}
2023-06-07 05:42:28 +03:00
this . # destroyed = true ;
2022-09-05 23:57:11 +03:00
}
2023-06-02 00:52:58 +03:00
/ * *
* Whether the provider is currently paused .
*
* A paused provider will not emit any events , and generally should
* not make any requests to the network , but that is up to sub - classes
* to manage .
*
* Setting ` ` paused = true ` ` is identical to calling ` ` . pause ( false ) ` ` ,
* which will buffer any events that occur while paused until the
* provider is unpaused .
* /
2022-09-05 23:57:11 +03:00
get paused ( ) { return ( this . # pausedState != null ) ; }
set paused ( pause ) {
if ( ! ! pause === this . paused ) {
return ;
}
if ( this . paused ) {
this . resume ( ) ;
}
else {
this . pause ( false ) ;
}
}
2023-06-02 00:52:58 +03:00
/ * *
* Pause the provider . If % % dropWhilePaused % % , any events that occur
* while paused are dropped , otherwise all events will be emitted once
* the provider is unpaused .
* /
2022-09-05 23:57:11 +03:00
pause ( dropWhilePaused ) {
2022-09-27 10:45:27 +03:00
this . # lastBlockNumber = - 1 ;
2022-09-05 23:57:11 +03:00
if ( this . # pausedState != null ) {
if ( this . # pausedState == ! ! dropWhilePaused ) {
return ;
}
2022-11-09 10:57:02 +03:00
assert ( false , "cannot change pause type; resume first" , "UNSUPPORTED_OPERATION" , {
2022-09-05 23:57:11 +03:00
operation : "pause"
} ) ;
}
this . _forEachSubscriber ( ( s ) => s . pause ( dropWhilePaused ) ) ;
this . # pausedState = ! ! dropWhilePaused ;
for ( const timer of this . # timers . values ( ) ) {
// Clear the timer
if ( timer . timer ) {
clearTimeout ( timer . timer ) ;
}
// Remaining time needed for when we become unpaused
timer . time = getTime ( ) - timer . time ;
}
}
2023-06-02 00:52:58 +03:00
/ * *
* Resume the provider .
* /
2022-09-05 23:57:11 +03:00
resume ( ) {
if ( this . # pausedState == null ) {
return ;
}
this . _forEachSubscriber ( ( s ) => s . resume ( ) ) ;
this . # pausedState = null ;
for ( const timer of this . # timers . values ( ) ) {
// Remaining time when we were paused
let timeout = timer . time ;
if ( timeout < 0 ) {
timeout = 0 ;
}
// Start time (in cause paused, so we con compute remaininf time)
timer . time = getTime ( ) ;
// Start the timer
setTimeout ( timer . func , timeout ) ;
}
}
}
function _parseString ( result , start ) {
try {
const bytes = _parseBytes ( result , start ) ;
if ( bytes ) {
return toUtf8String ( bytes ) ;
}
}
catch ( error ) { }
return null ;
}
function _parseBytes ( result , start ) {
if ( result === "0x" ) {
return null ;
}
try {
2022-09-16 05:58:45 +03:00
const offset = getNumber ( dataSlice ( result , start , start + 32 ) ) ;
const length = getNumber ( dataSlice ( result , offset , offset + 32 ) ) ;
2022-09-05 23:57:11 +03:00
return dataSlice ( result , offset + 32 , offset + 32 + length ) ;
}
catch ( error ) { }
return null ;
}
function numPad ( value ) {
2022-12-10 02:24:58 +03:00
const result = toBeArray ( value ) ;
2022-09-05 23:57:11 +03:00
if ( result . length > 32 ) {
throw new Error ( "internal; should not happen" ) ;
}
const padded = new Uint8Array ( 32 ) ;
padded . set ( result , 32 - result . length ) ;
return padded ;
}
function bytesPad ( value ) {
if ( ( value . length % 32 ) === 0 ) {
return value ;
}
const result = new Uint8Array ( Math . ceil ( value . length / 32 ) * 32 ) ;
result . set ( value ) ;
return result ;
}
const empty = new Uint8Array ( [ ] ) ;
// ABI Encodes a series of (bytes, bytes, ...)
function encodeBytes ( datas ) {
const result = [ ] ;
let byteCount = 0 ;
// Add place-holders for pointers as we add items
for ( let i = 0 ; i < datas . length ; i ++ ) {
result . push ( empty ) ;
byteCount += 32 ;
}
for ( let i = 0 ; i < datas . length ; i ++ ) {
2022-09-16 05:58:45 +03:00
const data = getBytes ( datas [ i ] ) ;
2022-09-05 23:57:11 +03:00
// Update the bytes offset
result [ i ] = numPad ( byteCount ) ;
// The length and padded value of data
result . push ( numPad ( data . length ) ) ;
result . push ( bytesPad ( data ) ) ;
byteCount += 32 + Math . ceil ( data . length / 32 ) * 32 ;
}
return concat ( result ) ;
}
const zeros = "0x0000000000000000000000000000000000000000000000000000000000000000" ;
function parseOffchainLookup ( data ) {
const result = {
sender : "" , urls : [ ] , calldata : "" , selector : "" , extraData : "" , errorArgs : [ ]
} ;
2023-01-28 09:53:29 +03:00
assert ( dataLength ( data ) >= 5 * 32 , "insufficient OffchainLookup data" , "OFFCHAIN_FAULT" , {
reason : "insufficient OffchainLookup data"
} ) ;
2022-09-05 23:57:11 +03:00
const sender = dataSlice ( data , 0 , 32 ) ;
2023-01-28 09:53:29 +03:00
assert ( dataSlice ( sender , 0 , 12 ) === dataSlice ( zeros , 0 , 12 ) , "corrupt OffchainLookup sender" , "OFFCHAIN_FAULT" , {
reason : "corrupt OffchainLookup sender"
} ) ;
2022-09-05 23:57:11 +03:00
result . sender = dataSlice ( sender , 12 ) ;
// Read the URLs from the response
try {
const urls = [ ] ;
2022-09-16 05:58:45 +03:00
const urlsOffset = getNumber ( dataSlice ( data , 32 , 64 ) ) ;
const urlsLength = getNumber ( dataSlice ( data , urlsOffset , urlsOffset + 32 ) ) ;
2022-09-05 23:57:11 +03:00
const urlsData = dataSlice ( data , urlsOffset + 32 ) ;
for ( let u = 0 ; u < urlsLength ; u ++ ) {
const url = _parseString ( urlsData , u * 32 ) ;
if ( url == null ) {
throw new Error ( "abort" ) ;
}
urls . push ( url ) ;
}
result . urls = urls ;
}
catch ( error ) {
2023-01-28 09:53:29 +03:00
assert ( false , "corrupt OffchainLookup urls" , "OFFCHAIN_FAULT" , {
reason : "corrupt OffchainLookup urls"
} ) ;
2022-09-05 23:57:11 +03:00
}
// Get the CCIP calldata to forward
try {
const calldata = _parseBytes ( data , 64 ) ;
if ( calldata == null ) {
throw new Error ( "abort" ) ;
}
result . calldata = calldata ;
}
catch ( error ) {
2023-01-28 09:53:29 +03:00
assert ( false , "corrupt OffchainLookup calldata" , "OFFCHAIN_FAULT" , {
reason : "corrupt OffchainLookup calldata"
} ) ;
2022-09-05 23:57:11 +03:00
}
// Get the callbackSelector (bytes4)
2023-01-28 09:53:29 +03:00
assert ( dataSlice ( data , 100 , 128 ) === dataSlice ( zeros , 0 , 28 ) , "corrupt OffchainLookup callbaackSelector" , "OFFCHAIN_FAULT" , {
reason : "corrupt OffchainLookup callbaackSelector"
} ) ;
2022-09-05 23:57:11 +03:00
result . selector = dataSlice ( data , 96 , 100 ) ;
// Get the extra data to send back to the contract as context
try {
const extraData = _parseBytes ( data , 128 ) ;
if ( extraData == null ) {
throw new Error ( "abort" ) ;
}
result . extraData = extraData ;
}
catch ( error ) {
2023-01-28 09:53:29 +03:00
assert ( false , "corrupt OffchainLookup extraData" , "OFFCHAIN_FAULT" , {
reason : "corrupt OffchainLookup extraData"
} ) ;
2022-09-05 23:57:11 +03:00
}
result . errorArgs = "sender,urls,calldata,selector,extraData" . split ( /,/ ) . map ( ( k ) => result [ k ] ) ;
return result ;
}
//# sourceMappingURL=abstract-provider.js.map