Skip to content
This repository was archived by the owner on Mar 5, 2025. It is now read-only.
Merged
72 changes: 52 additions & 20 deletions packages/web3-core/src/web3_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
Web3AccountProvider,
SupportedProviders,
HexString,
EthExecutionAPI,
} from 'web3-types';
import { isNullish } from 'web3-utils';
import { ExistingPluginNamespaceError } from 'web3-errors';
Expand All @@ -36,8 +37,7 @@ import { Web3BatchRequest } from './web3_batch_request';

// To avoid circular dependencies, we need to export type from here.
export type Web3ContextObject<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
API extends Web3APISpec = any,
API extends Web3APISpec = unknown,
RegisteredSubs extends {
[key: string]: Web3SubscriptionConstructor<API>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand All @@ -54,8 +54,7 @@ export type Web3ContextObject<
};

export type Web3ContextInitOptions<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
API extends Web3APISpec = any,
API extends Web3APISpec = unknown,
RegisteredSubs extends {
[key: string]: Web3SubscriptionConstructor<API>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand All @@ -70,23 +69,22 @@ export type Web3ContextInitOptions<
wallet?: Web3BaseWallet<Web3BaseWalletAccount>;
};

export type Web3ContextConstructor<
// eslint-disable-next-line no-use-before-define, @typescript-eslint/no-explicit-any
T extends Web3Context<any>,
T2 extends unknown[],
> = new (...args: [...extras: T2, context: Web3ContextObject]) => T;
// eslint-disable-next-line no-use-before-define
export type Web3ContextConstructor<T extends Web3Context, T2 extends unknown[]> = new (
...args: [...extras: T2, context: Web3ContextObject]
) => T;

// To avoid circular dependencies, we need to export type from here.
export type Web3ContextFactory<
// eslint-disable-next-line no-use-before-define, @typescript-eslint/no-explicit-any
T extends Web3Context<any>,
// eslint-disable-next-line no-use-before-define
T extends Web3Context,
T2 extends unknown[],
> = Web3ContextConstructor<T, T2> & {
fromContextObject(this: Web3ContextConstructor<T, T2>, contextObject: Web3ContextObject): T;
};

export class Web3Context<
API extends Web3APISpec,
API extends Web3APISpec = unknown,
RegisteredSubs extends {
[key: string]: Web3SubscriptionConstructor<API>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -174,7 +172,7 @@ export class Web3Context<
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
public static fromContextObject<T extends Web3Context<any>, T3 extends unknown[]>(
public static fromContextObject<T extends Web3Context, T3 extends unknown[]>(
this: Web3ContextConstructor<T, T3>,
...args: [Web3ContextObject, ...T3]
) {
Expand All @@ -200,8 +198,7 @@ export class Web3Context<
* and link it to current context. This can be used to initiate a global context object
* and then use it to create new objects of any type extended by `Web3Context`.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public use<T extends Web3Context<any>, T2 extends unknown[]>(
public use<T extends Web3Context, T2 extends unknown[]>(
ContextRef: Web3ContextConstructor<T, T2>,
...args: [...T2]
) {
Expand All @@ -220,7 +217,7 @@ export class Web3Context<
/**
* Link current context to another context.
*/
public link<T extends Web3Context<API, RegisteredSubs>>(parentContext: T) {
public link<T extends Web3Context>(parentContext: T) {
Comment on lines -223 to +220
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API should be passed here otherwise the specification of ChainlinkPluginAPI is meaningless. The purpose of specifying the ChainlinkPluginAPI (as we do with EthExecutionApi) is to provide type safety when using the requestManager e.g. API specifies what methods are available to call and their corresponding typed params when doing things like so:

image

which gives us type safety like so:

image

Removing <API, RegisteredSubs> for link means API gets default to unknown as you've added API extends Web3APISpec = unknown, which yields:

image

which means TypeScript is no longer aware of what's available for the requestManager to call, what params the specified method takes, and what the return type of the request is

Copy link
Contributor Author

@Muhammad-Altabba Muhammad-Altabba Sep 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, requestManager and link<T extends Web3Context> are separate. And so not passing API to link<T extends Web3Context> does not affect requestManager. And I kept it untouched as-is:

export class Web3RequestManager<
	API extends Web3APISpec = EthExecutionAPI,
> extends Web3EventEmitter<{
	[key in Web3RequestManagerEvent]: SupportedProviders<API> | undefined;
}> {
...

And so the safe typing is working well:
image

And, if I provided no params to eth_getBalance then running yarn build would give the following error:

web3-eth: src/rpc_methods.ts(99,3): error TS2322: Type '[]' is not assignable to type '[address: string, blockNumber: BlockNumberOrTag]'.

So, I think you just had a cashing problem when checking out this branch. Kindly try again by running yarn build. And close vscode and reopen it, if it was still not showing you the correct error...

And let me know, please, if there is any type-safe feature that is not working as expected.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The third screenshot is the inferred type when using the requestManager for the plugin. The EthExecutionApi (i.e. the requestManager when used within the web3-eth package) still resolves as expected with your approach, but the same API type resolution does not occur for ChainlinkPluginAPI - this is what #5480 is attempting to solve. I don't think making use of unknown is the best solution for this issue as we do know what the API is because we specify it. The changes in #5480 should allow us to pass an API that's not EthExecutionApi (or extends it as Web3EthExecutionApi does), but as mentioned on our call and below, our code around subscriptions/logs is hardcoded to handle EthExecutionApi and therefore throws type errors when trying to use an API that doesn't have eth_subscribe

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the plugin writer would like to use this._requestManager, then the plugin api would need to be something like:

// This is defined as a type, because defining this as an interface will fool the compiler to not well-detect
//  the API at EthExecutionAPI when passing it to Web3PluginBase.
export type ChainlinkPluginAPI = EthExecutionAPI & {
	getPrice: () => Promise<Price>;
};

And then all the type-safety for the request manager would work inside the plugin.


However, I also I caught the source of confusion for getPrice. This has nothing to do with the ChainlinkPluginAPI. This should be only inside ChainlinkPlugin. And, actually, when the user writes web3.chainlink, it should be of the type ChainlinkPlugin and not ChainlinkPluginAPI.
So, 2 places needed to be fixed for that:
First is the definition of the chainlink variable:

declare module 'web3' {
	interface Web3 {
		chainlink: ChainlinkPlugin; // Not ChainlinkPluginAPI
	}
}

And is that the type of ChainlinkPluginAPI should contain only the JSON RPC methods for the connected node. So, ti should not contain getPrice. But if the node provides some non-standard JSON RPC method. Or JSON RPC methods for substrate, for example (if the plugin was expanded later for such a possibility).


So, I pushed a commit that addresses those points in addition to a few enhancements....
Please, let me know if you have any other concerns about the provided solution. And if so, please provide a code snippet of what you would like to do exactly and I will help investigating that.

Copy link
Contributor

@spacesailor24 spacesailor24 Sep 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea I was totally thinking about ChainlinkPluginAPI incorrectly - it's not needed given that there are no JSON RPC methods being added as you've mentioned. I think it's fine for Web3PluginBase to just default to Web3APISpec (so the user doesn't have to worry about passing the generic if they're not adding a JSON RPC API) as added by this commit

The rest of the changes you've made in this PR (namely around Web3Context) I don't think are needed for #5393, but probably do need to be incorporated into #5480. #5480 is actually trying to solve an issue not strictly related to #5393 and that's fixing the existing architecture around Web3Context and the RequestManager when you pass an API that doesn't extend EthExecutionApi - which I don't believe should be a requirement when a plugin/user wants to add JSON RPC methods (it's currently a requirement since code around log subscriptions is currently hardcoded to use EthExecutionApi as mentioned here) I've also been thinking about this incorrectly, the issue I thought we had regarding instantiating a Web3Context with an API that does not extend EthExecutionApi is non-existent as demonstrated by the following:

export type FooApi = {
	foo_bar: (arg1: string, arg2: number) => boolean;
	bar_foo: () => string;
}

const web3Context = new Web3Context<FooApi>('http://127.0.0.1:8545');
web3Context.requestManager.send({
	method: 'foo_bar',
	params: [] // This errors as expected (see screenshot)
})

web3Context.requestManager.send({
	method: 'bar_foo',
	params: []
})

image

The error I was actually seeing is fixed by extending EthExecutionApi like you've mentioned above:

export type ChainlinkPluginAPI = EthExecutionAPI & {
	getPrice: () => Promise<Price>;
};

Even though this ChainlinkPluginAPI is not actually needed for the plugin (since we're not adding support for any new JSON RPC methods), for the sake of this example, let's just assume it's needed. When attempting to do

interface Price {
	roundId: string;
	answer: string;
	startedAt: string;
	updatedAt: string;
	answeredInRound: string;
}

interface ChainlinkPluginAPI extends Web3APISpec {
	getPrice: () => Promise<Price>;
}

declare module 'web3/dist/web3' {
	interface Web3 {
		chainlink: ChainlinkPluginAPI;
	}
}

export class ChainlinkPlugin extends Web3PluginBase<Web3APISpec> {
	public pluginNamespace = 'chainlink';

	protected readonly _contract: Contract<typeof AggregatorV3InterfaceABI>;

	public constructor(abi: ContractAbi, address: Address) {
		super();
		this._contract = new Contract(abi, address);
	}

	public async getPrice() {
		if (this._contract.currentProvider === undefined) this._contract.link(this);
		return this._contract.methods.latestRoundData().call();
	}
}

You receive the following error for the line

if (this._contract.currentProvider === undefined) this._contract.link(this);
Argument of type 'this' is not assignable to parameter of type 'Web3Context<EthExecutionAPI, { logs: typeof LogsSubscription; newHeads: typeof NewHeadsSubscription; newBlockHeaders: typeof NewHeadsSubscription; }>'.
  Type 'ChainlinkPlugin' is not assignable to type 'Web3Context<EthExecutionAPI, { logs: typeof LogsSubscription; newHeads: typeof NewHeadsSubscription; newBlockHeaders: typeof NewHeadsSubscription; }>'.
    Types of property '_requestManager' are incompatible.
      Type 'Web3RequestManager<ChainlinkPluginAPI>' is not assignable to type 'Web3RequestManager<EthExecutionAPI>'.
        Type 'ChainlinkPluginAPI' is missing the following properties from type 'EthExecutionAPI': eth_getBlockByHash, eth_getBlockByNumber, eth_getBlockTransactionCountByHash, eth_getBlockTransactionCountByNumber, and 44 more.ts(2345)

I was thinking ChainlinkPluginAPI shouldn't need to extend EthExeuctionAPI in this case because it would add a bunch of JSON RPC methods that aren't going to be used for the plugin. However, ChainlinkPluginAPI needs to extend EthExeuctionAPI because it's using web3-eth-contract which depends on JSON RPC methods defined by EthExeuctionAPI to perform certain actions. The error in my thinking was that you don't need EthExecutionAPI just to call methods on a deployed contract (as the example Chainlink plugin is doing), but that would entail removing functionality from the web3-eth-contract to get it to work which just doesn't make sense

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great @spacesailor24 👍
I am happy to see that this MR helped resolve the 2 blockers for #5393 and provided clarifications 😄

And so it is time to merge this MR.

However, because I think that, there are some copied ideas from this MR to #5393 (and this is also the reason for having conflicts in the MR with the destination branch). I suggest you do one of the following:

  • Ideally, it would be better to preserve the history and the orders of the commits. And so I suggest you to rebase your branch to an earlier commit and then re-push the commits from this branch and from your branch in the order they came. And before doing this I recommend that you save the commits from both branches and the date of each one. And also to have a full copy of the local folder, that you can use in case things were messed-up. This will keep the contribution clean of who did what at which time and it would be easier to trace if later needed and this is the best practice, I think.
  • The other option is easier but will vanish the valuable history. It is to resolve the conflicts in this MR and merge it with yours. This is not the best option because there were some ideas copied from this MR to the other MR in parallel instead of merging it early. But I am OK with that if you faced issues when trying the first option.

And by the way, I also pushed some more enhancements and comments to the code so when someone later would implement a plugin would have a clearer understanding, I hope. And this included 2 classes Web3PluginBase and Web3EthPluginBase 😄.

this.setConfig(parentContext.getConfig());
this._requestManager = parentContext.requestManager;
this.provider = parentContext.provider;
Expand Down Expand Up @@ -283,17 +280,52 @@ export class Web3Context<

// To avoid cycle dependency declare this type in this file
// TODO: When we have `web3-types` package we can share TransactionType
export type TransactionBuilder<
// eslint-disable-next-line @typescript-eslint/no-explicit-any
API extends Web3APISpec = any,
> = <ReturnType = Record<string, unknown>>(options: {
export type TransactionBuilder<API extends Web3APISpec = unknown> = <
ReturnType = Record<string, unknown>,
>(options: {
transaction: Record<string, unknown>;
web3Context: Web3Context<API>;
privateKey?: HexString | Buffer;
}) => Promise<ReturnType>;

/**
* Extend this class when creating a plugin that either doesn't require {@link EthExecutionAPI},
* or interacts with a RPC node that doesn't fully implement {@link EthExecutionAPI}.
*
* To add type support for RPC methods to the {@link Web3RequestManager},
* define a {@link Web3APISpec} and pass it as a generic to Web3PluginBase like so:
*
* ```ts
* type CustomRpcApi = {
* custom_rpc_method: () => string;
* custom_rpc_method_with_parameters: (parameter1: string, parameter2: number) => string;
* };
*
* class CustomPlugin extends Web3PluginBase<CustomRpcApi> {...}
* ```
*/
export abstract class Web3PluginBase<
API extends Web3APISpec = Web3APISpec,
> extends Web3Context<API> {
public abstract pluginNamespace: string;
}

/**
* Extend this class when creating a plugin that makes use of {@link EthExecutionAPI},
* or depends on other Web3 packages (such as `web3-eth-contract`) that depend on {@link EthExecutionAPI}.
*
* To add type support for RPC methods to the {@link Web3RequestManager} (in addition to {@link EthExecutionAPI}),
* define a {@link Web3APISpec} and pass it as a generic to Web3PluginBase like so:
*
* ```ts
* type CustomRpcApi = {
* custom_rpc_method: () => string;
* custom_rpc_method_with_parameters: (parameter1: string, parameter2: number) => string;
* };
*
* class CustomPlugin extends Web3PluginBase<CustomRpcApi> {...}
* ```
*/
export abstract class Web3EthPluginBase<API extends Web3APISpec = unknown> extends Web3PluginBase<
API & EthExecutionAPI
> {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
This file is part of web3.js.

web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/

pragma solidity ^0.8.7;

contract SampleStorageContract {

uint256 uintNum;

event NEWNUM(uint256 param);

function storeNum(uint256 param) public {
uintNum = param;
emit NEWNUM(param);
}

function retrieveNum() public view returns (uint256){
return uintNum;
}
}
59 changes: 59 additions & 0 deletions packages/web3-eth-contract/test/fixtures/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
This file is part of web3.js.

web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/

// sample storage contract ABI
export const sampleStorageContractABI = [
{
anonymous: false,
inputs: [
{
indexed: false,
internalType: 'uint256',
name: 'param',
type: 'uint256',
},
],
name: 'NEWNUM',
type: 'event',
},
{
inputs: [],
name: 'retrieveNum',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint256',
name: 'param',
type: 'uint256',
},
],
name: 'storeNum',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
];
142 changes: 142 additions & 0 deletions packages/web3-eth-contract/test/fixtures/unitTestFixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
This file is part of web3.js.

web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/

export const getLogsData = {
request: {
fromBlock: 'earliest',
toBlock: 'latest',
topics: ['0x7d7846723bda52976e0286c6efffee937ee9f76817a867ec70531ad29fb1fc0e'],
},

response: [
{
logIndex: 1,
transactionIndex: 0,
transactionHash: '0xbe70733bcf87282c0ba9bf3c0e2d545084fad48bd571c314140c8dc1db882673',
blockHash: '0x78755c18c9a0a1283fa04b2f78c7794c249395b08f7f7dff304034d64d6a1607',
blockNumber: 25,
address: '0x2D029a4bd792d795f35e0583F64eD9DedeBBa849',
data: '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000',
topics: ['0x7d7846723bda52976e0286c6efffee937ee9f76817a867ec70531ad29fb1fc0e'],
type: 'mined',
id: 'log_886b29f0',
},
],
};

export const getPastEventsData = {
event: 'GREETING_CHANGED',
response: [
{
logIndex: BigInt(1),
transactionIndex: BigInt(0),
transactionHash: '0xbe70733bcf87282c0ba9bf3c0e2d545084fad48bd571c314140c8dc1db882673',
blockHash: '0x78755c18c9a0a1283fa04b2f78c7794c249395b08f7f7dff304034d64d6a1607',
blockNumber: BigInt(25),
address: '0x2D029a4bd792d795f35e0583F64eD9DedeBBa849',
data: '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000',
topics: ['0x7d7846723bda52976e0286c6efffee937ee9f76817a867ec70531ad29fb1fc0e'],
returnValues: {
'0': 'Hello',
__length__: 1,
greeting: 'Hello',
},
event: 'GREETING_CHANGED',
signature: '0x7d7846723bda52976e0286c6efffee937ee9f76817a867ec70531ad29fb1fc0e',
raw: {
data: '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000',
topics: ['0x7d7846723bda52976e0286c6efffee937ee9f76817a867ec70531ad29fb1fc0e'],
},
},
],
};

export const AllGetPastEventsData = {
getLogsData: [
{
logIndex: 0,
transactionIndex: 0,
transactionHash: '0x1ba478ce1810bfa8a0725c0ca94f3cfe163a70c396037a1f3c94cad34e497959',
blockHash: '0x79eece1fb22b7109f302b65bd826b1cebf9f704642e86ae9086ed93baf44a45e',
blockNumber: 20,
address: '0x20bc23D0598b12c34cBDEf1fae439Ba8744DB426',
data: '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010416e6f74686572204772656574696e6700000000000000000000000000000000',
topics: ['0x0d363f2fba46ab11b6db8da0125b0d5484787c44e265b48810735998bab12b75'],
type: 'mined',
id: 'log_0a03b06c',
},
{
logIndex: 1,
transactionIndex: 0,
transactionHash: '0x1ba478ce1810bfa8a0725c0ca94f3cfe163a70c396037a1f3c94cad34e497959',
blockHash: '0x79eece1fb22b7109f302b65bd826b1cebf9f704642e86ae9086ed93baf44a45e',
blockNumber: 20,
address: '0x20bc23D0598b12c34cBDEf1fae439Ba8744DB426',
data: '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010416e6f74686572204772656574696e6700000000000000000000000000000000',
topics: ['0x7d7846723bda52976e0286c6efffee937ee9f76817a867ec70531ad29fb1fc0e'],
type: 'mined',
id: 'log_389ae161',
},
],

response: [
{
logIndex: BigInt(0),
transactionIndex: BigInt(0),
transactionHash: '0x1ba478ce1810bfa8a0725c0ca94f3cfe163a70c396037a1f3c94cad34e497959',
blockHash: '0x79eece1fb22b7109f302b65bd826b1cebf9f704642e86ae9086ed93baf44a45e',
blockNumber: BigInt(20),
address: '0x20bc23D0598b12c34cBDEf1fae439Ba8744DB426',
data: '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010416e6f74686572204772656574696e6700000000000000000000000000000000',
topics: ['0x0d363f2fba46ab11b6db8da0125b0d5484787c44e265b48810735998bab12b75'],
returnValues: {
'0': 'Hello',
'1': 'Another Greeting',
__length__: 2,
from: 'Hello',
to: 'Another Greeting',
},
event: 'GREETING_CHANGING',
signature: '0x0d363f2fba46ab11b6db8da0125b0d5484787c44e265b48810735998bab12b75',
raw: {
data: '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010416e6f74686572204772656574696e6700000000000000000000000000000000',
topics: ['0x0d363f2fba46ab11b6db8da0125b0d5484787c44e265b48810735998bab12b75'],
},
},
{
logIndex: BigInt(1),
transactionIndex: BigInt(0),
transactionHash: '0x1ba478ce1810bfa8a0725c0ca94f3cfe163a70c396037a1f3c94cad34e497959',
blockHash: '0x79eece1fb22b7109f302b65bd826b1cebf9f704642e86ae9086ed93baf44a45e',
blockNumber: BigInt(20),
address: '0x20bc23D0598b12c34cBDEf1fae439Ba8744DB426',
data: '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010416e6f74686572204772656574696e6700000000000000000000000000000000',
topics: ['0x7d7846723bda52976e0286c6efffee937ee9f76817a867ec70531ad29fb1fc0e'],
returnValues: {
'0': 'Another Greeting',
__length__: 1,
greeting: 'Another Greeting',
},
event: 'GREETING_CHANGED',
signature: '0x7d7846723bda52976e0286c6efffee937ee9f76817a867ec70531ad29fb1fc0e',
raw: {
data: '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010416e6f74686572204772656574696e6700000000000000000000000000000000',
topics: ['0x7d7846723bda52976e0286c6efffee937ee9f76817a867ec70531ad29fb1fc0e'],
},
},
],
};
Loading