123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980 |
- "use strict";
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
- return new (P || (P = Promise))(function (resolve, reject) {
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
- step((generator = generator.apply(thisArg, _arguments || [])).next());
- });
- };
- import { ForkEvent, Provider } from "@ethersproject/abstract-provider";
- import { Base58 } from "@ethersproject/basex";
- import { BigNumber } from "@ethersproject/bignumber";
- import { arrayify, concat, hexConcat, hexDataLength, hexDataSlice, hexlify, hexValue, hexZeroPad, isHexString } from "@ethersproject/bytes";
- import { HashZero } from "@ethersproject/constants";
- import { dnsEncode, namehash } from "@ethersproject/hash";
- import { getNetwork } from "@ethersproject/networks";
- import { defineReadOnly, getStatic, resolveProperties } from "@ethersproject/properties";
- import { sha256 } from "@ethersproject/sha2";
- import { toUtf8Bytes, toUtf8String } from "@ethersproject/strings";
- import { fetchJson, poll } from "@ethersproject/web";
- import bech32 from "bech32";
- import { Logger } from "@ethersproject/logger";
- import { version } from "./_version";
- const logger = new Logger(version);
- import { Formatter } from "./formatter";
- const MAX_CCIP_REDIRECTS = 10;
- //////////////////////////////
- // Event Serializeing
- function checkTopic(topic) {
- if (topic == null) {
- return "null";
- }
- if (hexDataLength(topic) !== 32) {
- logger.throwArgumentError("invalid topic", "topic", topic);
- }
- return topic.toLowerCase();
- }
- function serializeTopics(topics) {
- // Remove trailing null AND-topics; they are redundant
- topics = topics.slice();
- while (topics.length > 0 && topics[topics.length - 1] == null) {
- topics.pop();
- }
- return topics.map((topic) => {
- if (Array.isArray(topic)) {
- // Only track unique OR-topics
- const unique = {};
- topic.forEach((topic) => {
- unique[checkTopic(topic)] = true;
- });
- // The order of OR-topics does not matter
- const sorted = Object.keys(unique);
- sorted.sort();
- return sorted.join("|");
- }
- else {
- return checkTopic(topic);
- }
- }).join("&");
- }
- function deserializeTopics(data) {
- if (data === "") {
- return [];
- }
- return data.split(/&/g).map((topic) => {
- if (topic === "") {
- return [];
- }
- const comps = topic.split("|").map((topic) => {
- return ((topic === "null") ? null : topic);
- });
- return ((comps.length === 1) ? comps[0] : comps);
- });
- }
- function getEventTag(eventName) {
- if (typeof (eventName) === "string") {
- eventName = eventName.toLowerCase();
- if (hexDataLength(eventName) === 32) {
- return "tx:" + eventName;
- }
- if (eventName.indexOf(":") === -1) {
- return eventName;
- }
- }
- else if (Array.isArray(eventName)) {
- return "filter:*:" + serializeTopics(eventName);
- }
- else if (ForkEvent.isForkEvent(eventName)) {
- logger.warn("not implemented");
- throw new Error("not implemented");
- }
- else if (eventName && typeof (eventName) === "object") {
- return "filter:" + (eventName.address || "*") + ":" + serializeTopics(eventName.topics || []);
- }
- throw new Error("invalid event - " + eventName);
- }
- //////////////////////////////
- // Helper Object
- function getTime() {
- return (new Date()).getTime();
- }
- function stall(duration) {
- return new Promise((resolve) => {
- setTimeout(resolve, duration);
- });
- }
- //////////////////////////////
- // Provider Object
- /**
- * EventType
- * - "block"
- * - "poll"
- * - "didPoll"
- * - "pending"
- * - "error"
- * - "network"
- * - filter
- * - topics array
- * - transaction hash
- */
- const PollableEvents = ["block", "network", "pending", "poll"];
- export class Event {
- constructor(tag, listener, once) {
- defineReadOnly(this, "tag", tag);
- defineReadOnly(this, "listener", listener);
- defineReadOnly(this, "once", once);
- this._lastBlockNumber = -2;
- this._inflight = false;
- }
- get event() {
- switch (this.type) {
- case "tx":
- return this.hash;
- case "filter":
- return this.filter;
- }
- return this.tag;
- }
- get type() {
- return this.tag.split(":")[0];
- }
- get hash() {
- const comps = this.tag.split(":");
- if (comps[0] !== "tx") {
- return null;
- }
- return comps[1];
- }
- get filter() {
- const comps = this.tag.split(":");
- if (comps[0] !== "filter") {
- return null;
- }
- const address = comps[1];
- const topics = deserializeTopics(comps[2]);
- const filter = {};
- if (topics.length > 0) {
- filter.topics = topics;
- }
- if (address && address !== "*") {
- filter.address = address;
- }
- return filter;
- }
- pollable() {
- return (this.tag.indexOf(":") >= 0 || PollableEvents.indexOf(this.tag) >= 0);
- }
- }
- ;
- // https://github.com/satoshilabs/slips/blob/master/slip-0044.md
- const coinInfos = {
- "0": { symbol: "btc", p2pkh: 0x00, p2sh: 0x05, prefix: "bc" },
- "2": { symbol: "ltc", p2pkh: 0x30, p2sh: 0x32, prefix: "ltc" },
- "3": { symbol: "doge", p2pkh: 0x1e, p2sh: 0x16 },
- "60": { symbol: "eth", ilk: "eth" },
- "61": { symbol: "etc", ilk: "eth" },
- "700": { symbol: "xdai", ilk: "eth" },
- };
- function bytes32ify(value) {
- return hexZeroPad(BigNumber.from(value).toHexString(), 32);
- }
- // Compute the Base58Check encoded data (checksum is first 4 bytes of sha256d)
- function base58Encode(data) {
- return Base58.encode(concat([data, hexDataSlice(sha256(sha256(data)), 0, 4)]));
- }
- const matcherIpfs = new RegExp("^(ipfs):/\/(.*)$", "i");
- const matchers = [
- new RegExp("^(https):/\/(.*)$", "i"),
- new RegExp("^(data):(.*)$", "i"),
- matcherIpfs,
- new RegExp("^eip155:[0-9]+/(erc[0-9]+):(.*)$", "i"),
- ];
- function _parseString(result, start) {
- try {
- return toUtf8String(_parseBytes(result, start));
- }
- catch (error) { }
- return null;
- }
- function _parseBytes(result, start) {
- if (result === "0x") {
- return null;
- }
- const offset = BigNumber.from(hexDataSlice(result, start, start + 32)).toNumber();
- const length = BigNumber.from(hexDataSlice(result, offset, offset + 32)).toNumber();
- return hexDataSlice(result, offset + 32, offset + 32 + length);
- }
- // Trim off the ipfs:// prefix and return the default gateway URL
- function getIpfsLink(link) {
- if (link.match(/^ipfs:\/\/ipfs\//i)) {
- link = link.substring(12);
- }
- else if (link.match(/^ipfs:\/\//i)) {
- link = link.substring(7);
- }
- else {
- logger.throwArgumentError("unsupported IPFS format", "link", link);
- }
- return `https:/\/gateway.ipfs.io/ipfs/${link}`;
- }
- function numPad(value) {
- const result = arrayify(value);
- 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;
- }
- // 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(null);
- byteCount += 32;
- }
- for (let i = 0; i < datas.length; i++) {
- const data = arrayify(datas[i]);
- // 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 hexConcat(result);
- }
- export class Resolver {
- // The resolvedAddress is only for creating a ReverseLookup resolver
- constructor(provider, address, name, resolvedAddress) {
- defineReadOnly(this, "provider", provider);
- defineReadOnly(this, "name", name);
- defineReadOnly(this, "address", provider.formatter.address(address));
- defineReadOnly(this, "_resolvedAddress", resolvedAddress);
- }
- supportsWildcard() {
- if (!this._supportsEip2544) {
- // supportsInterface(bytes4 = selector("resolve(bytes,bytes)"))
- this._supportsEip2544 = this.provider.call({
- to: this.address,
- data: "0x01ffc9a79061b92300000000000000000000000000000000000000000000000000000000"
- }).then((result) => {
- return BigNumber.from(result).eq(1);
- }).catch((error) => {
- if (error.code === Logger.errors.CALL_EXCEPTION) {
- return false;
- }
- // Rethrow the error: link is down, etc. Let future attempts retry.
- this._supportsEip2544 = null;
- throw error;
- });
- }
- return this._supportsEip2544;
- }
- _fetch(selector, parameters) {
- return __awaiter(this, void 0, void 0, function* () {
- // e.g. keccak256("addr(bytes32,uint256)")
- const tx = {
- to: this.address,
- ccipReadEnabled: true,
- data: hexConcat([selector, namehash(this.name), (parameters || "0x")])
- };
- // Wildcard support; use EIP-2544 to resolve the request
- let parseBytes = false;
- if (yield this.supportsWildcard()) {
- parseBytes = true;
- // selector("resolve(bytes,bytes)")
- tx.data = hexConcat(["0x9061b923", encodeBytes([dnsEncode(this.name), tx.data])]);
- }
- try {
- let result = yield this.provider.call(tx);
- if ((arrayify(result).length % 32) === 4) {
- logger.throwError("resolver threw error", Logger.errors.CALL_EXCEPTION, {
- transaction: tx, data: result
- });
- }
- if (parseBytes) {
- result = _parseBytes(result, 0);
- }
- return result;
- }
- catch (error) {
- if (error.code === Logger.errors.CALL_EXCEPTION) {
- return null;
- }
- throw error;
- }
- });
- }
- _fetchBytes(selector, parameters) {
- return __awaiter(this, void 0, void 0, function* () {
- const result = yield this._fetch(selector, parameters);
- if (result != null) {
- return _parseBytes(result, 0);
- }
- return null;
- });
- }
- _getAddress(coinType, hexBytes) {
- const coinInfo = coinInfos[String(coinType)];
- if (coinInfo == null) {
- logger.throwError(`unsupported coin type: ${coinType}`, Logger.errors.UNSUPPORTED_OPERATION, {
- operation: `getAddress(${coinType})`
- });
- }
- if (coinInfo.ilk === "eth") {
- return this.provider.formatter.address(hexBytes);
- }
- const bytes = arrayify(hexBytes);
- // P2PKH: OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
- if (coinInfo.p2pkh != null) {
- const p2pkh = hexBytes.match(/^0x76a9([0-9a-f][0-9a-f])([0-9a-f]*)88ac$/);
- if (p2pkh) {
- const length = parseInt(p2pkh[1], 16);
- if (p2pkh[2].length === length * 2 && length >= 1 && length <= 75) {
- return base58Encode(concat([[coinInfo.p2pkh], ("0x" + p2pkh[2])]));
- }
- }
- }
- // P2SH: OP_HASH160 <scriptHash> OP_EQUAL
- if (coinInfo.p2sh != null) {
- const p2sh = hexBytes.match(/^0xa9([0-9a-f][0-9a-f])([0-9a-f]*)87$/);
- if (p2sh) {
- const length = parseInt(p2sh[1], 16);
- if (p2sh[2].length === length * 2 && length >= 1 && length <= 75) {
- return base58Encode(concat([[coinInfo.p2sh], ("0x" + p2sh[2])]));
- }
- }
- }
- // Bech32
- if (coinInfo.prefix != null) {
- const length = bytes[1];
- // https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#witness-program
- let version = bytes[0];
- if (version === 0x00) {
- if (length !== 20 && length !== 32) {
- version = -1;
- }
- }
- else {
- version = -1;
- }
- if (version >= 0 && bytes.length === 2 + length && length >= 1 && length <= 75) {
- const words = bech32.toWords(bytes.slice(2));
- words.unshift(version);
- return bech32.encode(coinInfo.prefix, words);
- }
- }
- return null;
- }
- getAddress(coinType) {
- return __awaiter(this, void 0, void 0, function* () {
- if (coinType == null) {
- coinType = 60;
- }
- // If Ethereum, use the standard `addr(bytes32)`
- if (coinType === 60) {
- try {
- // keccak256("addr(bytes32)")
- const result = yield this._fetch("0x3b3b57de");
- // No address
- if (result === "0x" || result === HashZero) {
- return null;
- }
- return this.provider.formatter.callAddress(result);
- }
- catch (error) {
- if (error.code === Logger.errors.CALL_EXCEPTION) {
- return null;
- }
- throw error;
- }
- }
- // keccak256("addr(bytes32,uint256")
- const hexBytes = yield this._fetchBytes("0xf1cb7e06", bytes32ify(coinType));
- // No address
- if (hexBytes == null || hexBytes === "0x") {
- return null;
- }
- // Compute the address
- const address = this._getAddress(coinType, hexBytes);
- if (address == null) {
- logger.throwError(`invalid or unsupported coin data`, Logger.errors.UNSUPPORTED_OPERATION, {
- operation: `getAddress(${coinType})`,
- coinType: coinType,
- data: hexBytes
- });
- }
- return address;
- });
- }
- getAvatar() {
- return __awaiter(this, void 0, void 0, function* () {
- const linkage = [{ type: "name", content: this.name }];
- try {
- // test data for ricmoo.eth
- //const avatar = "eip155:1/erc721:0x265385c7f4132228A0d54EB1A9e7460b91c0cC68/29233";
- const avatar = yield this.getText("avatar");
- if (avatar == null) {
- return null;
- }
- for (let i = 0; i < matchers.length; i++) {
- const match = avatar.match(matchers[i]);
- if (match == null) {
- continue;
- }
- const scheme = match[1].toLowerCase();
- switch (scheme) {
- case "https":
- linkage.push({ type: "url", content: avatar });
- return { linkage, url: avatar };
- case "data":
- linkage.push({ type: "data", content: avatar });
- return { linkage, url: avatar };
- case "ipfs":
- linkage.push({ type: "ipfs", content: avatar });
- return { linkage, url: getIpfsLink(avatar) };
- case "erc721":
- case "erc1155": {
- // Depending on the ERC type, use tokenURI(uint256) or url(uint256)
- const selector = (scheme === "erc721") ? "0xc87b56dd" : "0x0e89341c";
- linkage.push({ type: scheme, content: avatar });
- // The owner of this name
- const owner = (this._resolvedAddress || (yield this.getAddress()));
- const comps = (match[2] || "").split("/");
- if (comps.length !== 2) {
- return null;
- }
- const addr = yield this.provider.formatter.address(comps[0]);
- const tokenId = hexZeroPad(BigNumber.from(comps[1]).toHexString(), 32);
- // Check that this account owns the token
- if (scheme === "erc721") {
- // ownerOf(uint256 tokenId)
- const tokenOwner = this.provider.formatter.callAddress(yield this.provider.call({
- to: addr, data: hexConcat(["0x6352211e", tokenId])
- }));
- if (owner !== tokenOwner) {
- return null;
- }
- linkage.push({ type: "owner", content: tokenOwner });
- }
- else if (scheme === "erc1155") {
- // balanceOf(address owner, uint256 tokenId)
- const balance = BigNumber.from(yield this.provider.call({
- to: addr, data: hexConcat(["0x00fdd58e", hexZeroPad(owner, 32), tokenId])
- }));
- if (balance.isZero()) {
- return null;
- }
- linkage.push({ type: "balance", content: balance.toString() });
- }
- // Call the token contract for the metadata URL
- const tx = {
- to: this.provider.formatter.address(comps[0]),
- data: hexConcat([selector, tokenId])
- };
- let metadataUrl = _parseString(yield this.provider.call(tx), 0);
- if (metadataUrl == null) {
- return null;
- }
- linkage.push({ type: "metadata-url-base", content: metadataUrl });
- // ERC-1155 allows a generic {id} in the URL
- if (scheme === "erc1155") {
- metadataUrl = metadataUrl.replace("{id}", tokenId.substring(2));
- linkage.push({ type: "metadata-url-expanded", content: metadataUrl });
- }
- // Transform IPFS metadata links
- if (metadataUrl.match(/^ipfs:/i)) {
- metadataUrl = getIpfsLink(metadataUrl);
- }
- linkage.push({ type: "metadata-url", content: metadataUrl });
- // Get the token metadata
- const metadata = yield fetchJson(metadataUrl);
- if (!metadata) {
- return null;
- }
- linkage.push({ type: "metadata", content: JSON.stringify(metadata) });
- // Pull the image URL out
- let imageUrl = metadata.image;
- if (typeof (imageUrl) !== "string") {
- return null;
- }
- if (imageUrl.match(/^(https:\/\/|data:)/i)) {
- // Allow
- }
- else {
- // Transform IPFS link to gateway
- const ipfs = imageUrl.match(matcherIpfs);
- if (ipfs == null) {
- return null;
- }
- linkage.push({ type: "url-ipfs", content: imageUrl });
- imageUrl = getIpfsLink(imageUrl);
- }
- linkage.push({ type: "url", content: imageUrl });
- return { linkage, url: imageUrl };
- }
- }
- }
- }
- catch (error) { }
- return null;
- });
- }
- getContentHash() {
- return __awaiter(this, void 0, void 0, function* () {
- // keccak256("contenthash()")
- const hexBytes = yield this._fetchBytes("0xbc1c58d1");
- // No contenthash
- if (hexBytes == null || hexBytes === "0x") {
- return null;
- }
- // IPFS (CID: 1, Type: DAG-PB)
- const ipfs = hexBytes.match(/^0xe3010170(([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f]*))$/);
- if (ipfs) {
- const length = parseInt(ipfs[3], 16);
- if (ipfs[4].length === length * 2) {
- return "ipfs:/\/" + Base58.encode("0x" + ipfs[1]);
- }
- }
- // Swarm (CID: 1, Type: swarm-manifest; hash/length hard-coded to keccak256/32)
- const swarm = hexBytes.match(/^0xe40101fa011b20([0-9a-f]*)$/);
- if (swarm) {
- if (swarm[1].length === (32 * 2)) {
- return "bzz:/\/" + swarm[1];
- }
- }
- return logger.throwError(`invalid or unsupported content hash data`, Logger.errors.UNSUPPORTED_OPERATION, {
- operation: "getContentHash()",
- data: hexBytes
- });
- });
- }
- getText(key) {
- return __awaiter(this, void 0, void 0, function* () {
- // The key encoded as parameter to fetchBytes
- let keyBytes = toUtf8Bytes(key);
- // The nodehash consumes the first slot, so the string pointer targets
- // offset 64, with the length at offset 64 and data starting at offset 96
- keyBytes = concat([bytes32ify(64), bytes32ify(keyBytes.length), keyBytes]);
- // Pad to word-size (32 bytes)
- if ((keyBytes.length % 32) !== 0) {
- keyBytes = concat([keyBytes, hexZeroPad("0x", 32 - (key.length % 32))]);
- }
- const hexBytes = yield this._fetchBytes("0x59d1d43c", hexlify(keyBytes));
- if (hexBytes == null || hexBytes === "0x") {
- return null;
- }
- return toUtf8String(hexBytes);
- });
- }
- }
- let defaultFormatter = null;
- let nextPollId = 1;
- export class BaseProvider extends Provider {
- /**
- * ready
- *
- * A Promise<Network> that resolves only once the provider is ready.
- *
- * Sub-classes that call the super with a network without a chainId
- * MUST set this. Standard named networks have a known chainId.
- *
- */
- constructor(network) {
- logger.checkNew(new.target, Provider);
- super();
- // Events being listened to
- this._events = [];
- this._emitted = { block: -2 };
- this.disableCcipRead = false;
- this.formatter = new.target.getFormatter();
- // If network is any, this Provider allows the underlying
- // network to change dynamically, and we auto-detect the
- // current network
- defineReadOnly(this, "anyNetwork", (network === "any"));
- if (this.anyNetwork) {
- network = this.detectNetwork();
- }
- if (network instanceof Promise) {
- this._networkPromise = network;
- // Squash any "unhandled promise" errors; that do not need to be handled
- network.catch((error) => { });
- // Trigger initial network setting (async)
- this._ready().catch((error) => { });
- }
- else {
- const knownNetwork = getStatic(new.target, "getNetwork")(network);
- if (knownNetwork) {
- defineReadOnly(this, "_network", knownNetwork);
- this.emit("network", knownNetwork, null);
- }
- else {
- logger.throwArgumentError("invalid network", "network", network);
- }
- }
- this._maxInternalBlockNumber = -1024;
- this._lastBlockNumber = -2;
- this._maxFilterBlockRange = 10;
- this._pollingInterval = 4000;
- this._fastQueryDate = 0;
- }
- _ready() {
- return __awaiter(this, void 0, void 0, function* () {
- if (this._network == null) {
- let network = null;
- if (this._networkPromise) {
- try {
- network = yield this._networkPromise;
- }
- catch (error) { }
- }
- // Try the Provider's network detection (this MUST throw if it cannot)
- if (network == null) {
- network = yield this.detectNetwork();
- }
- // This should never happen; every Provider sub-class should have
- // suggested a network by here (or have thrown).
- if (!network) {
- logger.throwError("no network detected", Logger.errors.UNKNOWN_ERROR, {});
- }
- // Possible this call stacked so do not call defineReadOnly again
- if (this._network == null) {
- if (this.anyNetwork) {
- this._network = network;
- }
- else {
- defineReadOnly(this, "_network", network);
- }
- this.emit("network", network, null);
- }
- }
- return this._network;
- });
- }
- // This will always return the most recently established network.
- // For "any", this can change (a "network" event is emitted before
- // any change is reflected); otherwise this cannot change
- get ready() {
- return poll(() => {
- return this._ready().then((network) => {
- return network;
- }, (error) => {
- // If the network isn't running yet, we will wait
- if (error.code === Logger.errors.NETWORK_ERROR && error.event === "noNetwork") {
- return undefined;
- }
- throw error;
- });
- });
- }
- // @TODO: Remove this and just create a singleton formatter
- static getFormatter() {
- if (defaultFormatter == null) {
- defaultFormatter = new Formatter();
- }
- return defaultFormatter;
- }
- // @TODO: Remove this and just use getNetwork
- static getNetwork(network) {
- return getNetwork((network == null) ? "homestead" : network);
- }
- ccipReadFetch(tx, calldata, urls) {
- return __awaiter(this, void 0, void 0, function* () {
- if (this.disableCcipRead || urls.length === 0) {
- return null;
- }
- const sender = (tx.from || "0x0000000000000000000000000000000000000000").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 = (url.indexOf("{data}") >= 0) ? null : JSON.stringify({ data, sender });
- const result = yield fetchJson({ url: href, errorPassThrough: true }, json, (value, response) => {
- value.status = response.statusCode;
- return value;
- });
- if (result.data) {
- return result.data;
- }
- const errorMessage = (result.message || "unknown error");
- // 4xx indicates the result is not present; stop
- if (result.status >= 400 && result.status < 500) {
- return logger.throwError(`response not found during CCIP fetch: ${errorMessage}`, Logger.errors.SERVER_ERROR, { url, errorMessage });
- }
- // 5xx indicates server issue; try the next url
- errorMessages.push(errorMessage);
- }
- return logger.throwError(`error encountered during CCIP fetch: ${errorMessages.map((m) => JSON.stringify(m)).join(", ")}`, Logger.errors.SERVER_ERROR, {
- urls, errorMessages
- });
- });
- }
- // Fetches the blockNumber, but will reuse any result that is less
- // than maxAge old or has been requested since the last request
- _getInternalBlockNumber(maxAge) {
- return __awaiter(this, void 0, void 0, function* () {
- yield this._ready();
- // Allowing stale data up to maxAge old
- if (maxAge > 0) {
- // While there are pending internal block requests...
- while (this._internalBlockNumber) {
- // ..."remember" which fetch we started with
- const internalBlockNumber = this._internalBlockNumber;
- try {
- // Check the result is not too stale
- const result = yield internalBlockNumber;
- if ((getTime() - result.respTime) <= maxAge) {
- return result.blockNumber;
- }
- // Too old; fetch a new value
- break;
- }
- catch (error) {
- // The fetch rejected; if we are the first to get the
- // rejection, drop through so we replace it with a new
- // fetch; all others blocked will then get that fetch
- // which won't match the one they "remembered" and loop
- if (this._internalBlockNumber === internalBlockNumber) {
- break;
- }
- }
- }
- }
- const reqTime = getTime();
- const checkInternalBlockNumber = resolveProperties({
- blockNumber: this.perform("getBlockNumber", {}),
- networkError: this.getNetwork().then((network) => (null), (error) => (error))
- }).then(({ blockNumber, networkError }) => {
- if (networkError) {
- // Unremember this bad internal block number
- if (this._internalBlockNumber === checkInternalBlockNumber) {
- this._internalBlockNumber = null;
- }
- throw networkError;
- }
- const respTime = getTime();
- blockNumber = BigNumber.from(blockNumber).toNumber();
- if (blockNumber < this._maxInternalBlockNumber) {
- blockNumber = this._maxInternalBlockNumber;
- }
- this._maxInternalBlockNumber = blockNumber;
- this._setFastBlockNumber(blockNumber); // @TODO: Still need this?
- return { blockNumber, reqTime, respTime };
- });
- this._internalBlockNumber = checkInternalBlockNumber;
- // Swallow unhandled exceptions; if needed they are handled else where
- checkInternalBlockNumber.catch((error) => {
- // Don't null the dead (rejected) fetch, if it has already been updated
- if (this._internalBlockNumber === checkInternalBlockNumber) {
- this._internalBlockNumber = null;
- }
- });
- return (yield checkInternalBlockNumber).blockNumber;
- });
- }
- poll() {
- return __awaiter(this, void 0, void 0, function* () {
- const pollId = nextPollId++;
- // Track all running promises, so we can trigger a post-poll once they are complete
- const runners = [];
- let blockNumber = null;
- try {
- blockNumber = yield this._getInternalBlockNumber(100 + this.pollingInterval / 2);
- }
- catch (error) {
- this.emit("error", error);
- return;
- }
- this._setFastBlockNumber(blockNumber);
- // Emit a poll event after we have the latest (fast) block number
- this.emit("poll", pollId, blockNumber);
- // If the block has not changed, meh.
- if (blockNumber === this._lastBlockNumber) {
- this.emit("didPoll", pollId);
- return;
- }
- // First polling cycle, trigger a "block" events
- if (this._emitted.block === -2) {
- this._emitted.block = blockNumber - 1;
- }
- if (Math.abs((this._emitted.block) - blockNumber) > 1000) {
- logger.warn(`network block skew detected; skipping block events (emitted=${this._emitted.block} blockNumber${blockNumber})`);
- this.emit("error", logger.makeError("network block skew detected", Logger.errors.NETWORK_ERROR, {
- blockNumber: blockNumber,
- event: "blockSkew",
- previousBlockNumber: this._emitted.block
- }));
- this.emit("block", blockNumber);
- }
- else {
- // Notify all listener for each block that has passed
- for (let i = this._emitted.block + 1; i <= blockNumber; i++) {
- this.emit("block", i);
- }
- }
- // The emitted block was updated, check for obsolete events
- if (this._emitted.block !== blockNumber) {
- this._emitted.block = blockNumber;
- Object.keys(this._emitted).forEach((key) => {
- // The block event does not expire
- if (key === "block") {
- return;
- }
- // The block we were at when we emitted this event
- const eventBlockNumber = this._emitted[key];
- // We cannot garbage collect pending transactions or blocks here
- // They should be garbage collected by the Provider when setting
- // "pending" events
- if (eventBlockNumber === "pending") {
- return;
- }
- // Evict any transaction hashes or block hashes over 12 blocks
- // old, since they should not return null anyways
- if (blockNumber - eventBlockNumber > 12) {
- delete this._emitted[key];
- }
- });
- }
- // First polling cycle
- if (this._lastBlockNumber === -2) {
- this._lastBlockNumber = blockNumber - 1;
- }
- // Find all transaction hashes we are waiting on
- this._events.forEach((event) => {
- switch (event.type) {
- case "tx": {
- const hash = event.hash;
- let runner = this.getTransactionReceipt(hash).then((receipt) => {
- if (!receipt || receipt.blockNumber == null) {
- return null;
- }
- this._emitted["t:" + hash] = receipt.blockNumber;
- this.emit(hash, receipt);
- return null;
- }).catch((error) => { this.emit("error", error); });
- runners.push(runner);
- break;
- }
- case "filter": {
- // We only allow a single getLogs to be in-flight at a time
- if (!event._inflight) {
- event._inflight = true;
- // Filter from the last known event; due to load-balancing
- // and some nodes returning updated block numbers before
- // indexing events, a logs result with 0 entries cannot be
- // trusted and we must retry a range which includes it again
- const filter = event.filter;
- filter.fromBlock = event._lastBlockNumber + 1;
- filter.toBlock = blockNumber;
- // Prevent fitler ranges from growing too wild
- if (filter.toBlock - this._maxFilterBlockRange > filter.fromBlock) {
- filter.fromBlock = filter.toBlock - this._maxFilterBlockRange;
- }
- const runner = this.getLogs(filter).then((logs) => {
- // Allow the next getLogs
- event._inflight = false;
- if (logs.length === 0) {
- return;
- }
- logs.forEach((log) => {
- // Only when we get an event for a given block number
- // can we trust the events are indexed
- if (log.blockNumber > event._lastBlockNumber) {
- event._lastBlockNumber = log.blockNumber;
- }
- // Make sure we stall requests to fetch blocks and txs
- this._emitted["b:" + log.blockHash] = log.blockNumber;
- this._emitted["t:" + log.transactionHash] = log.blockNumber;
- this.emit(filter, log);
- });
- }).catch((error) => {
- this.emit("error", error);
- // Allow another getLogs (the range was not updated)
- event._inflight = false;
- });
- runners.push(runner);
- }
- break;
- }
- }
- });
- this._lastBlockNumber = blockNumber;
- // Once all events for this loop have been processed, emit "didPoll"
- Promise.all(runners).then(() => {
- this.emit("didPoll", pollId);
- }).catch((error) => { this.emit("error", error); });
- return;
- });
- }
- // Deprecated; do not use this
- resetEventsBlock(blockNumber) {
- this._lastBlockNumber = blockNumber - 1;
- if (this.polling) {
- this.poll();
- }
- }
- get network() {
- return this._network;
- }
- // This method should query the network if the underlying network
- // can change, such as when connected to a JSON-RPC backend
- detectNetwork() {
- return __awaiter(this, void 0, void 0, function* () {
- return logger.throwError("provider does not support network detection", Logger.errors.UNSUPPORTED_OPERATION, {
- operation: "provider.detectNetwork"
- });
- });
- }
- getNetwork() {
- return __awaiter(this, void 0, void 0, function* () {
- const network = yield this._ready();
- // Make sure we are still connected to the same network; this is
- // only an external call for backends which can have the underlying
- // network change spontaneously
- const currentNetwork = yield this.detectNetwork();
- if (network.chainId !== currentNetwork.chainId) {
- // We are allowing network changes, things can get complex fast;
- // make sure you know what you are doing if you use "any"
- if (this.anyNetwork) {
- this._network = currentNetwork;
- // Reset all internal block number guards and caches
- this._lastBlockNumber = -2;
- this._fastBlockNumber = null;
- this._fastBlockNumberPromise = null;
- this._fastQueryDate = 0;
- this._emitted.block = -2;
- this._maxInternalBlockNumber = -1024;
- this._internalBlockNumber = null;
- // The "network" event MUST happen before this method resolves
- // so any events have a chance to unregister, so we stall an
- // additional event loop before returning from /this/ call
- this.emit("network", currentNetwork, network);
- yield stall(0);
- return this._network;
- }
- const error = logger.makeError("underlying network changed", Logger.errors.NETWORK_ERROR, {
- event: "changed",
- network: network,
- detectedNetwork: currentNetwork
- });
- this.emit("error", error);
- throw error;
- }
- return network;
- });
- }
- get blockNumber() {
- this._getInternalBlockNumber(100 + this.pollingInterval / 2).then((blockNumber) => {
- this._setFastBlockNumber(blockNumber);
- }, (error) => { });
- return (this._fastBlockNumber != null) ? this._fastBlockNumber : -1;
- }
- get polling() {
- return (this._poller != null);
- }
- set polling(value) {
- if (value && !this._poller) {
- this._poller = setInterval(() => { this.poll(); }, this.pollingInterval);
- if (!this._bootstrapPoll) {
- this._bootstrapPoll = setTimeout(() => {
- this.poll();
- // We block additional polls until the polling interval
- // is done, to prevent overwhelming the poll function
- this._bootstrapPoll = setTimeout(() => {
- // If polling was disabled, something may require a poke
- // since starting the bootstrap poll and it was disabled
- if (!this._poller) {
- this.poll();
- }
- // Clear out the bootstrap so we can do another
- this._bootstrapPoll = null;
- }, this.pollingInterval);
- }, 0);
- }
- }
- else if (!value && this._poller) {
- clearInterval(this._poller);
- this._poller = null;
- }
- }
- get pollingInterval() {
- return this._pollingInterval;
- }
- set pollingInterval(value) {
- if (typeof (value) !== "number" || value <= 0 || parseInt(String(value)) != value) {
- throw new Error("invalid polling interval");
- }
- this._pollingInterval = value;
- if (this._poller) {
- clearInterval(this._poller);
- this._poller = setInterval(() => { this.poll(); }, this._pollingInterval);
- }
- }
- _getFastBlockNumber() {
- const now = getTime();
- // Stale block number, request a newer value
- if ((now - this._fastQueryDate) > 2 * this._pollingInterval) {
- this._fastQueryDate = now;
- this._fastBlockNumberPromise = this.getBlockNumber().then((blockNumber) => {
- if (this._fastBlockNumber == null || blockNumber > this._fastBlockNumber) {
- this._fastBlockNumber = blockNumber;
- }
- return this._fastBlockNumber;
- });
- }
- return this._fastBlockNumberPromise;
- }
- _setFastBlockNumber(blockNumber) {
- // Older block, maybe a stale request
- if (this._fastBlockNumber != null && blockNumber < this._fastBlockNumber) {
- return;
- }
- // Update the time we updated the blocknumber
- this._fastQueryDate = getTime();
- // Newer block number, use it
- if (this._fastBlockNumber == null || blockNumber > this._fastBlockNumber) {
- this._fastBlockNumber = blockNumber;
- this._fastBlockNumberPromise = Promise.resolve(blockNumber);
- }
- }
- waitForTransaction(transactionHash, confirmations, timeout) {
- return __awaiter(this, void 0, void 0, function* () {
- return this._waitForTransaction(transactionHash, (confirmations == null) ? 1 : confirmations, timeout || 0, null);
- });
- }
- _waitForTransaction(transactionHash, confirmations, timeout, replaceable) {
- return __awaiter(this, void 0, void 0, function* () {
- const receipt = yield this.getTransactionReceipt(transactionHash);
- // Receipt is already good
- if ((receipt ? receipt.confirmations : 0) >= confirmations) {
- return receipt;
- }
- // Poll until the receipt is good...
- return new Promise((resolve, reject) => {
- const cancelFuncs = [];
- let done = false;
- const alreadyDone = function () {
- if (done) {
- return true;
- }
- done = true;
- cancelFuncs.forEach((func) => { func(); });
- return false;
- };
- const minedHandler = (receipt) => {
- if (receipt.confirmations < confirmations) {
- return;
- }
- if (alreadyDone()) {
- return;
- }
- resolve(receipt);
- };
- this.on(transactionHash, minedHandler);
- cancelFuncs.push(() => { this.removeListener(transactionHash, minedHandler); });
- if (replaceable) {
- let lastBlockNumber = replaceable.startBlock;
- let scannedBlock = null;
- const replaceHandler = (blockNumber) => __awaiter(this, void 0, void 0, function* () {
- if (done) {
- return;
- }
- // Wait 1 second; this is only used in the case of a fault, so
- // we will trade off a little bit of latency for more consistent
- // results and fewer JSON-RPC calls
- yield stall(1000);
- this.getTransactionCount(replaceable.from).then((nonce) => __awaiter(this, void 0, void 0, function* () {
- if (done) {
- return;
- }
- if (nonce <= replaceable.nonce) {
- lastBlockNumber = blockNumber;
- }
- else {
- // First check if the transaction was mined
- {
- const mined = yield this.getTransaction(transactionHash);
- if (mined && mined.blockNumber != null) {
- return;
- }
- }
- // First time scanning. We start a little earlier for some
- // wiggle room here to handle the eventually consistent nature
- // of blockchain (e.g. the getTransactionCount was for a
- // different block)
- if (scannedBlock == null) {
- scannedBlock = lastBlockNumber - 3;
- if (scannedBlock < replaceable.startBlock) {
- scannedBlock = replaceable.startBlock;
- }
- }
- while (scannedBlock <= blockNumber) {
- if (done) {
- return;
- }
- const block = yield this.getBlockWithTransactions(scannedBlock);
- for (let ti = 0; ti < block.transactions.length; ti++) {
- const tx = block.transactions[ti];
- // Successfully mined!
- if (tx.hash === transactionHash) {
- return;
- }
- // Matches our transaction from and nonce; its a replacement
- if (tx.from === replaceable.from && tx.nonce === replaceable.nonce) {
- if (done) {
- return;
- }
- // Get the receipt of the replacement
- const receipt = yield this.waitForTransaction(tx.hash, confirmations);
- // Already resolved or rejected (prolly a timeout)
- if (alreadyDone()) {
- return;
- }
- // The reason we were replaced
- let reason = "replaced";
- if (tx.data === replaceable.data && tx.to === replaceable.to && tx.value.eq(replaceable.value)) {
- reason = "repriced";
- }
- else if (tx.data === "0x" && tx.from === tx.to && tx.value.isZero()) {
- reason = "cancelled";
- }
- // Explain why we were replaced
- reject(logger.makeError("transaction was replaced", Logger.errors.TRANSACTION_REPLACED, {
- cancelled: (reason === "replaced" || reason === "cancelled"),
- reason,
- replacement: this._wrapTransaction(tx),
- hash: transactionHash,
- receipt
- }));
- return;
- }
- }
- scannedBlock++;
- }
- }
- if (done) {
- return;
- }
- this.once("block", replaceHandler);
- }), (error) => {
- if (done) {
- return;
- }
- this.once("block", replaceHandler);
- });
- });
- if (done) {
- return;
- }
- this.once("block", replaceHandler);
- cancelFuncs.push(() => {
- this.removeListener("block", replaceHandler);
- });
- }
- if (typeof (timeout) === "number" && timeout > 0) {
- const timer = setTimeout(() => {
- if (alreadyDone()) {
- return;
- }
- reject(logger.makeError("timeout exceeded", Logger.errors.TIMEOUT, { timeout: timeout }));
- }, timeout);
- if (timer.unref) {
- timer.unref();
- }
- cancelFuncs.push(() => { clearTimeout(timer); });
- }
- });
- });
- }
- getBlockNumber() {
- return __awaiter(this, void 0, void 0, function* () {
- return this._getInternalBlockNumber(0);
- });
- }
- getGasPrice() {
- return __awaiter(this, void 0, void 0, function* () {
- yield this.getNetwork();
- const result = yield this.perform("getGasPrice", {});
- try {
- return BigNumber.from(result);
- }
- catch (error) {
- return logger.throwError("bad result from backend", Logger.errors.SERVER_ERROR, {
- method: "getGasPrice",
- result, error
- });
- }
- });
- }
- getBalance(addressOrName, blockTag) {
- return __awaiter(this, void 0, void 0, function* () {
- yield this.getNetwork();
- const params = yield resolveProperties({
- address: this._getAddress(addressOrName),
- blockTag: this._getBlockTag(blockTag)
- });
- const result = yield this.perform("getBalance", params);
- try {
- return BigNumber.from(result);
- }
- catch (error) {
- return logger.throwError("bad result from backend", Logger.errors.SERVER_ERROR, {
- method: "getBalance",
- params, result, error
- });
- }
- });
- }
- getTransactionCount(addressOrName, blockTag) {
- return __awaiter(this, void 0, void 0, function* () {
- yield this.getNetwork();
- const params = yield resolveProperties({
- address: this._getAddress(addressOrName),
- blockTag: this._getBlockTag(blockTag)
- });
- const result = yield this.perform("getTransactionCount", params);
- try {
- return BigNumber.from(result).toNumber();
- }
- catch (error) {
- return logger.throwError("bad result from backend", Logger.errors.SERVER_ERROR, {
- method: "getTransactionCount",
- params, result, error
- });
- }
- });
- }
- getCode(addressOrName, blockTag) {
- return __awaiter(this, void 0, void 0, function* () {
- yield this.getNetwork();
- const params = yield resolveProperties({
- address: this._getAddress(addressOrName),
- blockTag: this._getBlockTag(blockTag)
- });
- const result = yield this.perform("getCode", params);
- try {
- return hexlify(result);
- }
- catch (error) {
- return logger.throwError("bad result from backend", Logger.errors.SERVER_ERROR, {
- method: "getCode",
- params, result, error
- });
- }
- });
- }
- getStorageAt(addressOrName, position, blockTag) {
- return __awaiter(this, void 0, void 0, function* () {
- yield this.getNetwork();
- const params = yield resolveProperties({
- address: this._getAddress(addressOrName),
- blockTag: this._getBlockTag(blockTag),
- position: Promise.resolve(position).then((p) => hexValue(p))
- });
- const result = yield this.perform("getStorageAt", params);
- try {
- return hexlify(result);
- }
- catch (error) {
- return logger.throwError("bad result from backend", Logger.errors.SERVER_ERROR, {
- method: "getStorageAt",
- params, result, error
- });
- }
- });
- }
- // This should be called by any subclass wrapping a TransactionResponse
- _wrapTransaction(tx, hash, startBlock) {
- if (hash != null && hexDataLength(hash) !== 32) {
- throw new Error("invalid response - sendTransaction");
- }
- const result = tx;
- // Check the hash we expect is the same as the hash the server reported
- if (hash != null && tx.hash !== hash) {
- logger.throwError("Transaction hash mismatch from Provider.sendTransaction.", Logger.errors.UNKNOWN_ERROR, { expectedHash: tx.hash, returnedHash: hash });
- }
- result.wait = (confirms, timeout) => __awaiter(this, void 0, void 0, function* () {
- if (confirms == null) {
- confirms = 1;
- }
- if (timeout == null) {
- timeout = 0;
- }
- // Get the details to detect replacement
- let replacement = undefined;
- if (confirms !== 0 && startBlock != null) {
- replacement = {
- data: tx.data,
- from: tx.from,
- nonce: tx.nonce,
- to: tx.to,
- value: tx.value,
- startBlock
- };
- }
- const receipt = yield this._waitForTransaction(tx.hash, confirms, timeout, replacement);
- if (receipt == null && confirms === 0) {
- return null;
- }
- // No longer pending, allow the polling loop to garbage collect this
- this._emitted["t:" + tx.hash] = receipt.blockNumber;
- if (receipt.status === 0) {
- logger.throwError("transaction failed", Logger.errors.CALL_EXCEPTION, {
- transactionHash: tx.hash,
- transaction: tx,
- receipt: receipt
- });
- }
- return receipt;
- });
- return result;
- }
- sendTransaction(signedTransaction) {
- return __awaiter(this, void 0, void 0, function* () {
- yield this.getNetwork();
- const hexTx = yield Promise.resolve(signedTransaction).then(t => hexlify(t));
- const tx = this.formatter.transaction(signedTransaction);
- if (tx.confirmations == null) {
- tx.confirmations = 0;
- }
- const blockNumber = yield this._getInternalBlockNumber(100 + 2 * this.pollingInterval);
- try {
- const hash = yield this.perform("sendTransaction", { signedTransaction: hexTx });
- return this._wrapTransaction(tx, hash, blockNumber);
- }
- catch (error) {
- error.transaction = tx;
- error.transactionHash = tx.hash;
- throw error;
- }
- });
- }
- _getTransactionRequest(transaction) {
- return __awaiter(this, void 0, void 0, function* () {
- const values = yield transaction;
- const tx = {};
- ["from", "to"].forEach((key) => {
- if (values[key] == null) {
- return;
- }
- tx[key] = Promise.resolve(values[key]).then((v) => (v ? this._getAddress(v) : null));
- });
- ["gasLimit", "gasPrice", "maxFeePerGas", "maxPriorityFeePerGas", "value"].forEach((key) => {
- if (values[key] == null) {
- return;
- }
- tx[key] = Promise.resolve(values[key]).then((v) => (v ? BigNumber.from(v) : null));
- });
- ["type"].forEach((key) => {
- if (values[key] == null) {
- return;
- }
- tx[key] = Promise.resolve(values[key]).then((v) => ((v != null) ? v : null));
- });
- if (values.accessList) {
- tx.accessList = this.formatter.accessList(values.accessList);
- }
- ["data"].forEach((key) => {
- if (values[key] == null) {
- return;
- }
- tx[key] = Promise.resolve(values[key]).then((v) => (v ? hexlify(v) : null));
- });
- return this.formatter.transactionRequest(yield resolveProperties(tx));
- });
- }
- _getFilter(filter) {
- return __awaiter(this, void 0, void 0, function* () {
- filter = yield filter;
- const result = {};
- if (filter.address != null) {
- result.address = this._getAddress(filter.address);
- }
- ["blockHash", "topics"].forEach((key) => {
- if (filter[key] == null) {
- return;
- }
- result[key] = filter[key];
- });
- ["fromBlock", "toBlock"].forEach((key) => {
- if (filter[key] == null) {
- return;
- }
- result[key] = this._getBlockTag(filter[key]);
- });
- return this.formatter.filter(yield resolveProperties(result));
- });
- }
- _call(transaction, blockTag, attempt) {
- return __awaiter(this, void 0, void 0, function* () {
- if (attempt >= MAX_CCIP_REDIRECTS) {
- logger.throwError("CCIP read exceeded maximum redirections", Logger.errors.SERVER_ERROR, {
- redirects: attempt, transaction
- });
- }
- const txSender = transaction.to;
- const result = yield this.perform("call", { transaction, blockTag });
- // CCIP Read request via OffchainLookup(address,string[],bytes,bytes4,bytes)
- if (attempt >= 0 && blockTag === "latest" && txSender != null && result.substring(0, 10) === "0x556f1830" && (hexDataLength(result) % 32 === 4)) {
- try {
- const data = hexDataSlice(result, 4);
- // Check the sender of the OffchainLookup matches the transaction
- const sender = hexDataSlice(data, 0, 32);
- if (!BigNumber.from(sender).eq(txSender)) {
- logger.throwError("CCIP Read sender did not match", Logger.errors.CALL_EXCEPTION, {
- name: "OffchainLookup",
- signature: "OffchainLookup(address,string[],bytes,bytes4,bytes)",
- transaction, data: result
- });
- }
- // Read the URLs from the response
- const urls = [];
- const urlsOffset = BigNumber.from(hexDataSlice(data, 32, 64)).toNumber();
- const urlsLength = BigNumber.from(hexDataSlice(data, urlsOffset, urlsOffset + 32)).toNumber();
- const urlsData = hexDataSlice(data, urlsOffset + 32);
- for (let u = 0; u < urlsLength; u++) {
- const url = _parseString(urlsData, u * 32);
- if (url == null) {
- logger.throwError("CCIP Read contained corrupt URL string", Logger.errors.CALL_EXCEPTION, {
- name: "OffchainLookup",
- signature: "OffchainLookup(address,string[],bytes,bytes4,bytes)",
- transaction, data: result
- });
- }
- urls.push(url);
- }
- // Get the CCIP calldata to forward
- const calldata = _parseBytes(data, 64);
- // Get the callbackSelector (bytes4)
- if (!BigNumber.from(hexDataSlice(data, 100, 128)).isZero()) {
- logger.throwError("CCIP Read callback selector included junk", Logger.errors.CALL_EXCEPTION, {
- name: "OffchainLookup",
- signature: "OffchainLookup(address,string[],bytes,bytes4,bytes)",
- transaction, data: result
- });
- }
- const callbackSelector = hexDataSlice(data, 96, 100);
- // Get the extra data to send back to the contract as context
- const extraData = _parseBytes(data, 128);
- const ccipResult = yield this.ccipReadFetch(transaction, calldata, urls);
- if (ccipResult == null) {
- logger.throwError("CCIP Read disabled or provided no URLs", Logger.errors.CALL_EXCEPTION, {
- name: "OffchainLookup",
- signature: "OffchainLookup(address,string[],bytes,bytes4,bytes)",
- transaction, data: result
- });
- }
- const tx = {
- to: txSender,
- data: hexConcat([callbackSelector, encodeBytes([ccipResult, extraData])])
- };
- return this._call(tx, blockTag, attempt + 1);
- }
- catch (error) {
- if (error.code === Logger.errors.SERVER_ERROR) {
- throw error;
- }
- }
- }
- try {
- return hexlify(result);
- }
- catch (error) {
- return logger.throwError("bad result from backend", Logger.errors.SERVER_ERROR, {
- method: "call",
- params: { transaction, blockTag }, result, error
- });
- }
- });
- }
- call(transaction, blockTag) {
- return __awaiter(this, void 0, void 0, function* () {
- yield this.getNetwork();
- const resolved = yield resolveProperties({
- transaction: this._getTransactionRequest(transaction),
- blockTag: this._getBlockTag(blockTag),
- ccipReadEnabled: Promise.resolve(transaction.ccipReadEnabled)
- });
- return this._call(resolved.transaction, resolved.blockTag, resolved.ccipReadEnabled ? 0 : -1);
- });
- }
- estimateGas(transaction) {
- return __awaiter(this, void 0, void 0, function* () {
- yield this.getNetwork();
- const params = yield resolveProperties({
- transaction: this._getTransactionRequest(transaction)
- });
- const result = yield this.perform("estimateGas", params);
- try {
- return BigNumber.from(result);
- }
- catch (error) {
- return logger.throwError("bad result from backend", Logger.errors.SERVER_ERROR, {
- method: "estimateGas",
- params, result, error
- });
- }
- });
- }
- _getAddress(addressOrName) {
- return __awaiter(this, void 0, void 0, function* () {
- addressOrName = yield addressOrName;
- if (typeof (addressOrName) !== "string") {
- logger.throwArgumentError("invalid address or ENS name", "name", addressOrName);
- }
- const address = yield this.resolveName(addressOrName);
- if (address == null) {
- logger.throwError("ENS name not configured", Logger.errors.UNSUPPORTED_OPERATION, {
- operation: `resolveName(${JSON.stringify(addressOrName)})`
- });
- }
- return address;
- });
- }
- _getBlock(blockHashOrBlockTag, includeTransactions) {
- return __awaiter(this, void 0, void 0, function* () {
- yield this.getNetwork();
- blockHashOrBlockTag = yield blockHashOrBlockTag;
- // If blockTag is a number (not "latest", etc), this is the block number
- let blockNumber = -128;
- const params = {
- includeTransactions: !!includeTransactions
- };
- if (isHexString(blockHashOrBlockTag, 32)) {
- params.blockHash = blockHashOrBlockTag;
- }
- else {
- try {
- params.blockTag = yield this._getBlockTag(blockHashOrBlockTag);
- if (isHexString(params.blockTag)) {
- blockNumber = parseInt(params.blockTag.substring(2), 16);
- }
- }
- catch (error) {
- logger.throwArgumentError("invalid block hash or block tag", "blockHashOrBlockTag", blockHashOrBlockTag);
- }
- }
- return poll(() => __awaiter(this, void 0, void 0, function* () {
- const block = yield this.perform("getBlock", params);
- // Block was not found
- if (block == null) {
- // For blockhashes, if we didn't say it existed, that blockhash may
- // not exist. If we did see it though, perhaps from a log, we know
- // it exists, and this node is just not caught up yet.
- if (params.blockHash != null) {
- if (this._emitted["b:" + params.blockHash] == null) {
- return null;
- }
- }
- // For block tags, if we are asking for a future block, we return null
- if (params.blockTag != null) {
- if (blockNumber > this._emitted.block) {
- return null;
- }
- }
- // Retry on the next block
- return undefined;
- }
- // Add transactions
- if (includeTransactions) {
- let blockNumber = null;
- for (let i = 0; i < block.transactions.length; i++) {
- const tx = block.transactions[i];
- if (tx.blockNumber == null) {
- tx.confirmations = 0;
- }
- else if (tx.confirmations == null) {
- if (blockNumber == null) {
- blockNumber = yield this._getInternalBlockNumber(100 + 2 * this.pollingInterval);
- }
- // Add the confirmations using the fast block number (pessimistic)
- let confirmations = (blockNumber - tx.blockNumber) + 1;
- if (confirmations <= 0) {
- confirmations = 1;
- }
- tx.confirmations = confirmations;
- }
- }
- const blockWithTxs = this.formatter.blockWithTransactions(block);
- blockWithTxs.transactions = blockWithTxs.transactions.map((tx) => this._wrapTransaction(tx));
- return blockWithTxs;
- }
- return this.formatter.block(block);
- }), { oncePoll: this });
- });
- }
- getBlock(blockHashOrBlockTag) {
- return (this._getBlock(blockHashOrBlockTag, false));
- }
- getBlockWithTransactions(blockHashOrBlockTag) {
- return (this._getBlock(blockHashOrBlockTag, true));
- }
- getTransaction(transactionHash) {
- return __awaiter(this, void 0, void 0, function* () {
- yield this.getNetwork();
- transactionHash = yield transactionHash;
- const params = { transactionHash: this.formatter.hash(transactionHash, true) };
- return poll(() => __awaiter(this, void 0, void 0, function* () {
- const result = yield this.perform("getTransaction", params);
- if (result == null) {
- if (this._emitted["t:" + transactionHash] == null) {
- return null;
- }
- return undefined;
- }
- const tx = this.formatter.transactionResponse(result);
- if (tx.blockNumber == null) {
- tx.confirmations = 0;
- }
- else if (tx.confirmations == null) {
- const blockNumber = yield this._getInternalBlockNumber(100 + 2 * this.pollingInterval);
- // Add the confirmations using the fast block number (pessimistic)
- let confirmations = (blockNumber - tx.blockNumber) + 1;
- if (confirmations <= 0) {
- confirmations = 1;
- }
- tx.confirmations = confirmations;
- }
- return this._wrapTransaction(tx);
- }), { oncePoll: this });
- });
- }
- getTransactionReceipt(transactionHash) {
- return __awaiter(this, void 0, void 0, function* () {
- yield this.getNetwork();
- transactionHash = yield transactionHash;
- const params = { transactionHash: this.formatter.hash(transactionHash, true) };
- return poll(() => __awaiter(this, void 0, void 0, function* () {
- const result = yield this.perform("getTransactionReceipt", params);
- if (result == null) {
- if (this._emitted["t:" + transactionHash] == null) {
- return null;
- }
- return undefined;
- }
- // "geth-etc" returns receipts before they are ready
- if (result.blockHash == null) {
- return undefined;
- }
- const receipt = this.formatter.receipt(result);
- if (receipt.blockNumber == null) {
- receipt.confirmations = 0;
- }
- else if (receipt.confirmations == null) {
- const blockNumber = yield this._getInternalBlockNumber(100 + 2 * this.pollingInterval);
- // Add the confirmations using the fast block number (pessimistic)
- let confirmations = (blockNumber - receipt.blockNumber) + 1;
- if (confirmations <= 0) {
- confirmations = 1;
- }
- receipt.confirmations = confirmations;
- }
- return receipt;
- }), { oncePoll: this });
- });
- }
- getLogs(filter) {
- return __awaiter(this, void 0, void 0, function* () {
- yield this.getNetwork();
- const params = yield resolveProperties({ filter: this._getFilter(filter) });
- const logs = yield this.perform("getLogs", params);
- logs.forEach((log) => {
- if (log.removed == null) {
- log.removed = false;
- }
- });
- return Formatter.arrayOf(this.formatter.filterLog.bind(this.formatter))(logs);
- });
- }
- getEtherPrice() {
- return __awaiter(this, void 0, void 0, function* () {
- yield this.getNetwork();
- return this.perform("getEtherPrice", {});
- });
- }
- _getBlockTag(blockTag) {
- return __awaiter(this, void 0, void 0, function* () {
- blockTag = yield blockTag;
- if (typeof (blockTag) === "number" && blockTag < 0) {
- if (blockTag % 1) {
- logger.throwArgumentError("invalid BlockTag", "blockTag", blockTag);
- }
- let blockNumber = yield this._getInternalBlockNumber(100 + 2 * this.pollingInterval);
- blockNumber += blockTag;
- if (blockNumber < 0) {
- blockNumber = 0;
- }
- return this.formatter.blockTag(blockNumber);
- }
- return this.formatter.blockTag(blockTag);
- });
- }
- getResolver(name) {
- return __awaiter(this, void 0, void 0, function* () {
- let currentName = name;
- while (true) {
- if (currentName === "" || currentName === ".") {
- return null;
- }
- // Optimization since the eth node cannot change and does
- // not have a wildcar resolver
- if (name !== "eth" && currentName === "eth") {
- return null;
- }
- // Check the current node for a resolver
- const addr = yield this._getResolver(currentName, "getResolver");
- // Found a resolver!
- if (addr != null) {
- const resolver = new Resolver(this, addr, name);
- // Legacy resolver found, using EIP-2544 so it isn't safe to use
- if (currentName !== name && !(yield resolver.supportsWildcard())) {
- return null;
- }
- return resolver;
- }
- // Get the parent node
- currentName = currentName.split(".").slice(1).join(".");
- }
- });
- }
- _getResolver(name, operation) {
- return __awaiter(this, void 0, void 0, function* () {
- if (operation == null) {
- operation = "ENS";
- }
- const network = yield this.getNetwork();
- // No ENS...
- if (!network.ensAddress) {
- logger.throwError("network does not support ENS", Logger.errors.UNSUPPORTED_OPERATION, { operation, network: network.name });
- }
- try {
- // keccak256("resolver(bytes32)")
- const addrData = yield this.call({
- to: network.ensAddress,
- data: ("0x0178b8bf" + namehash(name).substring(2))
- });
- return this.formatter.callAddress(addrData);
- }
- catch (error) {
- // ENS registry cannot throw errors on resolver(bytes32)
- }
- return null;
- });
- }
- resolveName(name) {
- return __awaiter(this, void 0, void 0, function* () {
- name = yield name;
- // If it is already an address, nothing to resolve
- try {
- return Promise.resolve(this.formatter.address(name));
- }
- catch (error) {
- // If is is a hexstring, the address is bad (See #694)
- if (isHexString(name)) {
- throw error;
- }
- }
- if (typeof (name) !== "string") {
- logger.throwArgumentError("invalid ENS name", "name", name);
- }
- // Get the addr from the resovler
- const resolver = yield this.getResolver(name);
- if (!resolver) {
- return null;
- }
- return yield resolver.getAddress();
- });
- }
- lookupAddress(address) {
- return __awaiter(this, void 0, void 0, function* () {
- address = yield address;
- address = this.formatter.address(address);
- const node = address.substring(2).toLowerCase() + ".addr.reverse";
- const resolverAddr = yield this._getResolver(node, "lookupAddress");
- if (resolverAddr == null) {
- return null;
- }
- // keccak("name(bytes32)")
- const name = _parseString(yield this.call({
- to: resolverAddr,
- data: ("0x691f3431" + namehash(node).substring(2))
- }), 0);
- const addr = yield this.resolveName(name);
- if (addr != address) {
- return null;
- }
- return name;
- });
- }
- getAvatar(nameOrAddress) {
- return __awaiter(this, void 0, void 0, function* () {
- let resolver = null;
- if (isHexString(nameOrAddress)) {
- // Address; reverse lookup
- const address = this.formatter.address(nameOrAddress);
- const node = address.substring(2).toLowerCase() + ".addr.reverse";
- const resolverAddress = yield this._getResolver(node, "getAvatar");
- if (!resolverAddress) {
- return null;
- }
- // Try resolving the avatar against the addr.reverse resolver
- resolver = new Resolver(this, resolverAddress, node);
- try {
- const avatar = yield resolver.getAvatar();
- if (avatar) {
- return avatar.url;
- }
- }
- catch (error) {
- if (error.code !== Logger.errors.CALL_EXCEPTION) {
- throw error;
- }
- }
- // Try getting the name and performing forward lookup; allowing wildcards
- try {
- // keccak("name(bytes32)")
- const name = _parseString(yield this.call({
- to: resolverAddress,
- data: ("0x691f3431" + namehash(node).substring(2))
- }), 0);
- resolver = yield this.getResolver(name);
- }
- catch (error) {
- if (error.code !== Logger.errors.CALL_EXCEPTION) {
- throw error;
- }
- return null;
- }
- }
- else {
- // ENS name; forward lookup with wildcard
- resolver = yield this.getResolver(nameOrAddress);
- if (!resolver) {
- return null;
- }
- }
- const avatar = yield resolver.getAvatar();
- if (avatar == null) {
- return null;
- }
- return avatar.url;
- });
- }
- perform(method, params) {
- return logger.throwError(method + " not implemented", Logger.errors.NOT_IMPLEMENTED, { operation: method });
- }
- _startEvent(event) {
- this.polling = (this._events.filter((e) => e.pollable()).length > 0);
- }
- _stopEvent(event) {
- this.polling = (this._events.filter((e) => e.pollable()).length > 0);
- }
- _addEventListener(eventName, listener, once) {
- const event = new Event(getEventTag(eventName), listener, once);
- this._events.push(event);
- this._startEvent(event);
- return this;
- }
- on(eventName, listener) {
- return this._addEventListener(eventName, listener, false);
- }
- once(eventName, listener) {
- return this._addEventListener(eventName, listener, true);
- }
- emit(eventName, ...args) {
- let result = false;
- let stopped = [];
- let eventTag = getEventTag(eventName);
- this._events = this._events.filter((event) => {
- if (event.tag !== eventTag) {
- return true;
- }
- setTimeout(() => {
- event.listener.apply(this, args);
- }, 0);
- result = true;
- if (event.once) {
- stopped.push(event);
- return false;
- }
- return true;
- });
- stopped.forEach((event) => { this._stopEvent(event); });
- return result;
- }
- listenerCount(eventName) {
- if (!eventName) {
- return this._events.length;
- }
- let eventTag = getEventTag(eventName);
- return this._events.filter((event) => {
- return (event.tag === eventTag);
- }).length;
- }
- listeners(eventName) {
- if (eventName == null) {
- return this._events.map((event) => event.listener);
- }
- let eventTag = getEventTag(eventName);
- return this._events
- .filter((event) => (event.tag === eventTag))
- .map((event) => event.listener);
- }
- off(eventName, listener) {
- if (listener == null) {
- return this.removeAllListeners(eventName);
- }
- const stopped = [];
- let found = false;
- let eventTag = getEventTag(eventName);
- this._events = this._events.filter((event) => {
- if (event.tag !== eventTag || event.listener != listener) {
- return true;
- }
- if (found) {
- return true;
- }
- found = true;
- stopped.push(event);
- return false;
- });
- stopped.forEach((event) => { this._stopEvent(event); });
- return this;
- }
- removeAllListeners(eventName) {
- let stopped = [];
- if (eventName == null) {
- stopped = this._events;
- this._events = [];
- }
- else {
- const eventTag = getEventTag(eventName);
- this._events = this._events.filter((event) => {
- if (event.tag !== eventTag) {
- return true;
- }
- stopped.push(event);
- return false;
- });
- }
- stopped.forEach((event) => { this._stopEvent(event); });
- return this;
- }
- }
- //# sourceMappingURL=base-provider.js.map
|