Skip to content

Commit aed85b9

Browse files
authored
Emit an event after a tx is confirmed, remove "skipConfirm", return "txHash" (#298)
1 parent 55b22e8 commit aed85b9

7 files changed

+603
-91
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
"test:watch": "jest --watchAll"
2626
},
2727
"dependencies": {
28+
"@ethereumjs/tx": "^5.2.1",
29+
"@ethereumjs/util": "^9.0.2",
2830
"@ethersproject/bytes": "^5.7.0",
2931
"@metamask/base-controller": "^4.0.0",
3032
"@metamask/controller-utils": "^8.0.2",

src/SmartTransactionsController.test.ts

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,11 @@ const createSubmitTransactionsApiResponse = () => {
178178
return { uuid: 'dP23W7c2kt4FK9TmXOkz1UM2F20' };
179179
};
180180

181-
// TODO: How exactly a signed transaction should look like?
182181
const createSignedTransaction = () => {
182+
return '0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a02b79f322a625d623a2bb2911e0c6b3e7eaf741a7c7c5d2e8c67ef3ff4acf146ca01ae168fea63dc3391b75b586c8a7c0cb55cdf3b8e2e4d8e097957a3a56c6f2c5';
183+
};
184+
185+
const createTxParams = () => {
183186
return {
184187
from: '0x268392a24B6b093127E8581eAfbD1DA228bAdAe3',
185188
to: '0x0000000000000000000000000000000000000000',
@@ -193,19 +196,8 @@ const createSignedTransaction = () => {
193196
};
194197
};
195198

196-
// TODO: How exactly a signed canceled transaction should look like?
197199
const createSignedCanceledTransaction = () => {
198-
return {
199-
from: '0x268392a24B6b093127E8581eAfbD1DA228bAdAe3',
200-
to: '0x0000000000000000000000000000000000000000',
201-
value: 0,
202-
data: '0x',
203-
nonce: 0,
204-
type: 2,
205-
chainId: 4,
206-
maxFeePerGas: 2100001000,
207-
maxPriorityFeePerGas: 466503987,
208-
};
200+
return '0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a02b79f322a625d623a2bb2911e0c6b3e7eaf741a7c7c5d2e8c67ef3ff4acf146ca01ae168fea63dc3391b75b586c8a7c0cb55cdf3b8e2e4d8e097957a3a56c6f2c5';
209201
};
210202

211203
const createPendingBatchStatusApiResponse = () => ({
@@ -655,7 +647,7 @@ describe('SmartTransactionsController', () => {
655647
await smartTransactionsController.submitSignedTransactions({
656648
signedTransactions: [signedTransaction],
657649
signedCanceledTransactions: [signedCanceledTransaction],
658-
txParams: signedTransaction,
650+
txParams: createTxParams(),
659651
});
660652

661653
expect(
@@ -872,7 +864,6 @@ describe('SmartTransactionsController', () => {
872864
);
873865

874866
await flushPromises();
875-
876867
expect(
877868
smartTransactionsController.state.smartTransactionsState
878869
.smartTransactions[CHAIN_IDS.ETHEREUM],

src/SmartTransactionsController.ts

Lines changed: 38 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
isSmartTransactionPending,
4343
replayHistory,
4444
snapshotFromTxMeta,
45+
getTxHash,
4546
} from './utils';
4647

4748
const SECOND = 1000;
@@ -92,7 +93,7 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
9293

9394
private readonly trackMetaMetricsEvent: any;
9495

95-
private readonly eventEmitter: EventEmitter;
96+
public eventEmitter: EventEmitter;
9697

9798
private readonly getNetworkClientById: NetworkController['getNetworkClientById'];
9899

@@ -328,7 +329,7 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
328329
});
329330
}
330331

331-
#updateSmartTransaction(
332+
async #updateSmartTransaction(
332333
smartTransaction: SmartTransaction,
333334
{
334335
chainId = this.config.chainId,
@@ -337,7 +338,7 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
337338
chainId: Hex;
338339
ethQuery: EthQuery | undefined;
339340
},
340-
): void {
341+
): Promise<void> {
341342
const { smartTransactionsState } = this.state;
342343
const { smartTransactions } = smartTransactionsState;
343344
const currentSmartTransactions = smartTransactions[chainId] ?? [];
@@ -398,29 +399,32 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
398399
...currentSmartTransaction,
399400
...smartTransaction,
400401
};
401-
if (!smartTransaction.skipConfirm) {
402-
this.#confirmSmartTransaction(nextSmartTransaction, {
403-
chainId,
404-
ethQuery,
405-
});
406-
}
402+
await this.#confirmSmartTransaction(nextSmartTransaction, {
403+
chainId,
404+
ethQuery,
405+
});
406+
} else {
407+
this.update({
408+
smartTransactionsState: {
409+
...smartTransactionsState,
410+
smartTransactions: {
411+
...smartTransactionsState.smartTransactions,
412+
[chainId]: smartTransactionsState.smartTransactions[chainId].map(
413+
(item, index) => {
414+
return index === currentIndex
415+
? { ...item, ...smartTransaction }
416+
: item;
417+
},
418+
),
419+
},
420+
},
421+
});
407422
}
408423

409-
this.update({
410-
smartTransactionsState: {
411-
...smartTransactionsState,
412-
smartTransactions: {
413-
...smartTransactionsState.smartTransactions,
414-
[chainId]: smartTransactionsState.smartTransactions[chainId].map(
415-
(item, index) => {
416-
return index === currentIndex
417-
? { ...item, ...smartTransaction }
418-
: item;
419-
},
420-
),
421-
},
422-
},
423-
});
424+
this.eventEmitter.emit(
425+
`${smartTransaction.uuid}:smartTransaction`,
426+
smartTransaction,
427+
);
424428
}
425429

426430
async updateSmartTransactions({
@@ -453,10 +457,6 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
453457
ethQuery: EthQuery | undefined;
454458
},
455459
) {
456-
if (smartTransaction.skipConfirm) {
457-
return;
458-
}
459-
460460
if (ethQuery === undefined) {
461461
throw new Error(ETH_QUERY_ERROR_MSG);
462462
}
@@ -467,7 +467,6 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
467467
maxPriorityFeePerGas?: string;
468468
blockNumber: string;
469469
} | null = await query(ethQuery, 'getTransactionReceipt', [txHash]);
470-
471470
const transaction: {
472471
maxFeePerGas?: string;
473472
maxPriorityFeePerGas?: string;
@@ -568,7 +567,6 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
568567
cancellable: isSmartTransactionCancellable(stxStatus),
569568
uuid,
570569
};
571-
this.eventEmitter.emit(`${uuid}:smartTransaction`, smartTransaction);
572570
this.#updateSmartTransaction(smartTransaction, { chainId, ethQuery });
573571
});
574572

@@ -669,17 +667,17 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
669667
// * After this successful call client must add a nonce representative to
670668
// * transaction controller external transactions list
671669
async submitSignedTransactions({
670+
transactionMeta,
672671
txParams,
673672
signedTransactions,
674673
signedCanceledTransactions,
675674
networkClientId,
676-
skipConfirm,
677675
}: {
678676
signedTransactions: SignedTransaction[];
679677
signedCanceledTransactions: SignedCanceledTransaction[];
678+
transactionMeta?: any;
680679
txParams?: any;
681680
networkClientId?: NetworkClientId;
682-
skipConfirm?: boolean;
683681
}) {
684682
const chainId = this.#getChainId({ networkClientId });
685683
const ethQuery = this.#getEthQuery({ networkClientId });
@@ -717,6 +715,10 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
717715
txParams.nonce ??= nonce;
718716
}
719717
}
718+
const submitTransactionResponse = {
719+
...data,
720+
txHash: getTxHash(signedTransactions[0]),
721+
};
720722

721723
try {
722724
this.#updateSmartTransaction(
@@ -727,17 +729,18 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo
727729
status: SmartTransactionStatuses.PENDING,
728730
time,
729731
txParams,
730-
uuid: data.uuid,
732+
uuid: submitTransactionResponse.uuid,
733+
txHash: submitTransactionResponse.txHash,
731734
cancellable: true,
732-
skipConfirm: skipConfirm ?? false,
735+
type: transactionMeta?.type || 'swap',
733736
},
734737
{ chainId, ethQuery },
735738
);
736739
} finally {
737740
nonceLock?.releaseLock();
738741
}
739742

740-
return data;
743+
return submitTransactionResponse;
741744
}
742745

743746
#getChainId({

src/types.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export type SmartTransactionsStatus = {
7171

7272
export type SmartTransaction = {
7373
uuid: string;
74+
txHash?: string;
7475
chainId?: string;
7576
destinationTokenAddress?: string;
7677
destinationTokenDecimals?: string;
@@ -84,12 +85,12 @@ export type SmartTransaction = {
8485
sourceTokenSymbol?: string;
8586
swapMetaData?: any;
8687
swapTokenValue?: string;
87-
time?: number;
88+
time?: number; // @deprecated We should use creationTime instead.
89+
creationTime?: number;
8890
txParams?: any;
8991
type?: string;
9092
confirmed?: boolean;
9193
cancellable?: boolean;
92-
skipConfirm?: boolean;
9394
};
9495

9596
export type Fee = {

src/utils.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,17 @@ import {
1010
import * as utils from './utils';
1111
import packageJson from '../package.json';
1212

13+
const createSignedTransaction = () => {
14+
return '0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a02b79f322a625d623a2bb2911e0c6b3e7eaf741a7c7c5d2e8c67ef3ff4acf146ca01ae168fea63dc3391b75b586c8a7c0cb55cdf3b8e2e4d8e097957a3a56c6f2c5';
15+
};
16+
1317
describe('src/utils.js', () => {
1418
describe('isSmartTransactionPending', () => {
1519
const createSmartTransaction = () => {
1620
return {
1721
uuid: 'sdfasfj345345dfgag45353',
22+
txHash:
23+
'0x3c3e7c5e09c250d2200bcc3530f4a9088d7e3fb4ea3f4fccfd09f535a3539e84',
1824
status: 'pending',
1925
statusMetadata: {
2026
error: undefined,
@@ -274,4 +280,25 @@ describe('src/utils.js', () => {
274280
);
275281
});
276282
});
283+
284+
describe('getTxHash', () => {
285+
it('returns a transaction hash from a signed transaction', () => {
286+
const expectedTxHash =
287+
'0x0302b75dfb9fd9eb34056af031efcaee2a8cbd799ea054a85966165cd82a7356';
288+
const txHash = utils.getTxHash(createSignedTransaction());
289+
expect(txHash).toBe(expectedTxHash);
290+
});
291+
292+
it('returns an empty string if there is no signed transaction', () => {
293+
const expectedTxHash = '';
294+
const txHash = utils.getTxHash('');
295+
expect(txHash).toBe(expectedTxHash);
296+
});
297+
298+
it('throws an error with an incorrect signed transaction', () => {
299+
expect(() => {
300+
utils.getTxHash('0x0302b75dfb9fd9eb34056af0');
301+
}).toThrow('kzg instance required to instantiate blob tx');
302+
});
303+
});
277304
});

src/utils.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { TransactionFactory } from '@ethereumjs/tx';
2+
import { bytesToHex } from '@ethereumjs/util';
13
import { hexlify } from '@ethersproject/bytes';
24
import { BigNumber } from 'bignumber.js';
35
import jsonDiffer from 'fast-json-patch';
@@ -214,3 +216,14 @@ export const incrementNonceInHex = (nonceInHex: string): string => {
214216
const nonceInDec = new BigNumber(nonceInHex, 16).toString(10);
215217
return hexlify(Number(nonceInDec) + 1);
216218
};
219+
220+
export const getTxHash = (signedTxHex: any) => {
221+
if (!signedTxHex) {
222+
return '';
223+
}
224+
const txHashBytes = TransactionFactory.fromSerializedData(
225+
// eslint-disable-next-line no-restricted-globals
226+
Buffer.from(signedTxHex.slice(2), 'hex'),
227+
).hash();
228+
return bytesToHex(txHashBytes);
229+
};

0 commit comments

Comments
 (0)