Skip to content

Delay EthQuery instantiation until network provider is available #274

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions src/SmartTransactionsController.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { convertHexToDecimal } from '@metamask/controller-utils';
import { NetworkType, convertHexToDecimal } from '@metamask/controller-utils';
import type { NetworkState } from '@metamask/network-controller';
import { NetworkStatus } from '@metamask/network-controller';
import nock from 'nock';
import * as sinon from 'sinon';

Expand All @@ -9,7 +10,7 @@ import { API_BASE_URL, CHAIN_IDS } from './constants';
import SmartTransactionsController, {
DEFAULT_INTERVAL,
} from './SmartTransactionsController';
import { flushPromises, advanceTime } from './test-helpers';
import { advanceTime, flushPromises } from './test-helpers';
import type { SmartTransaction, UnsignedTransaction } from './types';
import { SmartTransactionStatuses } from './types';
import * as utils from './utils';
Expand Down Expand Up @@ -308,12 +309,47 @@ const defaultState = {
},
};

const mockProvider = {
sendAsync: jest.fn(),
};

const mockProviderConfig = {
chainId: '0x1' as `0x${string}`,
provider: mockProvider,
type: NetworkType.mainnet,
ticker: 'ticker',
};

const mockNetworkState = {
providerConfig: mockProviderConfig,
selectedNetworkClientId: 'id',
networkConfigurations: {
id: {
id: 'id',
rpcUrl: 'string',
chainId: '0x1' as `0x${string}`,
ticker: 'string',
},
},
networksMetadata: {
id: {
EIPS: {
1155: true,
},
status: NetworkStatus.Available,
},
},
};

describe('SmartTransactionsController', () => {
let smartTransactionsController: SmartTransactionsController;
let networkListener: (networkState: NetworkState) => void;

beforeEach(() => {
smartTransactionsController = new SmartTransactionsController({
onNetworkStateChange: (listener) => {
onNetworkStateChange: (
listener: (networkState: NetworkState) => void,
) => {
networkListener = listener;
},
getNonceLock: jest.fn(() => {
Expand Down Expand Up @@ -346,6 +382,8 @@ describe('SmartTransactionsController', () => {
});
// eslint-disable-next-line jest/prefer-spy-on
smartTransactionsController.subscribe = jest.fn();

networkListener(mockNetworkState);
});

afterEach(async () => {
Expand Down
59 changes: 37 additions & 22 deletions src/SmartTransactionsController.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
// eslint-disable-next-line import/no-nodejs-modules
import { hexlify } from '@ethersproject/bytes';
import type { BaseConfig, BaseState } from '@metamask/base-controller';
import { safelyExecute, query } from '@metamask/controller-utils';
import { query, safelyExecute } from '@metamask/controller-utils';
import type { Provider } from '@metamask/eth-query';
import EthQuery from '@metamask/eth-query';
import type {
NetworkState,
NetworkController,
NetworkClientId,
NetworkController,
NetworkState,
} from '@metamask/network-controller';
import { StaticIntervalPollingControllerV1 } from '@metamask/polling-controller';
import { BigNumber } from 'bignumber.js';
// eslint-disable-next-line import/no-nodejs-modules
import { EventEmitter } from 'events';
import EventEmitter from 'events';
import cloneDeep from 'lodash/cloneDeep';

import {
Expand All @@ -21,31 +21,33 @@ import {
MetaMetricsEventName,
} from './constants';
import type {
SmartTransaction,
SignedTransaction,
SignedCanceledTransaction,
UnsignedTransaction,
SmartTransactionsStatus,
Fees,
IndividualTxFees,
Hex,
IndividualTxFees,
SignedCanceledTransaction,
SignedTransaction,
SmartTransaction,
SmartTransactionsStatus,
UnsignedTransaction,
} from './types';
import { APIType, SmartTransactionStatuses } from './types';
import {
getAPIRequestURL,
isSmartTransactionPending,
calculateStatus,
snapshotFromTxMeta,
replayHistory,
generateHistoryEntry,
getAPIRequestURL,
getStxProcessingTime,
handleFetch,
isSmartTransactionCancellable,
incrementNonceInHex,
isSmartTransactionCancellable,
isSmartTransactionPending,
replayHistory,
snapshotFromTxMeta,
} from './utils';

const SECOND = 1000;
export const DEFAULT_INTERVAL = SECOND * 5;
const ETH_QUERY_ERROR_MSG =
'`ethQuery` is not defined on SmartTransactionsController';

export type SmartTransactionsControllerConfig = BaseConfig & {
interval: number;
Expand Down Expand Up @@ -84,7 +86,7 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo

private readonly getNonceLock: any;

private ethQuery: EthQuery;
private ethQuery: EthQuery | undefined;

public confirmExternalTransaction: any;

Expand Down Expand Up @@ -168,7 +170,7 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
this.initialize();
this.setIntervalLength(this.config.interval);
this.getNonceLock = getNonceLock;
this.ethQuery = new EthQuery(provider);
this.ethQuery = undefined;
this.confirmExternalTransaction = confirmExternalTransaction;
this.trackMetaMetricsEvent = trackMetaMetricsEvent;
this.getNetworkClientById = getNetworkClientById;
Expand Down Expand Up @@ -333,7 +335,7 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
ethQuery = this.ethQuery,
}: {
chainId: Hex;
ethQuery: EthQuery;
ethQuery: EthQuery | undefined;
},
): void {
const { smartTransactionsState } = this.state;
Expand All @@ -345,6 +347,9 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
const isNewSmartTransaction = this.isNewSmartTransaction(
smartTransaction.uuid,
);
if (this.ethQuery === undefined) {
throw new Error(ETH_QUERY_ERROR_MSG);
}

this.trackStxStatusChange(
smartTransaction,
Expand Down Expand Up @@ -445,12 +450,16 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
ethQuery = this.ethQuery,
}: {
chainId: Hex;
ethQuery: EthQuery;
ethQuery: EthQuery | undefined;
},
) {
if (smartTransaction.skipConfirm) {
return;
}

if (ethQuery === undefined) {
throw new Error(ETH_QUERY_ERROR_MSG);
}
const txHash = smartTransaction.statusMetadata?.minedHash;
try {
const transactionReceipt: {
Expand Down Expand Up @@ -744,9 +753,15 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
}: {
networkClientId?: NetworkClientId;
} = {}): EthQuery {
return networkClientId
? new EthQuery(this.getNetworkClientById(networkClientId).provider)
: this.ethQuery;
if (networkClientId) {
return new EthQuery(this.getNetworkClientById(networkClientId).provider);
}

if (this.ethQuery === undefined) {
throw new Error(ETH_QUERY_ERROR_MSG);
}

return this.ethQuery;
}

// TODO: This should return if the cancellation was on chain or not (for nonce management)
Expand Down