From ff3f8792a943378e806d5a72fd5b64a67e847276 Mon Sep 17 00:00:00 2001 From: rileystephens28 Date: Thu, 11 Apr 2024 17:58:35 -0500 Subject: [PATCH] Add provider method to get utxo outpoints by address --- src.ts/providers/abstract-provider.ts | 253 +++++++++++++------------ src.ts/providers/provider-fallback.ts | 33 ++-- src.ts/providers/provider-jsonrpc.ts | 263 +++++++++++++------------- src.ts/providers/provider.ts | 142 +++++++------- src.ts/transaction/utxo.ts | 9 +- 5 files changed, 364 insertions(+), 336 deletions(-) diff --git a/src.ts/providers/abstract-provider.ts b/src.ts/providers/abstract-provider.ts index e32b227f..0267665a 100644 --- a/src.ts/providers/abstract-provider.ts +++ b/src.ts/providers/abstract-provider.ts @@ -17,6 +17,7 @@ import { resolveAddress } from "../address/index.js"; import { ShardData } from "../constants/index.js"; import { Transaction } from "../transaction/index.js"; +import { Outpoint } from "../transaction/utxo.js"; import { hexlify, isHexString, getBigInt, getNumber, @@ -57,23 +58,23 @@ type Timer = ReturnType; const BN_2 = BigInt(2); function isPromise(value: any): value is Promise { - return (value && typeof(value.then) === "function"); + return (value && typeof (value.then) === "function"); } function getTag(prefix: string, value: any): string { return prefix + ":" + JSON.stringify(value, (k, v) => { if (v == null) { return "null"; } - if (typeof(v) === "bigint") { return `bigint:${ v.toString() }`} - if (typeof(v) === "string") { return v.toLowerCase(); } + 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)) { + 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; @@ -224,7 +225,7 @@ async function getSubscription(_event: ProviderEvent, provider: AbstractProvider // Normalize topic array info an EventFilter if (Array.isArray(_event)) { _event = { topics: _event }; } - if (typeof(_event) === "string") { + if (typeof (_event) === "string") { switch (_event) { case "block": case "debug": @@ -263,8 +264,8 @@ async function getSubscription(_event: ProviderEvent, provider: AbstractProvider }; if (event.address) { - const addresses: Array = [ ]; - const promises: Array> = [ ]; + const addresses: Array = []; + const promises: Array> = []; const addAddress = (addr: AddressLike) => { if (isHexString(addr)) { @@ -361,6 +362,9 @@ export type PerformActionRequest = { } | { method: "getBalance", address: string, blockTag: BlockTag, shard: string +} | { + method: "getOutpointsByAddress", + address: string, shard: string } | { method: "getBlock", blockTag: BlockTag, includeTransactions: boolean, @@ -427,7 +431,7 @@ export type PerformActionRequest = { type _PerformAccountRequest = { - method: "getBalance" | "getTransactionCount" | "getCode" + method: "getBalance" | "getTransactionCount" | "getCode" | "getOutpointsByAddress", } | { method: "getStorage", position: bigint } @@ -500,7 +504,7 @@ export class AbstractProvider implements Provider { * [[Network]] if necessary. */ constructor(_network?: "any" | Networkish, options?: AbstractProviderOptions) { - this.#options = Object.assign({ }, defaultOptions, options || { }); + this.#options = Object.assign({}, defaultOptions, options || {}); if (_network === "any") { this.#anyNetwork = true; @@ -568,7 +572,7 @@ export class AbstractProvider implements Provider { )?.byte || ''; } - get connect(): FetchRequest[] { return this.#connect; } + get connect(): FetchRequest[] { return this.#connect; } async shardFromAddress(_address: AddressLike): Promise { let address: string | Promise = this._getAddress(_address); @@ -643,9 +647,9 @@ export class AbstractProvider implements Provider { */ attachPlugin(plugin: AbstractProviderPlugin): this { if (this.#plugins.get(plugin.name)) { - throw new Error(`cannot replace existing plugin: ${ plugin.name } `); + throw new Error(`cannot replace existing plugin: ${plugin.name} `); } - this.#plugins.set(plugin.name, plugin.connect(this)); + this.#plugins.set(plugin.name, plugin.connect(this)); return this; } @@ -743,7 +747,7 @@ export class AbstractProvider implements Provider { * Sub-classes **must** override this. */ async _perform(req: PerformActionRequest): Promise { - assert(false, `unsupported method: ${ req.method }`, "UNSUPPORTED_OPERATION", { + assert(false, `unsupported method: ${req.method}`, "UNSUPPORTED_OPERATION", { operation: req.method, info: req }); @@ -789,11 +793,11 @@ export class AbstractProvider implements Provider { return toQuantity(blockTag); } - if (typeof(blockTag) === "bigint") { + if (typeof (blockTag) === "bigint") { blockTag = getNumber(blockTag, "blockTag"); } - if (typeof(blockTag) === "number") { + if (typeof (blockTag) === "number") { if (blockTag >= 0) { return toQuantity(blockTag); } if (this.#lastBlockNumber >= 0) { return toQuantity(this.#lastBlockNumber + blockTag); } return this.getBlockNumber(shard).then((b) => toQuantity(b + blockTag)); @@ -810,7 +814,7 @@ export class AbstractProvider implements Provider { _getFilter(filter: Filter | FilterByBlockHash): PerformActionFilter | Promise { // Create a canonical representation of the topics - const topics = (filter.topics || [ ]).map((t) => { + const topics = (filter.topics || []).map((t) => { if (t == null) { return null; } if (Array.isArray(t)) { return concisify(t.map((t) => t.toLowerCase())); @@ -818,7 +822,7 @@ export class AbstractProvider implements Provider { return t.toLowerCase(); }); - const blockHash = ("blockHash" in filter) ? filter.blockHash: undefined; + const blockHash = ("blockHash" in filter) ? filter.blockHash : undefined; const resolve = (_address: Array, fromBlock?: string, toBlock?: string, shard?: string) => { let address: undefined | string | Array = undefined; @@ -838,7 +842,7 @@ export class AbstractProvider implements Provider { } } - const filter = { }; + const filter = {}; if (address) { filter.address = address; } if (topics.length) { filter.topics = topics; } if (fromBlock) { filter.fromBlock = fromBlock; } @@ -850,7 +854,7 @@ export class AbstractProvider implements Provider { }; // Addresses could be async (ENS names or Addressables) - let address: Array> = [ ]; + let address: Array> = []; if (filter.address) { if (Array.isArray(filter.address)) { for (const addr of filter.address) { address.push(this._getAddress(addr)); } @@ -867,11 +871,11 @@ export class AbstractProvider implements Provider { const shard = filter.shard - if (address.filter((a) => (typeof(a) !== "string")).length || - (fromBlock != null && typeof(fromBlock) !== "string") || - (toBlock != null && typeof(toBlock) !== "string")) { + 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, shard ]).then((result) => { + return Promise.all([Promise.all(address), fromBlock, toBlock, shard]).then((result) => { return resolve(result[0], result[1], result[2]); }); } @@ -887,13 +891,13 @@ export class AbstractProvider implements Provider { _getTransactionRequest(_request: TransactionRequest): PerformActionTransaction | Promise { const request = copyRequest(_request); - const promises: Array> = [ ]; - [ "to", "from" ].forEach((key) => { + const promises: Array> = []; + ["to", "from"].forEach((key) => { if ((request)[key] == null) { return; } const addr = resolveAddress((request)[key]); if (isPromise(addr)) { - promises.push((async function() { (request)[key] = await addr; })()); + promises.push((async function () { (request)[key] = await addr; })()); } else { (request)[key] = addr; } @@ -902,14 +906,14 @@ export class AbstractProvider implements Provider { if (request.blockTag != null) { const blockTag = this._getBlockTag(request.chainId?.toString(), request.blockTag); if (isPromise(blockTag)) { - promises.push((async function() { request.blockTag = await blockTag; })()); + promises.push((async function () { request.blockTag = await blockTag; })()); } else { request.blockTag = blockTag; } } if (promises.length) { - return (async function() { + return (async function () { await Promise.all(promises); return request; })(); @@ -943,7 +947,7 @@ export class AbstractProvider implements Provider { const networkPromise = this.#networkPromise; - const [ expected, actual ] = await Promise.all([ + const [expected, actual] = await Promise.all([ networkPromise, // Possibly an explicit Network this._detectNetwork(shard) // The actual connected network ]); @@ -959,7 +963,7 @@ export class AbstractProvider implements Provider { } } else { // Otherwise, we do not allow changes to the underlying network - assert(false, `network changed: ${ expected.chainId } => ${ actual.chainId } `, "NETWORK_ERROR", { + assert(false, `network changed: ${expected.chainId} => ${actual.chainId} `, "NETWORK_ERROR", { event: "changed" }); } @@ -968,7 +972,7 @@ export class AbstractProvider implements Provider { return expected.clone(); } - async getRunningLocations(shard?:string): Promise { + async getRunningLocations(shard?: string): Promise { return await this.#perform(shard ? { method: "getRunningLocations", shard: shard } : { method: "getRunningLocations" }); @@ -991,7 +995,7 @@ export class AbstractProvider implements Provider { })()), priorityFee: ((async () => { try { - const value = txType ? await this.#perform({ method: "getMaxPriorityFeePerGas", shard: shard }): 0; + const value = txType ? await this.#perform({ method: "getMaxPriorityFeePerGas", shard: shard }) : 0; return getBigInt(value, "%response"); } catch (error) { } return null; @@ -1005,7 +1009,7 @@ export class AbstractProvider implements Provider { // These are the recommended EIP-1559 heuristics for fee data - maxPriorityFeePerGas = (priorityFee != null) ? priorityFee: BigInt("1000000000"); + maxPriorityFeePerGas = (priorityFee != null) ? priorityFee : BigInt("1000000000"); maxFeePerGas = (gasPrice * BN_2) + maxPriorityFeePerGas; return new FeeData(gasPrice, maxFeePerGas, maxPriorityFeePerGas); @@ -1034,68 +1038,68 @@ export class AbstractProvider implements Provider { async #call(tx: PerformActionTransaction, blockTag: string, attempt: number): Promise { - // This came in as a PerformActionTransaction, so to/from are safe; we can cast - const transaction = copyRequest(tx); - - try { - return hexlify(await this._perform({ method: "call", transaction, blockTag })); - - } catch (error: any) { - // CCIP Read OffchainLookup - // if (!this.disableCcipRead && isCallException(error) && error.data && attempt >= 0 && blockTag === "latest" && transaction.to != null && dataSlice(error.data, 0, 4) === "0x556f1830") { - // const data = error.data; - - // const txSender = await resolveAddress(transaction.to, this); - - // // Parse the CCIP Read Arguments - // let ccipArgs: CcipArgs; - // try { - // ccipArgs = parseOffchainLookup(dataSlice(error.data, 4)); - // } catch (error: any) { - // assert(false, error.message, "OFFCHAIN_FAULT", { - // reason: "BAD_DATA", transaction, info: { data } }); - // } - - // // Check the sender of the OffchainLookup matches the transaction - // assert(ccipArgs.sender.toLowerCase() === txSender.toLowerCase(), - // "CCIP Read sender mismatch", "CALL_EXCEPTION", { - // action: "call", - // data, - // reason: "OffchainLookup", - // transaction: transaction, // @TODO: populate data? - // invocation: null, - // revert: { - // signature: "OffchainLookup(address,string[],bytes,bytes4,bytes)", - // name: "OffchainLookup", - // args: ccipArgs.errorArgs - // } - // }); - - // const ccipResult = await this.ccipReadFetch(transaction, ccipArgs.calldata, ccipArgs.urls); - // assert(ccipResult != null, "CCIP Read failed to fetch data", "OFFCHAIN_FAULT", { - // reason: "FETCH_FAILED", transaction, info: { data: error.data, errorArgs: ccipArgs.errorArgs } }); - - // const tx = { - // to: txSender, - // 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; - // } - // } - - throw error; - } - } - - async #checkNetwork(promise: Promise, shard?: string, ): Promise { + // This came in as a PerformActionTransaction, so to/from are safe; we can cast + const transaction = copyRequest(tx); + + try { + return hexlify(await this._perform({ method: "call", transaction, blockTag })); + + } catch (error: any) { + // CCIP Read OffchainLookup + // if (!this.disableCcipRead && isCallException(error) && error.data && attempt >= 0 && blockTag === "latest" && transaction.to != null && dataSlice(error.data, 0, 4) === "0x556f1830") { + // const data = error.data; + + // const txSender = await resolveAddress(transaction.to, this); + + // // Parse the CCIP Read Arguments + // let ccipArgs: CcipArgs; + // try { + // ccipArgs = parseOffchainLookup(dataSlice(error.data, 4)); + // } catch (error: any) { + // assert(false, error.message, "OFFCHAIN_FAULT", { + // reason: "BAD_DATA", transaction, info: { data } }); + // } + + // // Check the sender of the OffchainLookup matches the transaction + // assert(ccipArgs.sender.toLowerCase() === txSender.toLowerCase(), + // "CCIP Read sender mismatch", "CALL_EXCEPTION", { + // action: "call", + // data, + // reason: "OffchainLookup", + // transaction: transaction, // @TODO: populate data? + // invocation: null, + // revert: { + // signature: "OffchainLookup(address,string[],bytes,bytes4,bytes)", + // name: "OffchainLookup", + // args: ccipArgs.errorArgs + // } + // }); + + // const ccipResult = await this.ccipReadFetch(transaction, ccipArgs.calldata, ccipArgs.urls); + // assert(ccipResult != null, "CCIP Read failed to fetch data", "OFFCHAIN_FAULT", { + // reason: "FETCH_FAILED", transaction, info: { data: error.data, errorArgs: ccipArgs.errorArgs } }); + + // const tx = { + // to: txSender, + // 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; + // } + // } + + throw error; + } + } + + async #checkNetwork(promise: Promise, shard?: string,): Promise { const { value } = await resolveProperties({ network: this.getNetwork(), value: promise @@ -1120,17 +1124,21 @@ export class AbstractProvider implements Provider { let blockTag: string | Promise = this._getBlockTag(shard, _blockTag); - if (typeof(address) !== "string" || typeof(blockTag) !== "string") { - [ address, blockTag ] = await Promise.all([ address, blockTag ]); + if (typeof (address) !== "string" || typeof (blockTag) !== "string") { + [address, blockTag] = await Promise.all([address, blockTag]); } - return await this.#checkNetwork(this.#perform(Object.assign(request, { address, blockTag, shard: shard })), shard); + return await this.#checkNetwork(this.#perform(Object.assign(request, { address, blockTag, shard: shard }) as PerformActionRequest), shard); } async getBalance(address: AddressLike, blockTag?: BlockTag): Promise { return getBigInt(await this.#getAccountValue({ method: "getBalance" }, address, blockTag), "%response"); } + async getOutpointsByAddress(address: AddressLike): Promise { + return await this.#getAccountValue({ method: "getOutpointsByAddress" }, address, "latest"); + } + async getTransactionCount(address: AddressLike, blockTag?: BlockTag): Promise { return getNumber(await this.#getAccountValue({ method: "getTransactionCount" }, address, blockTag), "%response"); } @@ -1141,21 +1149,22 @@ export class AbstractProvider implements Provider { async getStorage(address: AddressLike, _position: BigNumberish, blockTag?: BlockTag): Promise { const position = getBigInt(_position, "position"); - return hexlify(await this.#getAccountValue({ method: "getStorage", position}, address, blockTag)); + return hexlify(await this.#getAccountValue({ method: "getStorage", position }, address, blockTag)); } + //TODO: Provider method which gets unspent utxos for an address // Write async broadcastTransaction(shard: string, signedTx: string): Promise { const { blockNumber, hash, network } = await resolveProperties({ - blockNumber: this.getBlockNumber(shard), - hash: this._perform({ - method: "broadcastTransaction", - signedTransaction: signedTx, - shard: shard - }), - network: this.getNetwork() + blockNumber: this.getBlockNumber(shard), + hash: this._perform({ + method: "broadcastTransaction", + signedTransaction: signedTx, + shard: shard + }), + network: this.getNetwork() }); const tx = Transaction.from(signedTx); @@ -1164,11 +1173,11 @@ export class AbstractProvider implements Provider { return this._wrapTransactionResponse(tx, network).replaceableTransaction(blockNumber); } - async #validateTransactionHash(computedHash: string, nodehash: string){ - if (computedHash.substring(0,4) !== nodehash.substring(0,4)) - throw new Error("Transaction hash mismatch in origin Zone"); - if (computedHash.substring(6,8) !== nodehash.substring(6,8)) - throw new Error("Transaction hash mismatch in destination Zone"); + async #validateTransactionHash(computedHash: string, nodehash: string) { + if (computedHash.substring(0, 4) !== nodehash.substring(0, 4)) + throw new Error("Transaction hash mismatch in origin Zone"); + if (computedHash.substring(6, 8) !== nodehash.substring(6, 8)) + throw new Error("Transaction hash mismatch in destination Zone"); if (parseInt(computedHash[4], 16) < 8 !== parseInt(nodehash[4], 16) < 8) throw new Error("Transaction ledger mismatch in origin Zone"); if (parseInt(computedHash[8], 16) < 8 !== parseInt(nodehash[8], 16) < 8) @@ -1184,7 +1193,7 @@ export class AbstractProvider implements Provider { } let blockTag = this._getBlockTag(shard, block); - if (typeof(blockTag) !== "string") { blockTag = await blockTag; } + if (typeof (blockTag) !== "string") { blockTag = await blockTag; } return await this.#perform({ method: "getBlock", blockTag, includeTransactions, shard: shard @@ -1263,7 +1272,7 @@ export class AbstractProvider implements Provider { async waitForTransaction(hash: string, _confirms?: null | number, timeout?: null | number): Promise { const shard = this.shardFromHash(hash); - const confirms = (_confirms != null) ? _confirms: 1; + const confirms = (_confirms != null) ? _confirms : 1; if (confirms === 0) { return this.getTransactionReceipt(hash); } return new Promise(async (resolve, reject) => { @@ -1411,7 +1420,7 @@ export class AbstractProvider implements Provider { const addressableMap = new WeakMap(); const nameMap = new Map(); - sub = { subscriber, tag, addressableMap, nameMap, started: false, listeners: [ ] }; + sub = { subscriber, tag, addressableMap, nameMap, started: false, listeners: [] }; this.#subs.set(tag, sub); } @@ -1448,10 +1457,10 @@ export class AbstractProvider implements Provider { const count = sub.listeners.length; sub.listeners = sub.listeners.filter(({ listener, once }) => { - const payload = new EventPayload(this, (once ? null: listener), event); + const payload = new EventPayload(this, (once ? null : listener), event); try { listener.call(this, ...args, payload); - } catch(error) { } + } catch (error) { } return !once; }); @@ -1480,10 +1489,10 @@ export class AbstractProvider implements Provider { async listeners(event?: ProviderEvent): Promise> { if (event) { const sub = await this.#hasSub(event); - if (!sub) { return [ ]; } + if (!sub) { return []; } return sub.listeners.map(({ listener }) => listener); } - let result: Array = [ ]; + let result: Array = []; for (const { listeners } of this.#subs.values()) { result = result.concat(listeners.map(({ listener }) => listener)); } @@ -1513,7 +1522,7 @@ export class AbstractProvider implements Provider { if (started) { subscriber.stop(); } this.#subs.delete(tag); } else { - for (const [ tag, { started, subscriber } ] of this.#subs) { + for (const [tag, { started, subscriber }] of this.#subs) { if (started) { subscriber.stop(); } this.#subs.delete(tag); } @@ -1523,12 +1532,12 @@ export class AbstractProvider implements Provider { // Alias for "on" async addListener(event: ProviderEvent, listener: Listener): Promise { - return await this.on(event, listener); + return await this.on(event, listener); } // Alias for "off" async removeListener(event: ProviderEvent, listener: Listener): Promise { - return this.off(event, listener); + return this.off(event, listener); } /** diff --git a/src.ts/providers/provider-fallback.ts b/src.ts/providers/provider-fallback.ts index b9456442..cba88603 100644 --- a/src.ts/providers/provider-fallback.ts +++ b/src.ts/providers/provider-fallback.ts @@ -34,7 +34,7 @@ function getTime(): number { return (new Date()).getTime(); } function stringify(value: any): string { return JSON.stringify(value, (key, value) => { - if (typeof(value) === "bigint") { + if (typeof (value) === "bigint") { return { type: "bigint", value: value.toString() }; } return value; @@ -198,11 +198,11 @@ function _normalize(value: any): string { return "[" + (value.map(_normalize)).join(",") + "]"; } - if (typeof(value) === "object" && typeof(value.toJSON) === "function") { + if (typeof (value) === "object" && typeof (value.toJSON) === "function") { return _normalize(value.toJSON()); } - switch (typeof(value)) { + switch (typeof (value)) { case "boolean": case "symbol": return value.toString(); case "bigint": case "number": @@ -212,7 +212,7 @@ function _normalize(value: any): string { case "object": { const keys = Object.keys(value); keys.sort(); - return "{" + keys.map((k) => `${ JSON.stringify(k) }:${ _normalize(value[k]) }`).join(",") + "}"; + return "{" + keys.map((k) => `${JSON.stringify(k)}:${_normalize(value[k])}`).join(",") + "}"; } } @@ -266,7 +266,7 @@ function getMedian(quorum: number, results: Array): undefined | big const errorMap: Map = new Map(); let bestError: null | { weight: number, value: Error } = null; - const values: Array = [ ]; + const values: Array = []; for (const { value, tag, weight } of results) { if (value instanceof Error) { const e = errorMap.get(tag) || { value, weight: 0 }; @@ -289,7 +289,7 @@ function getMedian(quorum: number, results: Array): undefined | big } // Get the sorted values - values.sort((a, b) => ((a < b) ? -1: (b > a) ? 1: 0)); + values.sort((a, b) => ((a < b) ? -1 : (b > a) ? 1 : 0)); const mid = Math.floor(values.length / 2); @@ -389,9 +389,9 @@ export class FallbackProvider extends AbstractProvider { this.#configs = providers.map((p) => { if (p instanceof AbstractProvider) { - return Object.assign({ provider: p }, defaultConfig, defaultState ); + return Object.assign({ provider: p }, defaultConfig, defaultState); } else { - return Object.assign({ }, defaultConfig, p, defaultState ); + return Object.assign({}, defaultConfig, p, defaultState); } }); @@ -416,7 +416,7 @@ export class FallbackProvider extends AbstractProvider { get providerConfigs(): Array { return this.#configs.map((c) => { - const result: any = Object.assign({ }, c); + const result: any = Object.assign({}, c); for (const key in result) { if (key[0] === "_") { delete result[key]; } } @@ -441,15 +441,17 @@ export class FallbackProvider extends AbstractProvider { case "broadcastTransaction": return await provider.broadcastTransaction(req.shard, req.signedTransaction); case "call": - return await provider.call(Object.assign({ }, req.transaction, { blockTag: req.blockTag })); + return await provider.call(Object.assign({}, req.transaction, { blockTag: req.blockTag })); case "chainId": return (await provider.getNetwork()).chainId; case "estimateGas": return await provider.estimateGas(req.transaction); case "getBalance": return await provider.getBalance(req.address, req.blockTag); + case "getOutpointsByAddress": + return await provider.getOutpointsByAddress(req.address); case "getBlock": { - const block = ("blockHash" in req) ? req.blockHash: req.blockTag; + const block = ("blockHash" in req) ? req.blockHash : req.blockTag; return await provider.getBlock(req.shard, block, req.includeTransactions); } case "getBlockNumber": @@ -549,7 +551,7 @@ export class FallbackProvider extends AbstractProvider { async #initialSync(): Promise { let initialSync = this.#initialSyncPromise; if (!initialSync) { - const promises: Array> = [ ]; + const promises: Array> = []; this.#configs.forEach((config) => { promises.push((async () => { await waitForSync(config, 0); @@ -585,7 +587,7 @@ export class FallbackProvider extends AbstractProvider { async #checkQuorum(running: Set, req: PerformActionRequest): Promise { // Get all the result objects - const results: Array = [ ]; + const results: Array = []; for (const runner of running) { if (runner.result != null) { const { tag, value } = normalizeResult(runner.result); @@ -633,6 +635,7 @@ export class FallbackProvider extends AbstractProvider { case "call": case "chainId": case "getBalance": + case "getOutpointsByAddress": case "getTransactionCount": case "getCode": case "getStorage": @@ -646,7 +649,7 @@ export class FallbackProvider extends AbstractProvider { } assert(false, "unsupported method", "UNSUPPORTED_OPERATION", { - operation: `_perform(${ stringify((req).method) })` + operation: `_perform(${stringify((req).method)})` }); } @@ -655,7 +658,7 @@ export class FallbackProvider extends AbstractProvider { // Any promises that are interesting to watch for; an expired stall // or a successful perform - const interesting: Array> = [ ]; + const interesting: Array> = []; let newRunners = 0; for (const runner of running) { diff --git a/src.ts/providers/provider-jsonrpc.ts b/src.ts/providers/provider-jsonrpc.ts index 99dbccd4..1993c903 100644 --- a/src.ts/providers/provider-jsonrpc.ts +++ b/src.ts/providers/provider-jsonrpc.ts @@ -44,25 +44,25 @@ type Timer = ReturnType; const Primitive = "bigint,boolean,function,number,string,symbol".split(/,/g); //const Methods = "getAddress,then".split(/,/g); function deepCopy(value: T): T { - if (value == null || Primitive.indexOf(typeof(value)) >= 0) { + if (value == null || Primitive.indexOf(typeof (value)) >= 0) { return value; } // Keep any Addressable - if (typeof((value).getAddress) === "function") { + if (typeof ((value).getAddress) === "function") { return value; } if (Array.isArray(value)) { return (value.map(deepCopy)); } - if (typeof(value) === "object") { + if (typeof (value) === "object") { return Object.keys(value).reduce((accum, key) => { accum[key] = (value)[key]; return accum; - }, { }); + }, {}); } - throw new Error(`should not happen: ${ value } (${ typeof(value) })`); + throw new Error(`should not happen: ${value} (${typeof (value)})`); } function stall(duration: number): Promise { @@ -203,68 +203,68 @@ const defaultOptions = { * Ethereum API specification. */ export interface JsonRpcTransactionRequest { - /** - * The sender address to use when signing. - */ - from?: string; - - /** - * The target address. - */ - to?: string; - - /** - * The transaction data. - */ - data?: string; - - /** - * The chain ID the transaction is valid on. - */ - chainId?: string; - - /** - * The [[link-eip-2718]] transaction type. - */ - type?: string; - - /** - * The maximum amount of gas to allow a transaction to consume. - * - * In most other places in quais, this is called ``gasLimit`` which - * differs from the JSON-RPC Ethereum API specification. - */ - gas?: string; - - /** - * The gas price per wei for transactions prior to [[link-eip-1559]]. - */ - gasPrice?: string; - - /** - * The maximum fee per gas for [[link-eip-1559]] transactions. - */ - maxFeePerGas?: string; - - /** - * The maximum priority fee per gas for [[link-eip-1559]] transactions. - */ - maxPriorityFeePerGas?: string; - - /** - * The nonce for the transaction. - */ - nonce?: string; - - /** - * The transaction value (in wei). - */ - value?: string; - - /** - * The transaction access list. - */ - accessList?: Array<{ address: string, storageKeys: Array }>; + /** + * The sender address to use when signing. + */ + from?: string; + + /** + * The target address. + */ + to?: string; + + /** + * The transaction data. + */ + data?: string; + + /** + * The chain ID the transaction is valid on. + */ + chainId?: string; + + /** + * The [[link-eip-2718]] transaction type. + */ + type?: string; + + /** + * The maximum amount of gas to allow a transaction to consume. + * + * In most other places in quais, this is called ``gasLimit`` which + * differs from the JSON-RPC Ethereum API specification. + */ + gas?: string; + + /** + * The gas price per wei for transactions prior to [[link-eip-1559]]. + */ + gasPrice?: string; + + /** + * The maximum fee per gas for [[link-eip-1559]] transactions. + */ + maxFeePerGas?: string; + + /** + * The maximum priority fee per gas for [[link-eip-1559]] transactions. + */ + maxPriorityFeePerGas?: string; + + /** + * The nonce for the transaction. + */ + nonce?: string; + + /** + * The transaction value (in wei). + */ + value?: string; + + /** + * The transaction access list. + */ + accessList?: Array<{ address: string, storageKeys: Array }>; } // @TODO: Unchecked Signers @@ -318,7 +318,7 @@ export class JsonRpcSigner extends AbstractSigner { // we look it up for them. if (tx.gasLimit == null) { promises.push((async () => { - tx.gasLimit = await this.provider.estimateGas({ ...tx, from: this.address}); + tx.gasLimit = await this.provider.estimateGas({ ...tx, from: this.address }); })()); } @@ -334,7 +334,7 @@ export class JsonRpcSigner extends AbstractSigner { if (promises.length) { await Promise.all(promises); } const hexTx = this.provider.getRpcTransaction(tx); - return this.provider.send("quai_sendTransaction", [ hexTx ]); + return this.provider.send("quai_sendTransaction", [hexTx]); } async sendTransaction(tx: TransactionRequest): Promise { @@ -348,7 +348,7 @@ export class JsonRpcSigner extends AbstractSigner { // for a response, and we need the actual transaction, so we poll // for it; it should show up very quickly return await (new Promise((resolve, reject) => { - const timeouts = [ 1000, 100 ]; + const timeouts = [1000, 100]; let invalids = 0; const checkTx = async () => { @@ -371,7 +371,7 @@ export class JsonRpcSigner extends AbstractSigner { if (isError(error, "CANCELLED") || isError(error, "BAD_DATA") || isError(error, "NETWORK_ERROR" || isError(error, "UNSUPPORTED_OPERATION"))) { - if (error.info == null) { error.info = { }; } + if (error.info == null) { error.info = {}; } error.info.sendTransactionHash = hash; reject(error); @@ -381,7 +381,7 @@ export class JsonRpcSigner extends AbstractSigner { // Stop-gap for misbehaving backends; see #4513 if (isError(error, "INVALID_ARGUMENT")) { invalids++; - if (error.info == null) { error.info = { }; } + if (error.info == null) { error.info = {}; } error.info.sendTransactionHash = hash; if (invalids > 10) { reject(error); @@ -415,14 +415,14 @@ export class JsonRpcSigner extends AbstractSigner { } const hexTx = this.provider.getRpcTransaction(tx); - return await this.provider.send("quai_signTransaction", [ hexTx ]); + return await this.provider.send("quai_signTransaction", [hexTx]); } async signMessage(_message: string | Uint8Array): Promise { - const message = ((typeof(_message) === "string") ? toUtf8Bytes(_message): _message); + const message = ((typeof (_message) === "string") ? toUtf8Bytes(_message) : _message); return await this.provider.send("personal_sign", [ - hexlify(message), this.address.toLowerCase() ]); + hexlify(message), this.address.toLowerCase()]); } async signTypedData(domain: TypedDataDomain, types: Record>, _value: Record): Promise { @@ -436,14 +436,14 @@ export class JsonRpcSigner extends AbstractSigner { async unlock(password: string): Promise { return this.provider.send("personal_unlockAccount", [ - this.address.toLowerCase(), password, null ]); + this.address.toLowerCase(), password, null]); } // https://github.com/ethereum/wiki/wiki/JSON-RPC#quai_sign async _legacySignMessage(_message: string | Uint8Array): Promise { - const message = ((typeof(_message) === "string") ? toUtf8Bytes(_message): _message); + const message = ((typeof (_message) === "string") ? toUtf8Bytes(_message) : _message); return await this.provider.send("quai_sign", [ - this.address.toLowerCase(), hexlify(message) ]); + this.address.toLowerCase(), hexlify(message)]); } } @@ -487,18 +487,18 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { if (this.#drainTimer) { return; } // If we aren't using batching, no harm in sending it immediately - const stallTime = (this._getOption("batchMaxCount") === 1) ? 0: this._getOption("batchStallTime"); + const stallTime = (this._getOption("batchMaxCount") === 1) ? 0 : this._getOption("batchStallTime"); this.#drainTimer = setTimeout(() => { this.#drainTimer = null; const payloads = this.#payloads; - this.#payloads = [ ]; + this.#payloads = []; while (payloads.length) { // Create payload batches that satisfy our batch constraints - const batch = [ (payloads.shift()) ]; + const batch = [(payloads.shift())]; while (payloads.length) { if (batch.length === this.#options.batchMaxCount) { break; } batch.push((payloads.shift())); @@ -583,9 +583,9 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { super(network, options); this.#nextId = 1; - this.#options = Object.assign({ }, defaultOptions, options || { }); + this.#options = Object.assign({}, defaultOptions, options || {}); - this.#payloads = [ ]; + this.#payloads = []; this.#drainTimer = null; this.#network = null; @@ -600,7 +600,7 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { } const staticNetwork = this._getOption("staticNetwork"); - if (typeof(staticNetwork) === "boolean") { + if (typeof (staticNetwork) === "boolean") { assertArgument(!staticNetwork || network !== "any", "staticNetwork cannot be used on special network 'any'", "options", options); if (staticNetwork && network != null) { this.#network = Network.from(network); @@ -628,7 +628,7 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { * is detected, and if it has changed, the call will reject. */ get _network(): Network { - assert (this.#network, "network is not available yet", "NETWORK_ERROR"); + assert(this.#network, "network is not available yet", "NETWORK_ERROR"); return this.#network; } @@ -658,8 +658,8 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { const feeData = await this.getFeeData(req.shard); if (feeData.maxFeePerGas == null && feeData.maxPriorityFeePerGas == null) { // Network doesn't know about EIP-1559 (and hence type) - req = Object.assign({ }, req, { - transaction: Object.assign({ }, tx, { type: undefined }) + req = Object.assign({}, req, { + transaction: Object.assign({}, tx, { type: undefined }) }); } } @@ -701,7 +701,7 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { if (this.ready) { this.#pendingDetectNetwork = (async () => { try { - const result = Network.from(getBigInt(await this.send("quai_chainId", [ ]))); + const result = Network.from(getBigInt(await this.send("quai_chainId", []))); this.#pendingDetectNetwork = null; return result; } catch (error) { @@ -715,7 +715,7 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { // We are not ready yet; use the primitive _send this.#pendingDetectNetwork = (async () => { const payload: JsonRpcPayload = { - id: this.#nextId++, method: "quai_chainId", params: [ ], jsonrpc: "2.0" + id: this.#nextId++, method: "quai_chainId", params: [], jsonrpc: "2.0" }; this.emit("debug", { action: "sendRpcPayload", payload }); @@ -827,7 +827,7 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { if ((tx)[key] == null) { return; } let dstKey = key; if (key === "gasLimit") { dstKey = "gas"; } - (result)[dstKey] = toQuantity(getBigInt((tx)[key], `tx.${ key }`)); + (result)[dstKey] = toQuantity(getBigInt((tx)[key], `tx.${key}`)); }); // Make sure addresses and data are lowercase @@ -851,36 +851,41 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { getRpcRequest(req: PerformActionRequest): null | { method: string, args: Array } { switch (req.method) { case "chainId": - return { method: "quai_chainId", args: [ ] }; + return { method: "quai_chainId", args: [] }; case "getBlockNumber": - return { method: "quai_blockNumber", args: [ ] }; + return { method: "quai_blockNumber", args: [] }; case "getGasPrice": return { method: "quai_baseFee", - args: [ req.txType ] + args: [req.txType] }; case "getMaxPriorityFeePerGas": - return { method: "quai_maxPriorityFeePerGas", args: []}; - + return { method: "quai_maxPriorityFeePerGas", args: [] }; + case "getBalance": return { method: "quai_getBalance", - args: [ getLowerCase(req.address), req.blockTag ] + args: [getLowerCase(req.address), req.blockTag] }; + case "getOutpointsByAddress": + return { + method: "quai_getOutpointsByAddress", + args: [getLowerCase(req.address)] + }; case "getTransactionCount": return { method: "quai_getTransactionCount", - args: [ getLowerCase(req.address), req.blockTag ] + args: [getLowerCase(req.address), req.blockTag] }; case "getCode": return { method: "quai_getCode", - args: [ getLowerCase(req.address), req.blockTag ] + args: [getLowerCase(req.address), req.blockTag] }; case "getStorage": @@ -896,19 +901,19 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { case "broadcastTransaction": return { method: "quai_sendRawTransaction", - args: [ req.signedTransaction ] + args: [req.signedTransaction] }; case "getBlock": if ("blockTag" in req) { return { method: "quai_getBlockByNumber", - args: [ req.blockTag, !!req.includeTransactions ] + args: [req.blockTag, !!req.includeTransactions] }; } else if ("blockHash" in req) { return { method: "quai_getBlockByHash", - args: [ req.blockHash, !!req.includeTransactions ] + args: [req.blockHash, !!req.includeTransactions] }; } break; @@ -916,25 +921,25 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { case "getTransaction": return { method: "quai_getTransactionByHash", - args: [ req.hash ] + args: [req.hash] }; case "getTransactionReceipt": return { method: "quai_getTransactionReceipt", - args: [ req.hash ] + args: [req.hash] }; case "call": return { method: "quai_call", - args: [ this.getRpcTransaction(req.transaction), req.blockTag ] + args: [this.getRpcTransaction(req.transaction), req.blockTag] }; case "estimateGas": { return { method: "quai_estimateGas", - args: [ this.getRpcTransaction(req.transaction) ] + args: [this.getRpcTransaction(req.transaction)] }; } @@ -962,14 +967,14 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { case "getQiRateAtBlock": { return { method: "quai_qiRateAtBlock", - args: [ req.blockTag, req.amt ] + args: [req.blockTag, req.amt] } } case "getQuaiRateAtBlock": { return { method: "quai_quaiRateAtBlock", - args: [ req.blockTag, req.amt ] + args: [req.blockTag, req.amt] } } @@ -981,7 +986,7 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { req.filter.address = getLowerCase(req.filter.address); } } - return { method: "quai_getLogs", args: [ req.filter ] }; + return { method: "quai_getLogs", args: [req.filter] }; } return null; @@ -1011,9 +1016,9 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { const result = spelunkData(error); const e = AbiCoder.getBuiltinCallException( - (method === "quai_call") ? "call": "estimateGas", + (method === "quai_call") ? "call" : "estimateGas", ((payload).params[0]), - (result ? result.data: null) + (result ? result.data : null) ); e.info = { error, payload }; return e; @@ -1024,7 +1029,7 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { const message = JSON.stringify(spelunkMessage(error)); - if (typeof(error.message) === "string" && error.message.match(/user denied|quais-user-denied/i)) { + if (typeof (error.message) === "string" && error.message.match(/user denied|quais-user-denied/i)) { const actionMap: Record = { quai_sign: "signMessage", personal_sign: "signMessage", @@ -1036,7 +1041,7 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { }; return makeError(`user rejected action`, "ACTION_REJECTED", { - action: (actionMap[method] || "unknown") , + action: (actionMap[method] || "unknown"), reason: "rejected", info: { payload, error } }); @@ -1131,16 +1136,16 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { * * Throws if the account doesn't exist. */ - + // Works only if using a local node or browser wallet for this, otherwise cannot get accounts - + async getSigner(address?: number | string): Promise { if (address == null) { address = 0; } - const accountsPromise = this.send("quai_accounts", [ ]); + const accountsPromise = this.send("quai_accounts", []); // Account index - if (typeof(address) === "number") { + if (typeof (address) === "number") { const accounts = >(await accountsPromise); if (address >= accounts.length) { throw new Error("no such account"); } return new JsonRpcSigner(this, accounts[address]); @@ -1163,7 +1168,7 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { } async listAccounts(): Promise> { - const accounts: Array = await this.send("quai_accounts", [ ]); + const accounts: Array = await this.send("quai_accounts", []); return accounts.map((a) => new JsonRpcSigner(this, a)); } @@ -1180,7 +1185,7 @@ export abstract class JsonRpcApiProvider extends AbstractProvider { reject(makeError("provider destroyed; cancelled request", "UNSUPPORTED_OPERATION", { operation: payload.method })); } - this.#payloads = [ ]; + this.#payloads = []; // Parent clean-up super.destroy(); @@ -1204,7 +1209,7 @@ export class JsonRpcProvider extends JsonRpcApiProvider { if (Array.isArray(urls)) { this.initPromise = this.initUrlMap(urls); - } else if(typeof urls === "string") { + } else if (typeof urls === "string") { this.initPromise = this.initUrlMap([urls]); } else { this.initPromise = this.initUrlMap(urls.clone()); @@ -1217,7 +1222,7 @@ export class JsonRpcProvider extends JsonRpcApiProvider { } _getConnection(shard?: string): FetchRequest { - const connection = this.connect[this.connect.length - 1]!.clone(); + const connection = this.connect[this.connect.length - 1]!.clone(); if (typeof shard === "string") { const shardBytes = this.shardBytes(shard); connection.url = this._urlMap.get(shardBytes) ?? connection.url; @@ -1229,7 +1234,7 @@ export class JsonRpcProvider extends JsonRpcApiProvider { // All requests are over HTTP, so we can just start handling requests // We do this here rather than the constructor so that we don't send any // requests to the network (i.e. quai_chainId) until we absolutely have to. -// await this.initPromise; + // await this.initPromise; await this._start(); return await super.send(method, params, shard); @@ -1244,7 +1249,7 @@ export class JsonRpcProvider extends JsonRpcApiProvider { response.assertOk(); let resp = response.bodyJson; - if (!Array.isArray(resp)) { resp = [ resp ]; } + if (!Array.isArray(resp)) { resp = [resp]; } return resp; } @@ -1254,12 +1259,12 @@ function spelunkData(value: any): null | { message: string, data: string } { if (value == null) { return null; } // These *are* the droids we're looking for. - if (typeof(value.message) === "string" && value.message.match(/revert/i) && isHexString(value.data)) { + if (typeof (value.message) === "string" && value.message.match(/revert/i) && isHexString(value.data)) { return { message: value.message, data: value.data }; } // Spelunk further... - if (typeof(value) === "object") { + if (typeof (value) === "object") { for (const key in value) { const result = spelunkData(value[key]); if (result) { return result; } @@ -1268,7 +1273,7 @@ function spelunkData(value: any): null | { message: string, data: string } { } // Might be a JSON string we can further descend... - if (typeof(value) === "string") { + if (typeof (value) === "string") { try { return spelunkData(JSON.parse(value)); } catch (error) { } @@ -1281,19 +1286,19 @@ function _spelunkMessage(value: any, result: Array): void { if (value == null) { return; } // These *are* the droids we're looking for. - if (typeof(value.message) === "string") { + if (typeof (value.message) === "string") { result.push(value.message); } // Spelunk further... - if (typeof(value) === "object") { + if (typeof (value) === "object") { for (const key in value) { _spelunkMessage(value[key], result); } } // Might be a JSON string we can further descend... - if (typeof(value) === "string") { + if (typeof (value) === "string") { try { return _spelunkMessage(JSON.parse(value), result); } catch (error) { } @@ -1301,7 +1306,7 @@ function _spelunkMessage(value: any, result: Array): void { } function spelunkMessage(value: any): Array { - const result: Array = [ ]; + const result: Array = []; _spelunkMessage(value, result); return result; } diff --git a/src.ts/providers/provider.ts b/src.ts/providers/provider.ts index 6304ad2a..cea101f7 100644 --- a/src.ts/providers/provider.ts +++ b/src.ts/providers/provider.ts @@ -12,7 +12,7 @@ import type { AccessList, AccessListish, TransactionLike } from "../transaction/ import type { ContractRunner } from "./contracts.js"; import type { Network } from "./network.js"; -import type { UTXOEntry, UTXOTransactionOutput } from "../transaction/utxo.js"; +import type { Outpoint, UTXOEntry, UTXOTransactionOutput } from "../transaction/utxo.js"; const BN_0 = BigInt(0); @@ -306,7 +306,7 @@ export interface PreparedTransactionRequest { * types. */ export function copyRequest(req: TransactionRequest): PreparedTransactionRequest { - const result: any = { }; + const result: any = {}; // These could be addresses, ENS names or Addressables if (req.to) { result.to = req.to; } @@ -317,13 +317,13 @@ export function copyRequest(req: TransactionRequest): PreparedTransactionRequest const bigIntKeys = "chainId,gasLimit,gasPrice,maxFeePerGas,maxPriorityFeePerGas,value".split(/,/); for (const key of bigIntKeys) { if (!(key in req) || (req)[key] == null) { continue; } - result[key] = getBigInt((req)[key], `request.${ key }`); + result[key] = getBigInt((req)[key], `request.${key}`); } const numberKeys = "type,nonce".split(/,/); for (const key of numberKeys) { if (!(key in req) || (req)[key] == null) { continue; } - result[key] = getNumber((req)[key], `request.${ key }`); + result[key] = getNumber((req)[key], `request.${key}`); } if (req.accessList) { @@ -497,16 +497,16 @@ export class Block implements BlockParams, Iterable { */ constructor(block: BlockParams, provider: Provider) { - + this.#transactions = block.transactions.map((tx) => { - if (typeof(tx) !== "string") { + if (typeof (tx) !== "string") { return new TransactionResponse(tx, provider); } return tx; }); this.#extTransactions = block.extTransactions.map((tx) => { - if (typeof(tx) !== "string") { + if (typeof (tx) !== "string") { return new TransactionResponse(tx, provider); } return tx; @@ -564,14 +564,14 @@ export class Block implements BlockParams, Iterable { */ get transactions(): ReadonlyArray { return this.#transactions.map((tx) => { - if (typeof(tx) === "string") { return tx; } + if (typeof (tx) === "string") { return tx; } return tx.hash; }); } get extTransactions(): ReadonlyArray { return this.#extTransactions.map((tx) => { - if (typeof(tx) === "string") { return tx; } + if (typeof (tx) === "string") { return tx; } return tx.hash; }); } @@ -588,10 +588,10 @@ export class Block implements BlockParams, Iterable { const txs = this.#transactions.slice(); // Doesn't matter... - if (txs.length === 0) { return [ ]; } + if (txs.length === 0) { return []; } // Make sure we prefetched the transactions - assert(typeof(txs[0]) === "object", "transactions were not prefetched with block request", "UNSUPPORTED_OPERATION", { + assert(typeof (txs[0]) === "object", "transactions were not prefetched with block request", "UNSUPPORTED_OPERATION", { operation: "transactionResponses()" }); @@ -602,10 +602,10 @@ export class Block implements BlockParams, Iterable { const txs = this.#extTransactions.slice(); // Doesn't matter... - if (txs.length === 0) { return [ ]; } + if (txs.length === 0) { return []; } // Make sure we prefetched the transactions - assert(typeof(txs[0]) === "object", "transactions were not prefetched with block request", "UNSUPPORTED_OPERATION", { + assert(typeof (txs[0]) === "object", "transactions were not prefetched with block request", "UNSUPPORTED_OPERATION", { operation: "transactionResponses()" }); @@ -625,11 +625,11 @@ export class Block implements BlockParams, Iterable { sha3Uncles, size, evmRoot, utxoRoot, uncles, transactionsRoot, extRollupRoot, extTransactionsRoot } = this; - + // Using getters to retrieve the transactions and extTransactions const transactions = this.transactions; const extTransactions = this.extTransactions; - + return { _type: "Block", baseFeePerGas: toJson(baseFeePerGas), @@ -637,34 +637,34 @@ export class Block implements BlockParams, Iterable { extraData, gasLimit: toJson(gasLimit), gasUsed: toJson(gasUsed), - hash, - miner, - nonce, - number, - parentHash, + hash, + miner, + nonce, + number, + parentHash, timestamp, - manifestHash, - location, - parentDeltaS, + manifestHash, + location, + parentDeltaS, parentEntropy, - order, - subManifest, - totalEntropy, - mixHash, + order, + subManifest, + totalEntropy, + mixHash, receiptsRoot, - sha3Uncles, - size, + sha3Uncles, + size, evmRoot, utxoRoot, - uncles, + uncles, transactionsRoot, - extRollupRoot, + extRollupRoot, extTransactionsRoot, transactions, // Includes the transaction hashes or full transactions based on the prefetched data extTransactions // Includes the extended transaction hashes or full transactions based on the prefetched data }; } - + [Symbol.iterator](): Iterator { let index = 0; @@ -700,13 +700,13 @@ export class Block implements BlockParams, Iterable { async getTransaction(indexOrHash: number | string): Promise { // Find the internal value by its index or hash let tx: string | TransactionResponse | undefined = undefined; - if (typeof(indexOrHash) === "number") { + if (typeof (indexOrHash) === "number") { tx = this.#transactions[indexOrHash]; } else { const hash = indexOrHash.toLowerCase(); for (const v of this.#transactions) { - if (typeof(v) === "string") { + if (typeof (v) === "string") { if (v !== hash) { continue; } tx = v; break; @@ -719,7 +719,7 @@ export class Block implements BlockParams, Iterable { } if (tx == null) { throw new Error("no such tx"); } - if (typeof(tx) === "string") { + if (typeof (tx) === "string") { return (await this.provider.getTransaction(tx)); } else { return tx; @@ -729,13 +729,13 @@ export class Block implements BlockParams, Iterable { async getExtTransaction(indexOrHash: number | string): Promise { // Find the internal value by its index or hash let tx: string | TransactionResponse | undefined = undefined; - if (typeof(indexOrHash) === "number") { + if (typeof (indexOrHash) === "number") { tx = this.#extTransactions[indexOrHash]; - + } else { const hash = indexOrHash.toLowerCase(); for (const v of this.#extTransactions) { - if (typeof(v) === "string") { + if (typeof (v) === "string") { if (v !== hash) { continue; } tx = v; break; @@ -748,7 +748,7 @@ export class Block implements BlockParams, Iterable { } if (tx == null) { throw new Error("no such tx"); } - if (typeof(tx) === "string") { + if (typeof (tx) === "string") { return (await this.provider.getTransaction(tx)); } else { return tx; @@ -763,7 +763,7 @@ export class Block implements BlockParams, Iterable { */ getPrefetchedTransaction(indexOrHash: number | string): TransactionResponse { const txs = this.prefetchedTransactions; - if (typeof(indexOrHash) === "number") { + if (typeof (indexOrHash) === "number") { return txs[indexOrHash]; } @@ -909,7 +909,7 @@ export class Log implements LogParams { */ async getBlock(shard: string): Promise { const block = await this.provider.getBlock(shard, this.blockHash); - assert(!!block, "failed to find transaction", "UNKNOWN_ERROR", { }); + assert(!!block, "failed to find transaction", "UNKNOWN_ERROR", {}); return block; } @@ -918,7 +918,7 @@ export class Log implements LogParams { */ async getTransaction(): Promise { const tx = await this.provider.getTransaction(this.transactionHash); - assert(!!tx, "failed to find transaction", "UNKNOWN_ERROR", { }); + assert(!!tx, "failed to find transaction", "UNKNOWN_ERROR", {}); return tx; } @@ -928,7 +928,7 @@ export class Log implements LogParams { */ async getTransactionReceipt(): Promise { const receipt = await this.provider.getTransactionReceipt(this.transactionHash); - assert(!!receipt, "failed to find transaction receipt", "UNKNOWN_ERROR", { }); + assert(!!receipt, "failed to find transaction receipt", "UNKNOWN_ERROR", {}); return receipt; } @@ -1350,9 +1350,9 @@ export class TransactionResponse implements TransactionLike, Transaction */ readonly accessList!: null | AccessList; - readonly inputs ?: Array ; + readonly inputs?: Array; - readonly outputs ?: Array ; + readonly outputs?: Array; #startBlock: number; @@ -1362,8 +1362,8 @@ export class TransactionResponse implements TransactionLike, Transaction constructor(tx: TransactionResponseParams, provider: Provider) { this.provider = provider; - this.blockNumber = (tx.blockNumber != null) ? tx.blockNumber: null; - this.blockHash = (tx.blockHash != null) ? tx.blockHash: null; + this.blockNumber = (tx.blockNumber != null) ? tx.blockNumber : null; + this.blockHash = (tx.blockHash != null) ? tx.blockHash : null; this.hash = tx.hash; this.index = tx.index; @@ -1378,13 +1378,13 @@ export class TransactionResponse implements TransactionLike, Transaction this.data = tx.data; this.value = tx.value; - this.maxPriorityFeePerGas = (tx.maxPriorityFeePerGas != null) ? tx.maxPriorityFeePerGas: null; - this.maxFeePerGas = (tx.maxFeePerGas != null) ? tx.maxFeePerGas: null; + this.maxPriorityFeePerGas = (tx.maxPriorityFeePerGas != null) ? tx.maxPriorityFeePerGas : null; + this.maxFeePerGas = (tx.maxFeePerGas != null) ? tx.maxFeePerGas : null; this.chainId = tx.chainId; this.signature = tx.signature; - this.accessList = (tx.accessList != null) ? tx.accessList: null; + this.accessList = (tx.accessList != null) ? tx.accessList : null; this.#startBlock = -1; } @@ -1396,7 +1396,7 @@ export class TransactionResponse implements TransactionLike, Transaction blockNumber, blockHash, index, hash, type, to, from, nonce, data, signature, accessList, } = this; - let result ={ + let result = { _type: "TransactionReceipt", accessList, blockNumber, blockHash, chainId: toJson(this.chainId), @@ -1411,7 +1411,7 @@ export class TransactionResponse implements TransactionLike, Transaction return result; } - + /** * Resolves to the Block that this transaction was included in. @@ -1470,12 +1470,12 @@ export class TransactionResponse implements TransactionLike, Transaction * wait until enough confirmations have completed. */ async wait(_confirms?: number, _timeout?: number): Promise { - const confirms = (_confirms == null) ? 1: _confirms; - const timeout = (_timeout == null) ? 0: _timeout; + const confirms = (_confirms == null) ? 1 : _confirms; + const timeout = (_timeout == null) ? 0 : _timeout; let startBlock = this.#startBlock let nextScan = -1; - let stopScanning = (startBlock === -1) ? true: false; + let stopScanning = (startBlock === -1) ? true : false; const shard = shardFromHash(this.hash); const checkReplacement = async () => { // Get the current transaction count for this sender @@ -1537,7 +1537,7 @@ export class TransactionResponse implements TransactionLike, Transaction let reason: "replaced" | "repriced" | "cancelled" = "replaced"; if (tx.data === this.data && tx.to === this.to && tx.value === this.value) { reason = "repriced"; - } else if (tx.data === "0x" && tx.from === tx.to && tx.value === BN_0) { + } else if (tx.data === "0x" && tx.from === tx.to && tx.value === BN_0) { reason = "cancelled" } @@ -1588,7 +1588,7 @@ export class TransactionResponse implements TransactionLike, Transaction const waiter = new Promise((resolve, reject) => { // List of things to cancel when we have a result (one way or the other) - const cancellers: Array<() => void> = [ ]; + const cancellers: Array<() => void> = []; const cancel = () => { cancellers.forEach((c) => c()); }; // On cancel, stop scanning for replacements @@ -1748,15 +1748,17 @@ function createRemovedTransactionFilter(tx: { hash: string, blockHash: string, b } function createRemovedLogFilter(log: { blockHash: string, transactionHash: string, blockNumber: number, address: string, data: string, topics: ReadonlyArray, index: number }): OrphanFilter { - return { orphan: "drop-log", log: { - transactionHash: log.transactionHash, - blockHash: log.blockHash, - blockNumber: log.blockNumber, - address: log.address, - data: log.data, - topics: Object.freeze(log.topics.slice()), - index: log.index - } }; + return { + orphan: "drop-log", log: { + transactionHash: log.transactionHash, + blockHash: log.blockHash, + blockNumber: log.blockNumber, + address: log.address, + data: log.data, + topics: Object.freeze(log.topics.slice()), + index: log.index + } + }; } ////////////////////// @@ -1919,6 +1921,14 @@ export interface Provider extends ContractRunner, EventEmitterable; + /** + * Get the UTXO entries for %%address%%. + * + * @note On nodes without archive access enabled, the %%blockTag%% may be + * **silently ignored** by the node, which may cause issues if relied on. + */ + getOutpointsByAddress(address: AddressLike): Promise>; + /** * Get the number of transactions ever sent for %%address%%, which * is used as the ``nonce`` when sending a transaction. If diff --git a/src.ts/transaction/utxo.ts b/src.ts/transaction/utxo.ts index 32574956..435b8e68 100644 --- a/src.ts/transaction/utxo.ts +++ b/src.ts/transaction/utxo.ts @@ -2,13 +2,14 @@ import { getAddress } from "../address/index"; import { getBigInt } from "../utils/index"; import type { BigNumberish } from "../utils/index"; -export type OutPoint = { - txhash: string; - index: number; +export type Outpoint = { + Txhash: string; + Index: number; + Denomination: number; }; export type UTXOTransactionInput = { - previousOutPoint: OutPoint; + previousOutPoint: Outpoint; pubKey: Uint8Array; };