Skip to content

Commit

Permalink
Emit an event after a tx is confirmed, remove "skipConfirm", return "…
Browse files Browse the repository at this point in the history
…txHash" (#298)
  • Loading branch information
dan437 authored Mar 19, 2024
1 parent 55b22e8 commit aed85b9
Show file tree
Hide file tree
Showing 7 changed files with 603 additions and 91 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
"test:watch": "jest --watchAll"
},
"dependencies": {
"@ethereumjs/tx": "^5.2.1",
"@ethereumjs/util": "^9.0.2",
"@ethersproject/bytes": "^5.7.0",
"@metamask/base-controller": "^4.0.0",
"@metamask/controller-utils": "^8.0.2",
Expand Down
21 changes: 6 additions & 15 deletions src/SmartTransactionsController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,11 @@ const createSubmitTransactionsApiResponse = () => {
return { uuid: 'dP23W7c2kt4FK9TmXOkz1UM2F20' };
};

// TODO: How exactly a signed transaction should look like?
const createSignedTransaction = () => {
return '0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a02b79f322a625d623a2bb2911e0c6b3e7eaf741a7c7c5d2e8c67ef3ff4acf146ca01ae168fea63dc3391b75b586c8a7c0cb55cdf3b8e2e4d8e097957a3a56c6f2c5';
};

const createTxParams = () => {
return {
from: '0x268392a24B6b093127E8581eAfbD1DA228bAdAe3',
to: '0x0000000000000000000000000000000000000000',
Expand All @@ -193,19 +196,8 @@ const createSignedTransaction = () => {
};
};

// TODO: How exactly a signed canceled transaction should look like?
const createSignedCanceledTransaction = () => {
return {
from: '0x268392a24B6b093127E8581eAfbD1DA228bAdAe3',
to: '0x0000000000000000000000000000000000000000',
value: 0,
data: '0x',
nonce: 0,
type: 2,
chainId: 4,
maxFeePerGas: 2100001000,
maxPriorityFeePerGas: 466503987,
};
return '0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a02b79f322a625d623a2bb2911e0c6b3e7eaf741a7c7c5d2e8c67ef3ff4acf146ca01ae168fea63dc3391b75b586c8a7c0cb55cdf3b8e2e4d8e097957a3a56c6f2c5';
};

const createPendingBatchStatusApiResponse = () => ({
Expand Down Expand Up @@ -655,7 +647,7 @@ describe('SmartTransactionsController', () => {
await smartTransactionsController.submitSignedTransactions({
signedTransactions: [signedTransaction],
signedCanceledTransactions: [signedCanceledTransaction],
txParams: signedTransaction,
txParams: createTxParams(),
});

expect(
Expand Down Expand Up @@ -872,7 +864,6 @@ describe('SmartTransactionsController', () => {
);

await flushPromises();

expect(
smartTransactionsController.state.smartTransactionsState
.smartTransactions[CHAIN_IDS.ETHEREUM],
Expand Down
73 changes: 38 additions & 35 deletions src/SmartTransactionsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
isSmartTransactionPending,
replayHistory,
snapshotFromTxMeta,
getTxHash,
} from './utils';

const SECOND = 1000;
Expand Down Expand Up @@ -92,7 +93,7 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo

private readonly trackMetaMetricsEvent: any;

private readonly eventEmitter: EventEmitter;
public eventEmitter: EventEmitter;

private readonly getNetworkClientById: NetworkController['getNetworkClientById'];

Expand Down Expand Up @@ -328,7 +329,7 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
});
}

#updateSmartTransaction(
async #updateSmartTransaction(
smartTransaction: SmartTransaction,
{
chainId = this.config.chainId,
Expand All @@ -337,7 +338,7 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
chainId: Hex;
ethQuery: EthQuery | undefined;
},
): void {
): Promise<void> {
const { smartTransactionsState } = this.state;
const { smartTransactions } = smartTransactionsState;
const currentSmartTransactions = smartTransactions[chainId] ?? [];
Expand Down Expand Up @@ -398,29 +399,32 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
...currentSmartTransaction,
...smartTransaction,
};
if (!smartTransaction.skipConfirm) {
this.#confirmSmartTransaction(nextSmartTransaction, {
chainId,
ethQuery,
});
}
await this.#confirmSmartTransaction(nextSmartTransaction, {
chainId,
ethQuery,
});
} else {
this.update({
smartTransactionsState: {
...smartTransactionsState,
smartTransactions: {
...smartTransactionsState.smartTransactions,
[chainId]: smartTransactionsState.smartTransactions[chainId].map(
(item, index) => {
return index === currentIndex
? { ...item, ...smartTransaction }
: item;
},
),
},
},
});
}

this.update({
smartTransactionsState: {
...smartTransactionsState,
smartTransactions: {
...smartTransactionsState.smartTransactions,
[chainId]: smartTransactionsState.smartTransactions[chainId].map(
(item, index) => {
return index === currentIndex
? { ...item, ...smartTransaction }
: item;
},
),
},
},
});
this.eventEmitter.emit(
`${smartTransaction.uuid}:smartTransaction`,
smartTransaction,
);
}

async updateSmartTransactions({
Expand Down Expand Up @@ -453,10 +457,6 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
ethQuery: EthQuery | undefined;
},
) {
if (smartTransaction.skipConfirm) {
return;
}

if (ethQuery === undefined) {
throw new Error(ETH_QUERY_ERROR_MSG);
}
Expand All @@ -467,7 +467,6 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
maxPriorityFeePerGas?: string;
blockNumber: string;
} | null = await query(ethQuery, 'getTransactionReceipt', [txHash]);

const transaction: {
maxFeePerGas?: string;
maxPriorityFeePerGas?: string;
Expand Down Expand Up @@ -568,7 +567,6 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
cancellable: isSmartTransactionCancellable(stxStatus),
uuid,
};
this.eventEmitter.emit(`${uuid}:smartTransaction`, smartTransaction);
this.#updateSmartTransaction(smartTransaction, { chainId, ethQuery });

Check warning on line 570 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

Check warning on line 570 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
});

Expand Down Expand Up @@ -669,17 +667,17 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
// * After this successful call client must add a nonce representative to
// * transaction controller external transactions list
async submitSignedTransactions({
transactionMeta,
txParams,
signedTransactions,
signedCanceledTransactions,
networkClientId,
skipConfirm,
}: {
signedTransactions: SignedTransaction[];
signedCanceledTransactions: SignedCanceledTransaction[];
transactionMeta?: any;
txParams?: any;
networkClientId?: NetworkClientId;
skipConfirm?: boolean;
}) {
const chainId = this.#getChainId({ networkClientId });
const ethQuery = this.#getEthQuery({ networkClientId });
Expand Down Expand Up @@ -717,6 +715,10 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
txParams.nonce ??= nonce;
}
}
const submitTransactionResponse = {
...data,
txHash: getTxHash(signedTransactions[0]),
};

try {
this.#updateSmartTransaction(
Expand All @@ -727,17 +729,18 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
status: SmartTransactionStatuses.PENDING,
time,
txParams,
uuid: data.uuid,
uuid: submitTransactionResponse.uuid,
txHash: submitTransactionResponse.txHash,
cancellable: true,
skipConfirm: skipConfirm ?? false,
type: transactionMeta?.type || 'swap',
},
{ chainId, ethQuery },
);
} finally {
nonceLock?.releaseLock();
}

return data;
return submitTransactionResponse;
}

#getChainId({
Expand Down
5 changes: 3 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export type SmartTransactionsStatus = {

export type SmartTransaction = {
uuid: string;
txHash?: string;
chainId?: string;
destinationTokenAddress?: string;
destinationTokenDecimals?: string;
Expand All @@ -84,12 +85,12 @@ export type SmartTransaction = {
sourceTokenSymbol?: string;
swapMetaData?: any;
swapTokenValue?: string;
time?: number;
time?: number; // @deprecated We should use creationTime instead.
creationTime?: number;
txParams?: any;
type?: string;
confirmed?: boolean;
cancellable?: boolean;
skipConfirm?: boolean;
};

export type Fee = {
Expand Down
27 changes: 27 additions & 0 deletions src/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,17 @@ import {
import * as utils from './utils';
import packageJson from '../package.json';

const createSignedTransaction = () => {
return '0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a02b79f322a625d623a2bb2911e0c6b3e7eaf741a7c7c5d2e8c67ef3ff4acf146ca01ae168fea63dc3391b75b586c8a7c0cb55cdf3b8e2e4d8e097957a3a56c6f2c5';
};

describe('src/utils.js', () => {
describe('isSmartTransactionPending', () => {
const createSmartTransaction = () => {
return {
uuid: 'sdfasfj345345dfgag45353',
txHash:
'0x3c3e7c5e09c250d2200bcc3530f4a9088d7e3fb4ea3f4fccfd09f535a3539e84',
status: 'pending',
statusMetadata: {
error: undefined,
Expand Down Expand Up @@ -274,4 +280,25 @@ describe('src/utils.js', () => {
);
});
});

describe('getTxHash', () => {
it('returns a transaction hash from a signed transaction', () => {
const expectedTxHash =
'0x0302b75dfb9fd9eb34056af031efcaee2a8cbd799ea054a85966165cd82a7356';
const txHash = utils.getTxHash(createSignedTransaction());
expect(txHash).toBe(expectedTxHash);
});

it('returns an empty string if there is no signed transaction', () => {
const expectedTxHash = '';
const txHash = utils.getTxHash('');
expect(txHash).toBe(expectedTxHash);
});

it('throws an error with an incorrect signed transaction', () => {
expect(() => {
utils.getTxHash('0x0302b75dfb9fd9eb34056af0');
}).toThrow('kzg instance required to instantiate blob tx');
});
});
});
13 changes: 13 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { TransactionFactory } from '@ethereumjs/tx';
import { bytesToHex } from '@ethereumjs/util';
import { hexlify } from '@ethersproject/bytes';
import { BigNumber } from 'bignumber.js';
import jsonDiffer from 'fast-json-patch';
Expand Down Expand Up @@ -214,3 +216,14 @@ export const incrementNonceInHex = (nonceInHex: string): string => {
const nonceInDec = new BigNumber(nonceInHex, 16).toString(10);
return hexlify(Number(nonceInDec) + 1);
};

export const getTxHash = (signedTxHex: any) => {
if (!signedTxHex) {
return '';
}
const txHashBytes = TransactionFactory.fromSerializedData(
// eslint-disable-next-line no-restricted-globals
Buffer.from(signedTxHex.slice(2), 'hex'),
).hash();
return bytesToHex(txHashBytes);
};
Loading

0 comments on commit aed85b9

Please sign in to comment.