Skip to content
This repository was archived by the owner on Mar 5, 2025. It is now read-only.

Commit ed85cce

Browse files
fix: ensure contractInstance.subscriptionManager.subscribe is not throwing (#7327)
* rename `LogsSubscription` at `web3-eth-contract` to `ContractLogsSubscription` * fix issue that `contractInstance.subscriptionManager.subscribe` was throwing * add a unit test
1 parent fab66e9 commit ed85cce

File tree

8 files changed

+153
-22
lines changed

8 files changed

+153
-22
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
This file is part of web3.js.
3+
4+
web3.js is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU Lesser General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
web3.js is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU Lesser General Public License for more details.
13+
14+
You should have received a copy of the GNU Lesser General Public License
15+
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
import { Web3SubscriptionConstructor, Web3SubscriptionManager } from 'web3-core';
19+
import { EthExecutionAPI, ContractAbi, DataFormat, DEFAULT_RETURN_FORMAT } from 'web3-types';
20+
// eslint-disable-next-line import/no-cycle
21+
import { Contract } from './contract.js';
22+
23+
/**
24+
* Similar to `Web3SubscriptionManager` but has a reference to the Contract that uses
25+
*/
26+
export class ContractSubscriptionManager<
27+
API extends EthExecutionAPI,
28+
RegisteredSubs extends {
29+
[key: string]: Web3SubscriptionConstructor<API>;
30+
} = any, // = ContractSubscriptions
31+
> extends Web3SubscriptionManager<API, RegisteredSubs> {
32+
public readonly parentContract: Contract<ContractAbi>;
33+
34+
/**
35+
*
36+
* @param - Web3SubscriptionManager
37+
* @param - parentContract
38+
*
39+
* @example
40+
* ```ts
41+
* const requestManager = new Web3RequestManager("ws://localhost:8545");
42+
* const contract = new Contract(...)
43+
* const subscriptionManager = new Web3SubscriptionManager(requestManager, {}, contract);
44+
* ```
45+
*/
46+
public constructor(
47+
self: Web3SubscriptionManager<API, RegisteredSubs>,
48+
parentContract: Contract<ContractAbi>,
49+
) {
50+
super(self.requestManager, self.registeredSubscriptions);
51+
52+
this.parentContract = parentContract;
53+
}
54+
55+
/**
56+
* Will create a new subscription
57+
*
58+
* @param name - The subscription you want to subscribe to
59+
* @param args - Optional additional parameters, depending on the subscription type
60+
* @param returnFormat- ({@link DataFormat} defaults to {@link DEFAULT_RETURN_FORMAT}) - Specifies how the return data from the call should be formatted.
61+
*
62+
* Will subscribe to a specific topic (note: name)
63+
* @returns The subscription object
64+
*/
65+
public async subscribe<T extends keyof RegisteredSubs>(
66+
name: T,
67+
args?: ConstructorParameters<RegisteredSubs[T]>[0],
68+
returnFormat: DataFormat = DEFAULT_RETURN_FORMAT,
69+
): Promise<InstanceType<RegisteredSubs[T]>> {
70+
return super.subscribe(name, args ?? this.parentContract.options, returnFormat);
71+
}
72+
}

packages/web3-eth-contract/src/contract.ts

+21-6
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ import {
105105
encodeEventABI,
106106
encodeMethodABI,
107107
} from './encoding.js';
108-
import { LogsSubscription } from './log_subscription.js';
108+
import { ContractLogsSubscription } from './contract_log_subscription.js';
109109
import {
110110
ContractEventOptions,
111111
NonPayableMethodObject,
@@ -123,6 +123,8 @@ import {
123123
} from './utils.js';
124124
// eslint-disable-next-line import/no-cycle
125125
import { DeployerMethodClass } from './contract-deployer-method-class.js';
126+
// eslint-disable-next-line import/no-cycle
127+
import { ContractSubscriptionManager } from './contract-subscription-manager.js';
126128

127129
type ContractBoundMethod<
128130
Abi extends AbiFunctionFragment,
@@ -179,9 +181,9 @@ export type ContractMethodSend = Web3PromiEvent<
179181
* ```
180182
*
181183
* @param options - The options used to subscribe for the event
182-
* @returns - A Promise resolved with {@link LogsSubscription} object
184+
* @returns - A Promise resolved with {@link ContractLogsSubscription} object
183185
*/
184-
export type ContractBoundEvent = (options?: ContractEventOptions) => LogsSubscription;
186+
export type ContractBoundEvent = (options?: ContractEventOptions) => ContractLogsSubscription;
185187

186188
// To avoid circular dependency between types and encoding, declared these types here.
187189
export type ContractEventsInterface<
@@ -204,11 +206,13 @@ export type ContractEventEmitterInterface<Abi extends ContractAbi> = {
204206
type EventParameters = Parameters<typeof encodeEventABI>[2];
205207

206208
const contractSubscriptions = {
207-
logs: LogsSubscription,
209+
logs: ContractLogsSubscription,
208210
newHeads: NewHeadsSubscription,
209211
newBlockHeaders: NewHeadsSubscription,
210212
};
211213

214+
type ContractSubscriptions = typeof contractSubscriptions;
215+
212216
/**
213217
* The `web3.eth.Contract` makes it easy to interact with smart contracts on the ethereum blockchain.
214218
* For using contract package, first install Web3 package using: `npm i web3` or `yarn add web3` based on your package manager, after that contracts features can be used as mentioned in following snippet.
@@ -406,9 +410,15 @@ const contractSubscriptions = {
406410
*
407411
*/
408412
export class Contract<Abi extends ContractAbi>
409-
extends Web3Context<EthExecutionAPI, typeof contractSubscriptions>
413+
extends Web3Context<EthExecutionAPI, ContractSubscriptions>
410414
implements Web3EventEmitter<ContractEventEmitterInterface<Abi>>
411415
{
416+
protected override _subscriptionManager: ContractSubscriptionManager<EthExecutionAPI>;
417+
418+
public override get subscriptionManager(): ContractSubscriptionManager<EthExecutionAPI> {
419+
return this._subscriptionManager;
420+
}
421+
412422
/**
413423
* The options `object` for the contract instance. `from`, `gas` and `gasPrice` are used as fallback values when sending transactions.
414424
*
@@ -564,6 +574,11 @@ export class Contract<Abi extends ContractAbi>
564574
registeredSubscriptions: contractSubscriptions,
565575
});
566576

577+
this._subscriptionManager = new ContractSubscriptionManager<
578+
EthExecutionAPI,
579+
ContractSubscriptions
580+
>(super.subscriptionManager, this);
581+
567582
// Init protected properties
568583
if ((contractContext as Web3Context)?.wallet) {
569584
this._wallet = (contractContext as Web3Context).wallet;
@@ -1414,7 +1429,7 @@ export class Contract<Abi extends ContractAbi>
14141429
abi,
14151430
params[0] as EventParameters,
14161431
);
1417-
const sub = new LogsSubscription(
1432+
const sub = new ContractLogsSubscription(
14181433
{
14191434
address: this.options.address,
14201435
topics,

packages/web3-eth-contract/src/log_subscription.ts packages/web3-eth-contract/src/contract_log_subscription.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ import { Web3RequestManager, Web3Subscription, Web3SubscriptionManager } from 'w
2828
import { decodeEventABI } from 'web3-eth';
2929

3030
/**
31-
* LogSubscription to be used to subscribe to events logs.
31+
* ContractLogsSubscription to be used to subscribe to events logs.
3232
*
33-
* Following events are supported and can be accessed with either {@link LogsSubscription.once} or ${@link LogsSubscription.on} methods.
33+
* Following events are supported and can be accessed with either {@link ContractLogsSubscription.once} or ${@link ContractLogsSubscription.on} methods.
3434
*
3535
* - **connected**: Emitted when the subscription is connected.
3636
* - **data**: Fires on each incoming event with the event object as argument.
@@ -81,7 +81,7 @@ import { decodeEventABI } from 'web3-eth';
8181
* }
8282
* ```
8383
*/
84-
export class LogsSubscription extends Web3Subscription<
84+
export class ContractLogsSubscription extends Web3Subscription<
8585
{
8686
data: EventLog;
8787
changed: EventLog & { removed: true };
@@ -162,3 +162,7 @@ export class LogsSubscription extends Web3Subscription<
162162
return decodeEventABI(this.abi, data as LogsInput, this.jsonInterface, super.returnFormat);
163163
}
164164
}
165+
/**
166+
* @deprecated LogsSubscription is renamed to ContractLogsSubscription in this package to not be confused with LogsSubscription at 'web3-eth'.
167+
*/
168+
export const LogsSubscription = ContractLogsSubscription;

packages/web3-eth-contract/src/index.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,15 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
4242
*/
4343
import { Contract } from './contract.js';
4444

45+
import { ContractLogsSubscription } from './contract_log_subscription.js';
46+
/** @deprecated Use `ContractLogsSubscription` instead. */
47+
export type LogsSubscription = ContractLogsSubscription;
48+
4549
export * from './encoding.js';
4650

4751
export * from './contract.js';
4852
export * from './contract-deployer-method-class.js';
49-
export * from './log_subscription.js';
53+
export * from './contract_log_subscription.js';
5054
export * from './types.js';
5155
export * from './utils.js';
5256

packages/web3-eth-contract/src/types.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {
3232
} from 'web3-types';
3333
import { NewHeadsSubscription, SendTransactionEvents } from 'web3-eth';
3434
import type { ContractOptions } from 'web3-types';
35-
import { LogsSubscription } from './log_subscription.js';
35+
import { ContractLogsSubscription } from './contract_log_subscription.js';
3636

3737
export type NonPayableTxOptions = NonPayableCallOptions;
3838
export type PayableTxOptions = PayableCallOptions;
@@ -462,7 +462,7 @@ export type Web3ContractContext = Partial<
462462
Web3ContextInitOptions<
463463
EthExecutionAPI,
464464
{
465-
logs: typeof LogsSubscription;
465+
logs: typeof ContractLogsSubscription;
466466
newHeads: typeof NewHeadsSubscription;
467467
newBlockHeaders: typeof NewHeadsSubscription;
468468
}

packages/web3-eth-contract/test/fixtures/erc20.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
1616
*/
1717

1818
import { Address, Numbers } from 'web3-types';
19-
import { LogsSubscription } from '../../src/log_subscription';
19+
import { ContractLogsSubscription } from '../../src/contract_log_subscription';
2020
import { ContractEventOptions, PayableMethodObject, NonPayableMethodObject } from '../../src/types';
2121

2222
export interface Erc20Interface {
@@ -53,9 +53,9 @@ export interface Erc20Interface {
5353
};
5454

5555
events: {
56-
[key: string]: (options?: ContractEventOptions) => LogsSubscription;
57-
Approval: (options?: ContractEventOptions) => LogsSubscription;
58-
Transfer: (options?: ContractEventOptions) => LogsSubscription;
56+
[key: string]: (options?: ContractEventOptions) => ContractLogsSubscription;
57+
Approval: (options?: ContractEventOptions) => ContractLogsSubscription;
58+
Transfer: (options?: ContractEventOptions) => ContractLogsSubscription;
5959
};
6060
}
6161

packages/web3-eth-contract/test/fixtures/erc721.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
1616
*/
1717

1818
import { Address, Numbers } from 'web3-types';
19-
import { LogsSubscription } from '../../src/log_subscription';
19+
import { ContractLogsSubscription } from '../../src/contract_log_subscription';
2020
import { ContractEventOptions, NonPayableMethodObject, PayableMethodObject } from '../../src/types';
2121

2222
export interface Erc721Interface {
@@ -55,10 +55,10 @@ export interface Erc721Interface {
5555
};
5656

5757
events: {
58-
[key: string]: (options?: ContractEventOptions) => LogsSubscription;
59-
Transfer: (options?: ContractEventOptions) => LogsSubscription;
60-
Approval: (options?: ContractEventOptions) => LogsSubscription;
61-
ApprovalForAll: (options?: ContractEventOptions) => LogsSubscription;
58+
[key: string]: (options?: ContractEventOptions) => ContractLogsSubscription;
59+
Transfer: (options?: ContractEventOptions) => ContractLogsSubscription;
60+
Approval: (options?: ContractEventOptions) => ContractLogsSubscription;
61+
ApprovalForAll: (options?: ContractEventOptions) => ContractLogsSubscription;
6262
};
6363
}
6464

packages/web3-eth-contract/test/unit/log_subscription.test.ts

+37-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ along with web3.js. If not, see <http://www.gnu.org/licenses/>.
1717

1818
import * as eth from 'web3-eth';
1919
import { WebSocketProvider } from 'web3-providers-ws';
20-
import { Contract } from '../../src';
20+
import { Web3SubscriptionManager } from 'web3-core';
21+
import { Contract, ContractLogsSubscription } from '../../src';
2122
import { GreeterAbi, GreeterBytecode } from '../shared_fixtures/build/Greeter';
2223

2324
jest.mock('web3-eth');
@@ -76,4 +77,39 @@ describe('contract log subscription', () => {
7677
],
7778
});
7879
});
80+
81+
it('should be able to subscribe to logs with contractInstance.subscriptionManager.subscribe', async () => {
82+
const address = '0x407D73d8a49eeb85D32Cf465507dd71d507100c1';
83+
const contractInstance = new Contract(GreeterAbi, address);
84+
85+
jest.spyOn(WebSocketProvider.prototype, 'request').mockImplementation(
86+
async (payload: any) => {
87+
return {
88+
jsonrpc: '2.0',
89+
id: payload.id,
90+
result: {},
91+
};
92+
},
93+
);
94+
95+
jest.spyOn(Web3SubscriptionManager.prototype, 'subscribe').mockImplementation(
96+
async (name: string | number | symbol, args?: any) => {
97+
expect(name).toBe('logs');
98+
expect(args.address).toBe(address);
99+
100+
return new ContractLogsSubscription(args, {
101+
subscriptionManager: contractInstance.subscriptionManager,
102+
});
103+
},
104+
);
105+
106+
contract.setProvider(providerString);
107+
108+
const sub = contractInstance.subscriptionManager.subscribe('logs');
109+
expect(await sub).toBeInstanceOf(ContractLogsSubscription);
110+
111+
contractInstance.subscriptionManager.clear();
112+
113+
contractInstance.provider?.disconnect();
114+
});
79115
});

0 commit comments

Comments
 (0)