Skip to content
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
151 changes: 150 additions & 1 deletion src/SmartTransactionsController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@

import packageJson from '../package.json';
import { advanceTime, flushPromises, getFakeProvider } from '../tests/helpers';
import { API_BASE_URL, SENTINEL_API_BASE_URL_MAP } from './constants';
import {
API_BASE_URL,
SENTINEL_API_BASE_URL_MAP,
SmartTransactionsTraceName,
} from './constants';
import SmartTransactionsController, {
DEFAULT_INTERVAL,
getDefaultSmartTransactionsControllerState,
Expand Down Expand Up @@ -447,7 +451,7 @@

controller.timeoutHandle = setTimeout(() => ({}));

controller.poll(1000);

Check warning on line 454 in src/SmartTransactionsController.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (20.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator

Check warning on line 454 in src/SmartTransactionsController.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (18.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator

expect(updateSmartTransactionsSpy).toHaveBeenCalled();
});
Expand Down Expand Up @@ -2387,6 +2391,151 @@
);
});
});

describe('Tracing', () => {
const createTraceCallback = () =>
jest.fn().mockImplementation(async (_request, fn) => {
return fn?.();
});

it('traces getFees API call with expected name', async () => {
const traceCallback = createTraceCallback();

await withController(
{
options: {
trace: traceCallback,
},
},
async ({ controller }) => {
const apiUrl = API_BASE_URL;
nock(apiUrl)
.post(`/networks/${ethereumChainIdDec}/getFees`)
.reply(200, createGetFeesApiResponse());

const tradeTx = createUnsignedTransaction(ethereumChainIdDec);
await controller.getFees(tradeTx);

expect(traceCallback).toHaveBeenCalledWith(
{ name: SmartTransactionsTraceName.GetFees },
expect.any(Function),
);
},
);
});

it('traces submitSignedTransactions API call with expected name', async () => {
const traceCallback = createTraceCallback();

await withController(
{
options: {
trace: traceCallback,
},
},
async ({ controller }) => {
const apiUrl = API_BASE_URL;
nock(apiUrl)
.post(
`/networks/${ethereumChainIdDec}/submitTransactions?stxControllerVersion=${packageJson.version}`,
)
.reply(200, createSubmitTransactionsApiResponse());

const signedTx = createSignedTransaction();
const signedCanceledTx = createSignedCanceledTransaction();
const txParams = createTxParams();

await controller.submitSignedTransactions({
signedTransactions: [signedTx],
signedCanceledTransactions: [signedCanceledTx],
txParams,
});

expect(traceCallback).toHaveBeenCalledWith(
{ name: SmartTransactionsTraceName.SubmitTransactions },
expect.any(Function),
);
},
);
});

it('traces cancelSmartTransaction API call with expected name', async () => {
const traceCallback = createTraceCallback();

await withController(
{
options: {
trace: traceCallback,
},
},
async ({ controller }) => {
const apiUrl = API_BASE_URL;
nock(apiUrl)
.post(`/networks/${ethereumChainIdDec}/cancel`)
.reply(200, {});

await controller.cancelSmartTransaction('uuid1');

expect(traceCallback).toHaveBeenCalledWith(
{ name: SmartTransactionsTraceName.CancelTransaction },
expect.any(Function),
);
},
);
});

it('traces fetchLiveness API call with expected name', async () => {
const traceCallback = createTraceCallback();

await withController(
{
options: {
trace: traceCallback,
},
},
async ({ controller }) => {
nock(SENTINEL_API_BASE_URL_MAP[ethereumChainIdDec])
.get(`/network`)
.reply(200, createSuccessLivenessApiResponse());

await controller.fetchLiveness();

expect(traceCallback).toHaveBeenCalledWith(
{ name: SmartTransactionsTraceName.FetchLiveness },
expect.any(Function),
);
},
);
});

it('returns correct result when tracing is enabled', async () => {
const traceCallback = createTraceCallback();

await withController(
{
options: {
trace: traceCallback,
},
},
async ({ controller }) => {
const apiUrl = API_BASE_URL;
const expectedResponse = createGetFeesApiResponse();
nock(apiUrl)
.post(`/networks/${ethereumChainIdDec}/getFees`)
.reply(200, expectedResponse);

const tradeTx = createUnsignedTransaction(ethereumChainIdDec);
const result = await controller.getFees(tradeTx);

expect(traceCallback).toHaveBeenCalled();
expect(result).toMatchObject({
tradeTxFees: expectedResponse.txs[0],
approvalTxFees: null,
});
},
);
});
});
});

type WithControllerCallback<ReturnValue> = ({
Expand Down Expand Up @@ -2559,7 +2708,7 @@
triggerNetworStateChange,
});
} finally {
controller.stop();

Check warning on line 2711 in src/SmartTransactionsController.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (20.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator

Check warning on line 2711 in src/SmartTransactionsController.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (18.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
controller.stopAllPolling();
}
}
69 changes: 45 additions & 24 deletions src/SmartTransactionsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
safelyExecute,
ChainId,
isSafeDynamicKey,
type TraceCallback,
} from '@metamask/controller-utils';
import EthQuery from '@metamask/eth-query';
import type {
Expand All @@ -27,7 +28,11 @@
import { BigNumber } from 'bignumber.js';
import cloneDeep from 'lodash/cloneDeep';

import { MetaMetricsEventCategory, MetaMetricsEventName } from './constants';
import {
MetaMetricsEventCategory,
MetaMetricsEventName,
SmartTransactionsTraceName,
} from './constants';
import type {
Fees,
Hex,
Expand Down Expand Up @@ -206,6 +211,7 @@
getMetaMetricsProps: () => Promise<MetaMetricsProps>;
getFeatureFlags: () => FeatureFlags;
updateTransaction: (transaction: TransactionMeta, note: string) => void;
trace?: TraceCallback;
};

export type SmartTransactionsControllerPollingInput = {
Expand Down Expand Up @@ -245,6 +251,8 @@

#updateTransaction: SmartTransactionsControllerOptions['updateTransaction'];

#trace: TraceCallback;

/* istanbul ignore next */
async #fetch(request: string, options?: RequestInit) {
const fetchOptions = {
Expand Down Expand Up @@ -272,6 +280,7 @@
getMetaMetricsProps,
getFeatureFlags,
updateTransaction,
trace,
}: SmartTransactionsControllerOptions) {
super({
name: controllerName,
Expand All @@ -295,6 +304,7 @@
this.#getMetaMetricsProps = getMetaMetricsProps;
this.#getFeatureFlags = getFeatureFlags;
this.#updateTransaction = updateTransaction;
this.#trace = trace ?? (((_request, fn) => fn?.()) as TraceCallback);

this.initializeSmartTransactionsForChainId();

Expand Down Expand Up @@ -348,9 +358,9 @@
isSmartTransactionPending,
);
if (!this.timeoutHandle && pendingTransactions?.length > 0) {
this.poll();

Check warning on line 361 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (20.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator

Check warning on line 361 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (18.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
} else if (this.timeoutHandle && pendingTransactions?.length === 0) {
this.stop();

Check warning on line 363 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (20.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator

Check warning on line 363 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (18.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
}
}

Expand All @@ -375,7 +385,7 @@
}

this.timeoutHandle = setInterval(() => {
safelyExecute(async () => this.updateSmartTransactions());

Check warning on line 388 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (20.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator

Check warning on line 388 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (18.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
}, this.#interval);
await safelyExecute(async () => this.updateSmartTransactions());
}
Expand Down Expand Up @@ -442,7 +452,7 @@
ethQuery = new EthQuery(provider);
}

this.#createOrUpdateSmartTransaction(smartTransaction, {

Check warning on line 455 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (20.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator

Check warning on line 455 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (18.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
chainId,
ethQuery,
});
Expand Down Expand Up @@ -729,7 +739,7 @@
: originalTxMeta;

if (this.#doesTransactionNeedConfirmation(txHash)) {
this.#confirmExternalTransaction(

Check warning on line 742 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (20.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator

Check warning on line 742 in src/SmartTransactionsController.ts

View workflow job for this annotation

GitHub Actions / Build, lint, and test / Lint (18.x)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
// TODO: Replace 'as' assertion with correct typing for `txMeta`
txMeta as TransactionMeta,
transactionReceipt,
Expand Down Expand Up @@ -860,7 +870,7 @@
const chainId = this.#getChainId({
networkClientId: selectedNetworkClientId,
});
const transactions = [];
const transactions: UnsignedTransaction[] = [];
let unsignedTradeTransactionWithNonce;
if (approvalTx) {
const unsignedApprovalTransactionWithNonce =
Expand All @@ -880,14 +890,15 @@
);
}
transactions.push(unsignedTradeTransactionWithNonce);
const data = await this.#fetch(
getAPIRequestURL(APIType.GET_FEES, chainId),
{
method: 'POST',
body: JSON.stringify({
txs: transactions,
const data = await this.#trace(
{ name: SmartTransactionsTraceName.GetFees },
async () =>
await this.#fetch(getAPIRequestURL(APIType.GET_FEES, chainId), {
method: 'POST',
body: JSON.stringify({
txs: transactions,
}),
}),
},
);
let approvalTxFees: IndividualTxFees | null;
let tradeTxFees: IndividualTxFees | null;
Expand Down Expand Up @@ -943,15 +954,19 @@
const ethQuery = this.#getEthQuery({
networkClientId: selectedNetworkClientId,
});
const data = await this.#fetch(
getAPIRequestURL(APIType.SUBMIT_TRANSACTIONS, chainId),
{
method: 'POST',
body: JSON.stringify({
rawTxs: signedTransactions,
rawCancelTxs: signedCanceledTransactions,
}),
},
const data = await this.#trace(
{ name: SmartTransactionsTraceName.SubmitTransactions },
async () =>
await this.#fetch(
getAPIRequestURL(APIType.SUBMIT_TRANSACTIONS, chainId),
{
method: 'POST',
body: JSON.stringify({
rawTxs: signedTransactions,
rawCancelTxs: signedCanceledTransactions,
}),
},
),
);
const time = Date.now();
let preTxBalance;
Expand Down Expand Up @@ -1089,10 +1104,14 @@
} = {},
): Promise<void> {
const chainId = this.#getChainId({ networkClientId });
await this.#fetch(getAPIRequestURL(APIType.CANCEL, chainId), {
method: 'POST',
body: JSON.stringify({ uuid }),
});
await this.#trace(
{ name: SmartTransactionsTraceName.CancelTransaction },
async () =>
await this.#fetch(getAPIRequestURL(APIType.CANCEL, chainId), {
method: 'POST',
body: JSON.stringify({ uuid }),
}),
);
}

async fetchLiveness({
Expand All @@ -1103,8 +1122,10 @@
const chainId = this.#getChainId({ networkClientId });
let liveness = false;
try {
const response = await this.#fetch(
getAPIRequestURL(APIType.LIVENESS, chainId),
const response = await this.#trace(
{ name: SmartTransactionsTraceName.FetchLiveness },
async () =>
await this.#fetch(getAPIRequestURL(APIType.LIVENESS, chainId)),
);
liveness = Boolean(response.smartTransactions);
} catch (error) {
Expand Down
7 changes: 7 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,10 @@ export enum MetaMetricsEventCategory {
Transactions = 'Transactions',
Navigation = 'Navigation',
}

export enum SmartTransactionsTraceName {
GetFees = 'Smart Transactions: Get Fees',
SubmitTransactions = 'Smart Transactions: Submit Transactions',
CancelTransaction = 'Smart Transactions: Cancel Transaction',
FetchLiveness = 'Smart Transactions: Fetch Liveness',
}
Loading