mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 01:47:00 +01:00
Fix gas api overcalling (#12069)
* Move gasEstimation calls onto edit-gas-popover instead of transaction list item * delete useCancelTransaction and useRetryTransaction hooks consolidate gasFee calls into cancel button component. * add tests * update component name * addressing feedback * fix failing e2e test * followup fix e2e tests * change useIncrementedGasFees to accept single transaction rather than transactionGroup as argument * remove unnecessary change to fixture * only ever pass primary transaction to useIncrementedGasFees * remove unnecessary optional chaining
This commit is contained in:
parent
28fc2d471f
commit
74fa6fa187
234
test/data/mock-pending-transaction-data.json
Normal file
234
test/data/mock-pending-transaction-data.json
Normal file
@ -0,0 +1,234 @@
|
||||
{
|
||||
"hasCancelled": false,
|
||||
"hasRetried": false,
|
||||
"initialTransaction": {
|
||||
"id": 6854191329910881,
|
||||
"time": 1631558469046,
|
||||
"status": "approved",
|
||||
"metamaskNetworkId": "42",
|
||||
"chainId": "0x2a",
|
||||
"loadingDefaults": false,
|
||||
"dappSuggestedGasFees": null,
|
||||
"txParams": {
|
||||
"from": "0x0853dccd30e0582df80b16ec014092160b48e797",
|
||||
"to": "0x8d09d17af2e20f51a9b598cb9edd01489a26ae27",
|
||||
"value": "0x38d7ea4c68000",
|
||||
"gas": "0x5208",
|
||||
"maxFeePerGas": "0x47868c00",
|
||||
"maxPriorityFeePerGas": "0x47868c00",
|
||||
"type": "0x2"
|
||||
},
|
||||
"origin": "metamask",
|
||||
"type": "sentEther",
|
||||
"history": [
|
||||
{
|
||||
"id": 6854191329910881,
|
||||
"time": 1631558469046,
|
||||
"status": "unapproved",
|
||||
"metamaskNetworkId": "42",
|
||||
"chainId": "0x2a",
|
||||
"loadingDefaults": true,
|
||||
"dappSuggestedGasFees": null,
|
||||
"txParams": {
|
||||
"from": "0x0853dccd30e0582df80b16ec014092160b48e797",
|
||||
"to": "0x8d09d17af2e20f51a9b598cb9edd01489a26ae27",
|
||||
"value": "0x38d7ea4c68000",
|
||||
"gas": "0x5208",
|
||||
"maxFeePerGas": "0x47868c00",
|
||||
"maxPriorityFeePerGas": "0x47868c00",
|
||||
"type": "0x2"
|
||||
},
|
||||
"origin": "metamask",
|
||||
"type": "sentEther"
|
||||
},
|
||||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/loadingDefaults",
|
||||
"value": false,
|
||||
"note": "Added new unapproved transaction.",
|
||||
"timestamp": 1631558469059
|
||||
},
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/userFeeLevel",
|
||||
"value": "medium"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/estimatedBaseFee",
|
||||
"value": "0",
|
||||
"note": "confTx: user approved transaction",
|
||||
"timestamp": 1631558472917
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/status",
|
||||
"value": "approved",
|
||||
"note": "txStateManager: setting status to approved",
|
||||
"timestamp": 1631558472925
|
||||
}
|
||||
]
|
||||
],
|
||||
"userFeeLevel": "medium",
|
||||
"estimatedBaseFee": "0"
|
||||
},
|
||||
"primaryTransaction": {
|
||||
"id": 6854191329910881,
|
||||
"time": 1631558469046,
|
||||
"status": "approved",
|
||||
"metamaskNetworkId": "42",
|
||||
"chainId": "0x2a",
|
||||
"loadingDefaults": false,
|
||||
"dappSuggestedGasFees": null,
|
||||
"txParams": {
|
||||
"from": "0x0853dccd30e0582df80b16ec014092160b48e797",
|
||||
"to": "0x8d09d17af2e20f51a9b598cb9edd01489a26ae27",
|
||||
"value": "0x38d7ea4c68000",
|
||||
"gas": "0x5208",
|
||||
"maxFeePerGas": "0x47868c00",
|
||||
"maxPriorityFeePerGas": "0x47868c00",
|
||||
"type": "0x2"
|
||||
},
|
||||
"origin": "metamask",
|
||||
"type": "sentEther",
|
||||
"history": [
|
||||
{
|
||||
"id": 6854191329910881,
|
||||
"time": 1631558469046,
|
||||
"status": "unapproved",
|
||||
"metamaskNetworkId": "42",
|
||||
"chainId": "0x2a",
|
||||
"loadingDefaults": true,
|
||||
"dappSuggestedGasFees": null,
|
||||
"txParams": {
|
||||
"from": "0x0853dccd30e0582df80b16ec014092160b48e797",
|
||||
"to": "0x8d09d17af2e20f51a9b598cb9edd01489a26ae27",
|
||||
"value": "0x38d7ea4c68000",
|
||||
"gas": "0x5208",
|
||||
"maxFeePerGas": "0x47868c00",
|
||||
"maxPriorityFeePerGas": "0x47868c00",
|
||||
"type": "0x2"
|
||||
},
|
||||
"origin": "metamask",
|
||||
"type": "sentEther"
|
||||
},
|
||||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/loadingDefaults",
|
||||
"value": false,
|
||||
"note": "Added new unapproved transaction.",
|
||||
"timestamp": 1631558469059
|
||||
},
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/userFeeLevel",
|
||||
"value": "medium"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/estimatedBaseFee",
|
||||
"value": "0",
|
||||
"note": "confTx: user approved transaction",
|
||||
"timestamp": 1631558472917
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/status",
|
||||
"value": "approved",
|
||||
"note": "txStateManager: setting status to approved",
|
||||
"timestamp": 1631558472925
|
||||
}
|
||||
]
|
||||
],
|
||||
"userFeeLevel": "medium",
|
||||
"estimatedBaseFee": "0"
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
"id": 6854191329910881,
|
||||
"time": 1631558469046,
|
||||
"status": "approved",
|
||||
"metamaskNetworkId": "42",
|
||||
"chainId": "0x2a",
|
||||
"loadingDefaults": false,
|
||||
"dappSuggestedGasFees": null,
|
||||
"txParams": {
|
||||
"from": "0x0853dccd30e0582df80b16ec014092160b48e797",
|
||||
"to": "0x8d09d17af2e20f51a9b598cb9edd01489a26ae27",
|
||||
"value": "0x38d7ea4c68000",
|
||||
"gas": "0x5208",
|
||||
"maxFeePerGas": "0x47868c00",
|
||||
"maxPriorityFeePerGas": "0x47868c00",
|
||||
"type": "0x2"
|
||||
},
|
||||
"origin": "metamask",
|
||||
"type": "sentEther",
|
||||
"history": [
|
||||
{
|
||||
"id": 6854191329910881,
|
||||
"time": 1631558469046,
|
||||
"status": "unapproved",
|
||||
"metamaskNetworkId": "42",
|
||||
"chainId": "0x2a",
|
||||
"loadingDefaults": true,
|
||||
"dappSuggestedGasFees": null,
|
||||
"txParams": {
|
||||
"from": "0x0853dccd30e0582df80b16ec014092160b48e797",
|
||||
"to": "0x8d09d17af2e20f51a9b598cb9edd01489a26ae27",
|
||||
"value": "0x38d7ea4c68000",
|
||||
"gas": "0x5208",
|
||||
"maxFeePerGas": "0x47868c00",
|
||||
"maxPriorityFeePerGas": "0x47868c00",
|
||||
"type": "0x2"
|
||||
},
|
||||
"origin": "metamask",
|
||||
"type": "sentEther"
|
||||
},
|
||||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/loadingDefaults",
|
||||
"value": false,
|
||||
"note": "Added new unapproved transaction.",
|
||||
"timestamp": 1631558469059
|
||||
},
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/userFeeLevel",
|
||||
"value": "medium"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/estimatedBaseFee",
|
||||
"value": "0",
|
||||
"note": "confTx: user approved transaction",
|
||||
"timestamp": 1631558472917
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/status",
|
||||
"value": "approved",
|
||||
"note": "txStateManager: setting status to approved",
|
||||
"timestamp": 1631558472925
|
||||
}
|
||||
]
|
||||
],
|
||||
"userFeeLevel": "medium",
|
||||
"estimatedBaseFee": "0"
|
||||
}
|
||||
]
|
||||
}
|
@ -61,6 +61,68 @@
|
||||
"transactionIndex": "0"
|
||||
}
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
"id": 4243712234858512,
|
||||
"time": 1589314601567,
|
||||
"status": "confirmed",
|
||||
"metamaskNetworkId": "4",
|
||||
"loadingDefaults": false,
|
||||
"txParams": {
|
||||
"from": "0x9eca64466f257793eaa52fcfff5066894b76a149",
|
||||
"to": "0xffe5bc4e8f1f969934d773fa67da095d2e491a97",
|
||||
"nonce": "0xc",
|
||||
"value": "0xde0b6b3a7640000",
|
||||
"gas": "0x5208",
|
||||
"gasPrice": "0x2540be400"
|
||||
},
|
||||
"origin": "metamask",
|
||||
"type": "sentEther",
|
||||
"nonceDetails": {
|
||||
"params": {
|
||||
"highestLocallyConfirmed": 12,
|
||||
"highestSuggested": 12,
|
||||
"nextNetworkNonce": 12
|
||||
},
|
||||
"local": {
|
||||
"name": "local",
|
||||
"nonce": 12,
|
||||
"details": {
|
||||
"startPoint": 12,
|
||||
"highest": 12
|
||||
}
|
||||
},
|
||||
"network": {
|
||||
"name": "network",
|
||||
"nonce": 12,
|
||||
"details": {
|
||||
"blockNumber": "0x62d5dc",
|
||||
"baseCount": 12
|
||||
}
|
||||
}
|
||||
},
|
||||
"r": "0xe0b79a8e33b15460ea79b05a5fb16bc067a796592eeb4edc5007c88615c12595",
|
||||
"s": "0x1c834a25f1df07af5122996a40e99e554a40dc971a25041bc6e31638846c4f58",
|
||||
"v": "0x2c",
|
||||
"rawTx": "0xf86c0c8502540be40082520894ffe5bc4e8f1f969934d773fa67da095d2e491a97880de0b6b3a7640000802ca0e0b79a8e33b15460ea79b05a5fb16bc067a796592eeb4edc5007c88615c12595a01c834a25f1df07af5122996a40e99e554a40dc971a25041bc6e31638846c4f58",
|
||||
"hash": "0x06bb79b856f5eb67025e4c4ffff44bca26ae135d1c3e6bd9a4193f422dcecca2",
|
||||
"submittedTime": 1589314602908,
|
||||
"txReceipt": {
|
||||
"blockHash": "0xb9d2d71153b66146fde74b14b1c1ffc0588eb4a02ff464e32a4db9ae4bbfad8a",
|
||||
"blockNumber": "62d5de",
|
||||
"contractAddress": null,
|
||||
"cumulativeGasUsed": "5208",
|
||||
"from": "0x9eca64466f257793eaa52fcfff5066894b76a149",
|
||||
"gasUsed": "5208",
|
||||
"logs": [],
|
||||
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
"status": "0x1",
|
||||
"to": "0xffe5bc4e8f1f969934d773fa67da095d2e491a97",
|
||||
"transactionHash": "0x06bb79b856f5eb67025e4c4ffff44bca26ae135d1c3e6bd9a4193f422dcecca2",
|
||||
"transactionIndex": "0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"primaryTransaction": {
|
||||
"id": 4243712234858512,
|
||||
"time": 1589314601567,
|
||||
@ -186,6 +248,68 @@
|
||||
"transactionIndex": "0"
|
||||
}
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
"id": 4243712234858507,
|
||||
"time": 1589314355872,
|
||||
"status": "confirmed",
|
||||
"metamaskNetworkId": "4",
|
||||
"loadingDefaults": false,
|
||||
"txParams": {
|
||||
"from": "0x9eca64466f257793eaa52fcfff5066894b76a149",
|
||||
"to": "0x0ccc8aeeaf5ce790f3b448325981a143fdef8848",
|
||||
"nonce": "0xb",
|
||||
"value": "0x1bc16d674ec80000",
|
||||
"gas": "0x5208",
|
||||
"gasPrice": "0x2540be400"
|
||||
},
|
||||
"origin": "metamask",
|
||||
"type": "sentEther",
|
||||
"nonceDetails": {
|
||||
"params": {
|
||||
"highestLocallyConfirmed": 0,
|
||||
"highestSuggested": 10,
|
||||
"nextNetworkNonce": 10
|
||||
},
|
||||
"local": {
|
||||
"name": "local",
|
||||
"nonce": 11,
|
||||
"details": {
|
||||
"startPoint": 10,
|
||||
"highest": 11
|
||||
}
|
||||
},
|
||||
"network": {
|
||||
"name": "network",
|
||||
"nonce": 10,
|
||||
"details": {
|
||||
"blockNumber": "0x62d5cc",
|
||||
"baseCount": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
"r": "0xe6828baea0a93a52779ffa5ea55e927781fb7d4be58107a29c75d314d433d055",
|
||||
"s": "0x10613f984c57b8928d8ed9fce16ddda5746767e5f68f4c8fc29542e86a61f458",
|
||||
"v": "0x2b",
|
||||
"rawTx": "0xf86c0b8502540be400825208940ccc8aeeaf5ce790f3b448325981a143fdef8848881bc16d674ec80000802ba0e6828baea0a93a52779ffa5ea55e927781fb7d4be58107a29c75d314d433d055a010613f984c57b8928d8ed9fce16ddda5746767e5f68f4c8fc29542e86a61f458",
|
||||
"hash": "0x2ccb9e2c0c64399ebc5c4ac70bebc5b537248458dee6cbce32df4b50c9e73bbd",
|
||||
"submittedTime": 1589314356907,
|
||||
"txReceipt": {
|
||||
"blockHash": "0xfa3c8b63aaba2ef64ab328af72811dd5110a7641bd435cc6fbdfd9ea0d334542",
|
||||
"blockNumber": "62d5ce",
|
||||
"contractAddress": null,
|
||||
"cumulativeGasUsed": "5208",
|
||||
"from": "0x9eca64466f257793eaa52fcfff5066894b76a149",
|
||||
"gasUsed": "5208",
|
||||
"logs": [],
|
||||
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
"status": "0x1",
|
||||
"to": "0x0ccc8aeeaf5ce790f3b448325981a143fdef8848",
|
||||
"transactionHash": "0x2ccb9e2c0c64399ebc5c4ac70bebc5b537248458dee6cbce32df4b50c9e73bbd",
|
||||
"transactionIndex": "0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"primaryTransaction": {
|
||||
"id": 4243712234858507,
|
||||
"time": 1589314355872,
|
||||
@ -312,6 +436,69 @@
|
||||
"transactionIndex": "1"
|
||||
}
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
"id": 4243712234858506,
|
||||
"time": 1589314345433,
|
||||
"status": "confirmed",
|
||||
"metamaskNetworkId": "4",
|
||||
"loadingDefaults": false,
|
||||
"txParams": {
|
||||
"from": "0x9eca64466f257793eaa52fcfff5066894b76a149",
|
||||
"to": "0xffe5bc4e8f1f969934d773fa67da095d2e491a97",
|
||||
"nonce": "0xa",
|
||||
"value": "0x1bc16d674ec80000",
|
||||
"gas": "0x5208",
|
||||
"gasPrice": "0x306dc4200"
|
||||
},
|
||||
"origin": "metamask",
|
||||
"type": "sentEther",
|
||||
"nonceDetails": {
|
||||
"params": {
|
||||
"highestLocallyConfirmed": 0,
|
||||
"highestSuggested": 10,
|
||||
"nextNetworkNonce": 10
|
||||
},
|
||||
"local": {
|
||||
"name": "local",
|
||||
"nonce": 10,
|
||||
"details": {
|
||||
"startPoint": 10,
|
||||
"highest": 10
|
||||
}
|
||||
},
|
||||
"network": {
|
||||
"name": "network",
|
||||
"nonce": 10,
|
||||
"details": {
|
||||
"blockNumber": "0x62d5cb",
|
||||
"baseCount": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
"r": "0x94b120a1df80be3dfad057b7ccac866b6b7583b63d61e5b021811c8b7ffc9a3b",
|
||||
"s": "0x1778de08e29a4c8dfc3aa3e2c2338e98494ebd2c380c901d0dfba95126dde65f",
|
||||
"v": "0x2c",
|
||||
"rawTx": "0xf86c0a850306dc420082520894ffe5bc4e8f1f969934d773fa67da095d2e491a97881bc16d674ec80000802ca094b120a1df80be3dfad057b7ccac866b6b7583b63d61e5b021811c8b7ffc9a3ba01778de08e29a4c8dfc3aa3e2c2338e98494ebd2c380c901d0dfba95126dde65f",
|
||||
"hash": "0x52604fd8d329894a747d8cf521cbbc4adb35eb9a91e3a3ba3ee32d8729c16536",
|
||||
"submittedTime": 1589314348235,
|
||||
"firstRetryBlockNumber": "0x62d5cc",
|
||||
"txReceipt": {
|
||||
"blockHash": "0x3d61a8d8a0e79e0e7a3a9206bf62f9a8e47791c527cd85cb4fcf800609234115",
|
||||
"blockNumber": "62d5cd",
|
||||
"contractAddress": null,
|
||||
"cumulativeGasUsed": "a810",
|
||||
"from": "0x9eca64466f257793eaa52fcfff5066894b76a149",
|
||||
"gasUsed": "5208",
|
||||
"logs": [],
|
||||
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
"status": "0x1",
|
||||
"to": "0xffe5bc4e8f1f969934d773fa67da095d2e491a97",
|
||||
"transactionHash": "0x52604fd8d329894a747d8cf521cbbc4adb35eb9a91e3a3ba3ee32d8729c16536",
|
||||
"transactionIndex": "1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"primaryTransaction": {
|
||||
"id": 4243712234858506,
|
||||
"time": 1589314345433,
|
||||
@ -394,6 +581,25 @@
|
||||
"hash": "0x5ca26d1cdcabef1ac2ad5b2b38604c9ced65d143efc7525f848c46f28e0e4116",
|
||||
"type": "incoming"
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
"blockNumber": "6477257",
|
||||
"id": 4243712234858505,
|
||||
"metamaskNetworkId": "4",
|
||||
"status": "confirmed",
|
||||
"time": 1589314295000,
|
||||
"txParams": {
|
||||
"from": "0x31b98d14007bdee637298086988a0bbd31184523",
|
||||
"gas": "0x5208",
|
||||
"gasPrice": "0x3b9aca00",
|
||||
"nonce": "0x56540",
|
||||
"to": "0x9eca64466f257793eaa52fcfff5066894b76a149",
|
||||
"value": "0x1043561a882930000"
|
||||
},
|
||||
"hash": "0x5ca26d1cdcabef1ac2ad5b2b38604c9ced65d143efc7525f848c46f28e0e4116",
|
||||
"type": "incoming"
|
||||
}
|
||||
],
|
||||
"primaryTransaction": {
|
||||
"blockNumber": "6477257",
|
||||
"id": 4243712234858505,
|
||||
@ -432,6 +638,25 @@
|
||||
"hash": "0xa42b2b433e5bd2616b52e30792aedb6a3c374a752a95d43d99e2a8b143937889",
|
||||
"type": "incoming"
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
"blockNumber": "6454493",
|
||||
"id": 4243712234858475,
|
||||
"metamaskNetworkId": "4",
|
||||
"status": "confirmed",
|
||||
"time": 1588972833000,
|
||||
"txParams": {
|
||||
"from": "0x9eca64466f257793eaa52fcfff5066894b76a149",
|
||||
"gas": "0x5208",
|
||||
"gasPrice": "0x24e160300",
|
||||
"nonce": "0x8",
|
||||
"to": "0x9eca64466f257793eaa52fcfff5066894b76a149",
|
||||
"value": "0x0"
|
||||
},
|
||||
"hash": "0xa42b2b433e5bd2616b52e30792aedb6a3c374a752a95d43d99e2a8b143937889",
|
||||
"type": "incoming"
|
||||
}
|
||||
],
|
||||
"primaryTransaction": {
|
||||
"blockNumber": "6454493",
|
||||
"id": 4243712234858475,
|
||||
@ -470,6 +695,25 @@
|
||||
"hash": "0xbcb195f393f4468945b4045cd41bcdbc2f19ad75ae92a32cf153a3004e42009a",
|
||||
"type": "incoming"
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
"blockNumber": "6195526",
|
||||
"id": 4243712234858466,
|
||||
"metamaskNetworkId": "4",
|
||||
"status": "confirmed",
|
||||
"time": 1585087013000,
|
||||
"txParams": {
|
||||
"from": "0xee014609ef9e09776ac5fe00bdbfef57bcdefebb",
|
||||
"gas": "0x5208",
|
||||
"gasPrice": "0x77359400",
|
||||
"nonce": "0x3",
|
||||
"to": "0x9eca64466f257793eaa52fcfff5066894b76a149",
|
||||
"value": "0xde0b6b3a7640000"
|
||||
},
|
||||
"hash": "0xbcb195f393f4468945b4045cd41bcdbc2f19ad75ae92a32cf153a3004e42009a",
|
||||
"type": "incoming"
|
||||
}
|
||||
],
|
||||
"primaryTransaction": {
|
||||
"blockNumber": "6195526",
|
||||
"id": 4243712234858466,
|
||||
@ -510,6 +754,28 @@
|
||||
"sourceTokenSymbol": "ETH",
|
||||
"type": "swap"
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
"blockNumber": "6195527",
|
||||
"id": 4243712234858467,
|
||||
"metamaskNetworkId": "4",
|
||||
"status": "confirmed",
|
||||
"time": 1585088013000,
|
||||
"txParams": {
|
||||
"from": "0xee014609ef9e09776ac5fe00bdbfef57bcdefebb",
|
||||
"gas": "0x5208",
|
||||
"gasPrice": "0x77359400",
|
||||
"nonce": "0x3",
|
||||
"to": "0xabca64466f257793eaa52fcfff5066894b76a149",
|
||||
"value": "0xde0b6b3a7640000"
|
||||
},
|
||||
"hash": "0xbcb195f393f4468945b4045cd41bcdbc2f19ad75ae92a32cf153a3004e42009a",
|
||||
"type": "swap",
|
||||
"destinationTokenSymbol": "ABC",
|
||||
"destinationTokenAddress": "0xabca64466f257793eaa52fcfff5066894b76a149",
|
||||
"sourceTokenSymbol": "ETH"
|
||||
}
|
||||
],
|
||||
"primaryTransaction": {
|
||||
"blockNumber": "6195527",
|
||||
"id": 4243712234858467,
|
||||
|
@ -130,6 +130,23 @@
|
||||
"transactions": {
|
||||
"4046084157914634": {
|
||||
"chainId": "0x539",
|
||||
"primaryTransaction": {
|
||||
"chainId": "0x539",
|
||||
"id": 4046084157914634,
|
||||
"loadingDefaults": true,
|
||||
"metamaskNetworkId": "1337",
|
||||
"origin": "metamask",
|
||||
"status": "unapproved",
|
||||
"time": 1617228030067,
|
||||
"txParams": {
|
||||
"from": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1",
|
||||
"gas": "0x61a8",
|
||||
"gasPrice": "0x2540be400",
|
||||
"to": "0x2f318C334780961FB129D2a6c30D0763d9a5C970",
|
||||
"value": "0xde0b6b3a7640000"
|
||||
},
|
||||
"type": "sentEther"
|
||||
},
|
||||
"history": [
|
||||
{
|
||||
"chainId": "0x539",
|
||||
|
64
ui/components/app/cancel-button/cancel-button.js
Normal file
64
ui/components/app/cancel-button/cancel-button.js
Normal file
@ -0,0 +1,64 @@
|
||||
import { Tooltip } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useSelector } from 'react-redux';
|
||||
import classnames from 'classnames';
|
||||
import Button from '../../ui/button';
|
||||
import { getMaximumGasTotalInHexWei } from '../../../../shared/modules/gas.utils';
|
||||
import { getConversionRate } from '../../../ducks/metamask/metamask';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import { useIncrementedGasFees } from '../../../hooks/useIncrementedGasFees';
|
||||
import { isBalanceSufficient } from '../../../pages/send/send.utils';
|
||||
import { getSelectedAccount } from '../../../selectors';
|
||||
|
||||
export default function CancelButton({
|
||||
cancelTransaction,
|
||||
transaction,
|
||||
detailsModal,
|
||||
}) {
|
||||
const t = useI18nContext();
|
||||
|
||||
const customCancelGasSettings = useIncrementedGasFees(transaction);
|
||||
|
||||
const selectedAccount = useSelector(getSelectedAccount);
|
||||
const conversionRate = useSelector(getConversionRate);
|
||||
|
||||
const hasEnoughCancelGas = isBalanceSufficient({
|
||||
amount: '0x0',
|
||||
gasTotal: getMaximumGasTotalInHexWei(customCancelGasSettings),
|
||||
balance: selectedAccount.balance,
|
||||
conversionRate,
|
||||
});
|
||||
|
||||
const btn = (
|
||||
<Button
|
||||
onClick={cancelTransaction}
|
||||
rounded={!detailsModal}
|
||||
type={detailsModal ? 'raise' : null}
|
||||
className={classnames({
|
||||
'transaction-list-item__header-button': !detailsModal,
|
||||
'transaction-list-item-details__header-button': detailsModal,
|
||||
})}
|
||||
disabled={!hasEnoughCancelGas}
|
||||
>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
);
|
||||
return hasEnoughCancelGas ? (
|
||||
btn
|
||||
) : (
|
||||
<Tooltip
|
||||
title={t('notEnoughGas')}
|
||||
data-testid="not-enough-gas__tooltip"
|
||||
position="bottom"
|
||||
>
|
||||
<div>{btn}</div>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
CancelButton.propTypes = {
|
||||
transaction: PropTypes.object,
|
||||
cancelTransaction: PropTypes.func,
|
||||
detailsModal: PropTypes.bool,
|
||||
};
|
1
ui/components/app/cancel-button/index.js
Normal file
1
ui/components/app/cancel-button/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './cancel-button';
|
@ -28,6 +28,7 @@ import {
|
||||
} from '../../../store/actions';
|
||||
import LoadingHeartBeat from '../../ui/loading-heartbeat';
|
||||
import { checkNetworkAndAccountSupports1559 } from '../../../selectors';
|
||||
import { useIncrementedGasFees } from '../../../hooks/useIncrementedGasFees';
|
||||
|
||||
export default function EditGasPopover({
|
||||
popoverTitle = '',
|
||||
@ -62,6 +63,19 @@ export default function EditGasPopover({
|
||||
] = useState(false);
|
||||
|
||||
const minimumGasLimitDec = hexToDecimal(minimumGasLimit);
|
||||
const updatedCustomGasSettings = useIncrementedGasFees(transaction);
|
||||
|
||||
let updatedTransaction = transaction;
|
||||
if (mode === EDIT_GAS_MODES.SPEED_UP || mode === EDIT_GAS_MODES.CANCEL) {
|
||||
updatedTransaction = {
|
||||
...transaction,
|
||||
userFeeLevel: 'custom',
|
||||
txParams: {
|
||||
...transaction.txParams,
|
||||
...updatedCustomGasSettings,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const {
|
||||
maxPriorityFeePerGas,
|
||||
@ -89,10 +103,15 @@ export default function EditGasPopover({
|
||||
balanceError,
|
||||
estimatesUnavailableWarning,
|
||||
estimatedBaseFee,
|
||||
} = useGasFeeInputs(defaultEstimateToUse, transaction, minimumGasLimit, mode);
|
||||
} = useGasFeeInputs(
|
||||
defaultEstimateToUse,
|
||||
updatedTransaction,
|
||||
minimumGasLimit,
|
||||
mode,
|
||||
);
|
||||
|
||||
const txParamsHaveBeenCustomized =
|
||||
estimateToUse === 'custom' || txParamsAreDappSuggested(transaction);
|
||||
estimateToUse === 'custom' || txParamsAreDappSuggested(updatedTransaction);
|
||||
|
||||
/**
|
||||
* Temporary placeholder, this should be managed by the parent component but
|
||||
@ -109,7 +128,7 @@ export default function EditGasPopover({
|
||||
}, [onClose, dispatch]);
|
||||
|
||||
const onSubmit = useCallback(() => {
|
||||
if (!transaction || !mode) {
|
||||
if (!updatedTransaction || !mode) {
|
||||
closePopover();
|
||||
}
|
||||
|
||||
@ -128,14 +147,14 @@ export default function EditGasPopover({
|
||||
gasPrice: decGWEIToHexWEI(gasPrice),
|
||||
};
|
||||
|
||||
const cleanTransactionParams = { ...transaction.txParams };
|
||||
const cleanTransactionParams = { ...updatedTransaction.txParams };
|
||||
|
||||
if (networkAndAccountSupport1559) {
|
||||
delete cleanTransactionParams.gasPrice;
|
||||
}
|
||||
|
||||
const updatedTxMeta = {
|
||||
...transaction,
|
||||
...updatedTransaction,
|
||||
userFeeLevel: estimateToUse || 'custom',
|
||||
txParams: {
|
||||
...cleanTransactionParams,
|
||||
@ -146,14 +165,14 @@ export default function EditGasPopover({
|
||||
switch (mode) {
|
||||
case EDIT_GAS_MODES.CANCEL:
|
||||
dispatch(
|
||||
createCancelTransaction(transaction.id, newGasSettings, {
|
||||
createCancelTransaction(updatedTransaction.id, newGasSettings, {
|
||||
estimatedBaseFee,
|
||||
}),
|
||||
);
|
||||
break;
|
||||
case EDIT_GAS_MODES.SPEED_UP:
|
||||
dispatch(
|
||||
createSpeedUpTransaction(transaction.id, newGasSettings, {
|
||||
createSpeedUpTransaction(updatedTransaction.id, newGasSettings, {
|
||||
estimatedBaseFee,
|
||||
}),
|
||||
);
|
||||
@ -174,7 +193,7 @@ export default function EditGasPopover({
|
||||
|
||||
closePopover();
|
||||
}, [
|
||||
transaction,
|
||||
updatedTransaction,
|
||||
mode,
|
||||
dispatch,
|
||||
closePopover,
|
||||
@ -259,7 +278,7 @@ export default function EditGasPopover({
|
||||
estimatedMaximumFiat={estimatedMaximumFiat}
|
||||
onEducationClick={() => setShowEducationContent(true)}
|
||||
mode={mode}
|
||||
transaction={transaction}
|
||||
transaction={updatedTransaction}
|
||||
gasErrors={gasErrors}
|
||||
gasWarnings={gasWarnings}
|
||||
onManualChange={onManualChange}
|
||||
|
@ -9,6 +9,7 @@ import TransactionBreakdown from '../transaction-breakdown';
|
||||
import Button from '../../ui/button';
|
||||
import Tooltip from '../../ui/tooltip';
|
||||
import Copy from '../../ui/icon/copy-icon.component';
|
||||
import CancelButton from '../cancel-button';
|
||||
import Popover from '../../ui/popover';
|
||||
import { SECOND } from '../../../../shared/constants/time';
|
||||
import { TRANSACTION_TYPES } from '../../../../shared/constants/transaction';
|
||||
@ -32,7 +33,6 @@ export default class TransactionListItemDetails extends PureComponent {
|
||||
showSpeedUp: PropTypes.bool,
|
||||
showRetry: PropTypes.bool,
|
||||
isEarliestNonce: PropTypes.bool,
|
||||
cancelDisabled: PropTypes.bool,
|
||||
primaryCurrency: PropTypes.string,
|
||||
transactionGroup: PropTypes.object,
|
||||
title: PropTypes.string.isRequired,
|
||||
@ -114,38 +114,6 @@ export default class TransactionListItemDetails extends PureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
renderCancel() {
|
||||
const { t } = this.context;
|
||||
const { showCancel, cancelDisabled } = this.props;
|
||||
|
||||
if (!showCancel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return cancelDisabled ? (
|
||||
<Tooltip title={t('notEnoughGas')} position="bottom">
|
||||
<div>
|
||||
<Button
|
||||
type="raised"
|
||||
onClick={this.handleCancel}
|
||||
className="transaction-list-item-details__header-button"
|
||||
disabled
|
||||
>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Button
|
||||
type="raised"
|
||||
onClick={this.handleCancel}
|
||||
className="transaction-list-item-details__header-button"
|
||||
>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { t } = this.context;
|
||||
const { justCopied } = this.state;
|
||||
@ -163,6 +131,7 @@ export default class TransactionListItemDetails extends PureComponent {
|
||||
title,
|
||||
onClose,
|
||||
recipientNickname,
|
||||
showCancel,
|
||||
} = this.props;
|
||||
const {
|
||||
primaryTransaction: transaction,
|
||||
@ -185,7 +154,13 @@ export default class TransactionListItemDetails extends PureComponent {
|
||||
{t('speedUp')}
|
||||
</Button>
|
||||
)}
|
||||
{this.renderCancel()}
|
||||
{showCancel && (
|
||||
<CancelButton
|
||||
transaction={transaction}
|
||||
cancelTransaction={this.handleCancel}
|
||||
detailsModal
|
||||
/>
|
||||
)}
|
||||
<Tooltip
|
||||
wrapperClassName="transaction-list-item-details__header-button"
|
||||
containerClassName="transaction-list-item-details__header-button-tooltip-container"
|
||||
|
@ -5,10 +5,7 @@ import { useHistory } from 'react-router-dom';
|
||||
import ListItem from '../../ui/list-item';
|
||||
import { useTransactionDisplayData } from '../../../hooks/useTransactionDisplayData';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
import { useCancelTransaction } from '../../../hooks/useCancelTransaction';
|
||||
import { useRetryTransaction } from '../../../hooks/useRetryTransaction';
|
||||
import Button from '../../ui/button';
|
||||
import Tooltip from '../../ui/tooltip';
|
||||
|
||||
import TransactionListItemDetails from '../transaction-list-item-details';
|
||||
import { CONFIRM_TRANSACTION_ROUTE } from '../../../helpers/constants/routes';
|
||||
import { useShouldShowSpeedUp } from '../../../hooks/useShouldShowSpeedUp';
|
||||
@ -20,6 +17,9 @@ import {
|
||||
} from '../../../../shared/constants/transaction';
|
||||
import { EDIT_GAS_MODES } from '../../../../shared/constants/gas';
|
||||
import EditGasPopover from '../edit-gas-popover';
|
||||
import { useMetricEvent } from '../../../hooks/useMetricEvent';
|
||||
import Button from '../../ui/button';
|
||||
import CancelButton from '../cancel-button';
|
||||
|
||||
export default function TransactionListItem({
|
||||
transactionGroup,
|
||||
@ -29,24 +29,50 @@ export default function TransactionListItem({
|
||||
const history = useHistory();
|
||||
const { hasCancelled } = transactionGroup;
|
||||
const [showDetails, setShowDetails] = useState(false);
|
||||
const [showCancelEditGasPopover, setShowCancelEditGasPopover] = useState(
|
||||
false,
|
||||
);
|
||||
const [showRetryEditGasPopover, setShowRetryEditGasPopover] = useState(false);
|
||||
|
||||
const {
|
||||
initialTransaction: { id },
|
||||
primaryTransaction: { err, status },
|
||||
} = transactionGroup;
|
||||
const {
|
||||
hasEnoughCancelGas,
|
||||
cancelTransaction,
|
||||
showCancelEditGasPopover,
|
||||
closeCancelEditGasPopover,
|
||||
customCancelGasSettings,
|
||||
} = useCancelTransaction(transactionGroup);
|
||||
const {
|
||||
retryTransaction,
|
||||
showRetryEditGasPopover,
|
||||
closeRetryEditGasPopover,
|
||||
customRetryGasSettings,
|
||||
} = useRetryTransaction(transactionGroup);
|
||||
|
||||
const speedUpMetricsEvent = useMetricEvent({
|
||||
eventOpts: {
|
||||
category: 'Navigation',
|
||||
action: 'Activity Log',
|
||||
name: 'Clicked "Speed Up"',
|
||||
},
|
||||
});
|
||||
|
||||
const cancelMetricsEvent = useMetricEvent({
|
||||
eventOpts: {
|
||||
category: 'Navigation',
|
||||
action: 'Activity Log',
|
||||
name: 'Clicked "Cancel"',
|
||||
},
|
||||
});
|
||||
|
||||
const retryTransaction = useCallback(
|
||||
async (event) => {
|
||||
event.stopPropagation();
|
||||
setShowRetryEditGasPopover(true);
|
||||
speedUpMetricsEvent();
|
||||
},
|
||||
[speedUpMetricsEvent],
|
||||
);
|
||||
|
||||
const cancelTransaction = useCallback(
|
||||
(event) => {
|
||||
event.stopPropagation();
|
||||
setShowCancelEditGasPopover(true);
|
||||
cancelMetricsEvent();
|
||||
},
|
||||
[cancelMetricsEvent],
|
||||
);
|
||||
|
||||
const shouldShowSpeedUp = useShouldShowSpeedUp(
|
||||
transactionGroup,
|
||||
isEarliestNonce,
|
||||
@ -90,37 +116,6 @@ export default function TransactionListItem({
|
||||
setShowDetails((prev) => !prev);
|
||||
}, [isUnapproved, history, id]);
|
||||
|
||||
const cancelButton = useMemo(() => {
|
||||
const btn = (
|
||||
<Button
|
||||
onClick={cancelTransaction}
|
||||
rounded
|
||||
className="transaction-list-item__header-button"
|
||||
disabled={!hasEnoughCancelGas}
|
||||
>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
);
|
||||
if (hasCancelled || !isPending || isUnapproved) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return hasEnoughCancelGas ? (
|
||||
btn
|
||||
) : (
|
||||
<Tooltip title={t('notEnoughGas')} position="bottom">
|
||||
<div>{btn}</div>
|
||||
</Tooltip>
|
||||
);
|
||||
}, [
|
||||
isPending,
|
||||
t,
|
||||
isUnapproved,
|
||||
hasEnoughCancelGas,
|
||||
cancelTransaction,
|
||||
hasCancelled,
|
||||
]);
|
||||
|
||||
const speedUpButton = useMemo(() => {
|
||||
if (!shouldShowSpeedUp || !isPending || isUnapproved) {
|
||||
return null;
|
||||
@ -140,11 +135,13 @@ export default function TransactionListItem({
|
||||
isUnapproved,
|
||||
t,
|
||||
isPending,
|
||||
retryTransaction,
|
||||
hasCancelled,
|
||||
retryTransaction,
|
||||
cancelTransaction,
|
||||
]);
|
||||
|
||||
const showCancelButton = !hasCancelled && isPending && !isUnapproved;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListItem
|
||||
@ -194,7 +191,12 @@ export default function TransactionListItem({
|
||||
>
|
||||
<div className="transaction-list-item__pending-actions">
|
||||
{speedUpButton}
|
||||
{cancelButton}
|
||||
{showCancelButton && (
|
||||
<CancelButton
|
||||
transaction={transactionGroup.primaryTransaction}
|
||||
cancelTransaction={cancelTransaction}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</ListItem>
|
||||
{showDetails && (
|
||||
@ -211,35 +213,20 @@ export default function TransactionListItem({
|
||||
isEarliestNonce={isEarliestNonce}
|
||||
onCancel={cancelTransaction}
|
||||
showCancel={isPending && !hasCancelled}
|
||||
cancelDisabled={!hasEnoughCancelGas}
|
||||
/>
|
||||
)}
|
||||
{showRetryEditGasPopover && (
|
||||
<EditGasPopover
|
||||
onClose={closeRetryEditGasPopover}
|
||||
onClose={() => setShowRetryEditGasPopover(false)}
|
||||
mode={EDIT_GAS_MODES.SPEED_UP}
|
||||
transaction={{
|
||||
...transactionGroup.primaryTransaction,
|
||||
userFeeLevel: 'custom',
|
||||
txParams: {
|
||||
...transactionGroup.primaryTransaction?.txParams,
|
||||
...customRetryGasSettings,
|
||||
},
|
||||
}}
|
||||
transaction={transactionGroup.primaryTransaction}
|
||||
/>
|
||||
)}
|
||||
{showCancelEditGasPopover && (
|
||||
<EditGasPopover
|
||||
onClose={closeCancelEditGasPopover}
|
||||
onClose={() => setShowCancelEditGasPopover(false)}
|
||||
mode={EDIT_GAS_MODES.CANCEL}
|
||||
transaction={{
|
||||
...transactionGroup.primaryTransaction,
|
||||
userFeeLevel: 'custom',
|
||||
txParams: {
|
||||
...transactionGroup.primaryTransaction?.txParams,
|
||||
...customCancelGasSettings,
|
||||
},
|
||||
}}
|
||||
transaction={transactionGroup.primaryTransaction}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
@ -0,0 +1,139 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import transactionGroup from '../../../../test/data/mock-pending-transaction-data.json';
|
||||
import {
|
||||
getConversionRate,
|
||||
getSelectedAccount,
|
||||
getTokenExchangeRates,
|
||||
getPreferences,
|
||||
getShouldShowFiat,
|
||||
} from '../../../selectors';
|
||||
import {
|
||||
renderWithProvider,
|
||||
setBackgroundConnection,
|
||||
} from '../../../../test/jest';
|
||||
|
||||
import { useGasFeeEstimates } from '../../../hooks/useGasFeeEstimates';
|
||||
import { GAS_ESTIMATE_TYPES } from '../../../../shared/constants/gas';
|
||||
import TransactionListItem from './transaction-list-item.component';
|
||||
|
||||
const FEE_MARKET_ESTIMATE_RETURN_VALUE = {
|
||||
gasEstimateType: GAS_ESTIMATE_TYPES.FEE_MARKET,
|
||||
gasFeeEstimates: {
|
||||
low: {
|
||||
minWaitTimeEstimate: 180000,
|
||||
maxWaitTimeEstimate: 300000,
|
||||
suggestedMaxPriorityFeePerGas: '3',
|
||||
suggestedMaxFeePerGas: '53',
|
||||
},
|
||||
medium: {
|
||||
minWaitTimeEstimate: 15000,
|
||||
maxWaitTimeEstimate: 60000,
|
||||
suggestedMaxPriorityFeePerGas: '7',
|
||||
suggestedMaxFeePerGas: '70',
|
||||
},
|
||||
high: {
|
||||
minWaitTimeEstimate: 0,
|
||||
maxWaitTimeEstimate: 15000,
|
||||
suggestedMaxPriorityFeePerGas: '10',
|
||||
suggestedMaxFeePerGas: '100',
|
||||
},
|
||||
estimatedBaseFee: '50',
|
||||
},
|
||||
estimatedGasFeeTimeBounds: {},
|
||||
};
|
||||
|
||||
jest.mock('react-redux', () => {
|
||||
const actual = jest.requireActual('react-redux');
|
||||
|
||||
return {
|
||||
...actual,
|
||||
useSelector: jest.fn(),
|
||||
useDispatch: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../../hooks/useGasFeeEstimates', () => ({
|
||||
useGasFeeEstimates: jest.fn(),
|
||||
}));
|
||||
|
||||
setBackgroundConnection({
|
||||
getGasFeeTimeEstimate: jest.fn(),
|
||||
getGasFeeEstimatesAndStartPolling: jest.fn(),
|
||||
});
|
||||
|
||||
const generateUseSelectorRouter = (opts) => (selector) => {
|
||||
if (selector === getConversionRate) {
|
||||
return 1;
|
||||
} else if (selector === getSelectedAccount) {
|
||||
return {
|
||||
balance: opts.balance ?? '2AA1EFB94E0000',
|
||||
};
|
||||
} else if (selector === getTokenExchangeRates) {
|
||||
return opts.tokenExchangeRates ?? {};
|
||||
} else if (selector === getPreferences) {
|
||||
return (
|
||||
opts.preferences ?? {
|
||||
useNativeCurrencyAsPrimaryCurrency: true,
|
||||
}
|
||||
);
|
||||
} else if (selector === getShouldShowFiat) {
|
||||
return opts.shouldShowFiat ?? false;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
describe('TransactionListItem', () => {
|
||||
describe('when account has insufficient balance to cover gas', function () {
|
||||
beforeAll(function () {
|
||||
useGasFeeEstimates.mockImplementation(
|
||||
() => FEE_MARKET_ESTIMATE_RETURN_VALUE,
|
||||
);
|
||||
});
|
||||
|
||||
afterAll(function () {
|
||||
useGasFeeEstimates.restore();
|
||||
});
|
||||
|
||||
it(`should indicate account has insufficient funds to cover gas price for cancellation of pending transaction`, function () {
|
||||
useSelector.mockImplementation(
|
||||
generateUseSelectorRouter({
|
||||
balance: '0x3',
|
||||
}),
|
||||
);
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<TransactionListItem transactionGroup={transactionGroup} />,
|
||||
);
|
||||
expect(queryByTestId('not-enough-gas__tooltip')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not disable "cancel" button when user has sufficient funds', function () {
|
||||
useSelector.mockImplementation(
|
||||
generateUseSelectorRouter({
|
||||
balance: '2AA1EFB94E0000',
|
||||
}),
|
||||
);
|
||||
const { queryByTestId } = renderWithProvider(
|
||||
<TransactionListItem transactionGroup={transactionGroup} />,
|
||||
);
|
||||
expect(queryByTestId('not-enough-gas__tooltip')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it(`should open the edit gas popover when cancel is clicked`, function () {
|
||||
useSelector.mockImplementation(
|
||||
generateUseSelectorRouter({
|
||||
balance: '2AA1EFB94E0000',
|
||||
}),
|
||||
);
|
||||
const { getByText, queryByText } = renderWithProvider(
|
||||
<TransactionListItem transactionGroup={transactionGroup} />,
|
||||
);
|
||||
expect(queryByText('Cancel transaction')).not.toBeInTheDocument();
|
||||
|
||||
const cancelButton = getByText('Cancel');
|
||||
fireEvent.click(cancelButton);
|
||||
expect(getByText('Cancel transaction')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
@ -1,55 +0,0 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { isBalanceSufficient } from '../pages/send/send.utils';
|
||||
import { getSelectedAccount } from '../selectors';
|
||||
import { getConversionRate } from '../ducks/metamask/metamask';
|
||||
|
||||
import { getMaximumGasTotalInHexWei } from '../../shared/modules/gas.utils';
|
||||
import { useIncrementedGasFees } from './useIncrementedGasFees';
|
||||
|
||||
/**
|
||||
* Determine whether a transaction can be cancelled and provide a method to
|
||||
* kick off the process of cancellation.
|
||||
*
|
||||
* Provides a reusable hook that, given a transactionGroup, will return
|
||||
* whether or not the account has enough funds to cover the gas cancellation
|
||||
* fee, and a method for beginning the cancellation process
|
||||
* @param {Object} transactionGroup
|
||||
* @return {[boolean, Function]}
|
||||
*/
|
||||
export function useCancelTransaction(transactionGroup) {
|
||||
const { primaryTransaction } = transactionGroup;
|
||||
|
||||
const customCancelGasSettings = useIncrementedGasFees(transactionGroup);
|
||||
|
||||
const selectedAccount = useSelector(getSelectedAccount);
|
||||
const conversionRate = useSelector(getConversionRate);
|
||||
|
||||
const [showCancelEditGasPopover, setShowCancelEditGasPopover] = useState(
|
||||
false,
|
||||
);
|
||||
|
||||
const closeCancelEditGasPopover = () => setShowCancelEditGasPopover(false);
|
||||
|
||||
const cancelTransaction = useCallback((event) => {
|
||||
event.stopPropagation();
|
||||
return setShowCancelEditGasPopover(true);
|
||||
}, []);
|
||||
|
||||
const hasEnoughCancelGas =
|
||||
primaryTransaction.txParams &&
|
||||
isBalanceSufficient({
|
||||
amount: '0x0',
|
||||
gasTotal: getMaximumGasTotalInHexWei(customCancelGasSettings),
|
||||
balance: selectedAccount.balance,
|
||||
conversionRate,
|
||||
});
|
||||
|
||||
return {
|
||||
hasEnoughCancelGas,
|
||||
customCancelGasSettings,
|
||||
cancelTransaction,
|
||||
showCancelEditGasPopover,
|
||||
closeCancelEditGasPopover,
|
||||
};
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
import * as reactRedux from 'react-redux';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import sinon from 'sinon';
|
||||
import transactions from '../../test/data/transaction-data.json';
|
||||
import { getConversionRate, getSelectedAccount } from '../selectors';
|
||||
import { increaseLastGasPrice } from '../helpers/utils/confirm-tx.util';
|
||||
import { useCancelTransaction } from './useCancelTransaction';
|
||||
|
||||
jest.mock('../store/actions', () => ({
|
||||
disconnectGasFeeEstimatePoller: jest.fn(),
|
||||
getGasFeeEstimatesAndStartPolling: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve()),
|
||||
addPollingTokenToAppState: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('useCancelTransaction', function () {
|
||||
let useSelector;
|
||||
const dispatch = sinon.spy();
|
||||
|
||||
beforeAll(function () {
|
||||
sinon.stub(reactRedux, 'useDispatch').returns(dispatch);
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
dispatch.resetHistory();
|
||||
});
|
||||
|
||||
afterAll(function () {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
describe('when account has insufficient balance to cover gas', function () {
|
||||
beforeAll(function () {
|
||||
useSelector = sinon.stub(reactRedux, 'useSelector');
|
||||
useSelector.callsFake((selector) => {
|
||||
if (selector === getConversionRate) {
|
||||
return 280.46;
|
||||
} else if (selector === getSelectedAccount) {
|
||||
return {
|
||||
balance: '0x3',
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
});
|
||||
afterAll(function () {
|
||||
useSelector.restore();
|
||||
});
|
||||
transactions.forEach((transactionGroup) => {
|
||||
const originalGasPrice =
|
||||
transactionGroup.primaryTransaction.txParams?.gasPrice;
|
||||
const gasPrice =
|
||||
originalGasPrice && increaseLastGasPrice(originalGasPrice);
|
||||
const transactionId = transactionGroup.initialTransaction.id;
|
||||
it(`should indicate account has insufficient funds to cover ${gasPrice} gas price`, function () {
|
||||
const { result } = renderHook(() =>
|
||||
useCancelTransaction(transactionGroup),
|
||||
);
|
||||
expect(result.current.hasEnoughCancelGas).toStrictEqual(false);
|
||||
});
|
||||
it(`should return a function that kicks off cancellation for id ${transactionId}`, function () {
|
||||
const { result } = renderHook(() =>
|
||||
useCancelTransaction(transactionGroup),
|
||||
);
|
||||
expect(typeof result.current.cancelTransaction).toStrictEqual(
|
||||
'function',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when account has sufficient balance to cover gas', function () {
|
||||
beforeAll(function () {
|
||||
useSelector = sinon.stub(reactRedux, 'useSelector');
|
||||
useSelector.callsFake((selector) => {
|
||||
if (selector === getConversionRate) {
|
||||
return 280.46;
|
||||
} else if (selector === getSelectedAccount) {
|
||||
return {
|
||||
balance: '0x9C2007651B2500000',
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(function () {
|
||||
useSelector.restore();
|
||||
});
|
||||
|
||||
transactions.forEach((transactionGroup) => {
|
||||
const originalGasPrice =
|
||||
transactionGroup.primaryTransaction.txParams?.gasPrice;
|
||||
const gasPrice =
|
||||
originalGasPrice && increaseLastGasPrice(originalGasPrice);
|
||||
const transactionId = transactionGroup.initialTransaction.id;
|
||||
it(`should indicate account has funds to cover ${gasPrice} gas price`, function () {
|
||||
const { result } = renderHook(() =>
|
||||
useCancelTransaction(transactionGroup),
|
||||
);
|
||||
expect(result.current.hasEnoughCancelGas).toStrictEqual(true);
|
||||
});
|
||||
it(`should return a function that opens the gas popover onsubmit kicks off cancellation for id ${transactionId}`, function () {
|
||||
const { result } = renderHook(() =>
|
||||
useCancelTransaction(transactionGroup),
|
||||
);
|
||||
expect(typeof result.current.cancelTransaction).toStrictEqual(
|
||||
'function',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -50,14 +50,12 @@ function getHighestIncrementedFee(originalFee, currentEstimate) {
|
||||
* discarded by the network to avoid DoS attacks. This hook returns an object
|
||||
* that either has gasPrice or maxFeePerGas/maxPriorityFeePerGas specified. In
|
||||
* addition the gasLimit will also be included.
|
||||
* @param {} transactionGroup
|
||||
* @param {} transaction
|
||||
* @returns {import(
|
||||
* '../../app/scripts/controllers/transactions'
|
||||
* ).CustomGasSettings} - Gas settings for cancellations/speed ups
|
||||
*/
|
||||
export function useIncrementedGasFees(transactionGroup) {
|
||||
const { primaryTransaction } = transactionGroup;
|
||||
|
||||
export function useIncrementedGasFees(transaction) {
|
||||
const { gasFeeEstimates = {} } = useGasFeeEstimates();
|
||||
|
||||
// We memoize this value so that it can be relied upon in other hooks.
|
||||
@ -68,8 +66,8 @@ export function useIncrementedGasFees(transactionGroup) {
|
||||
// do not have txParams. This is why we use optional chaining on the
|
||||
// txParams object in this hook.
|
||||
const temporaryGasSettings = {
|
||||
gasLimit: primaryTransaction.txParams?.gas,
|
||||
gas: primaryTransaction.txParams?.gas,
|
||||
gasLimit: transaction.txParams?.gas,
|
||||
gas: transaction.txParams?.gas,
|
||||
};
|
||||
|
||||
const suggestedMaxFeePerGas =
|
||||
@ -77,10 +75,10 @@ export function useIncrementedGasFees(transactionGroup) {
|
||||
const suggestedMaxPriorityFeePerGas =
|
||||
gasFeeEstimates?.medium?.suggestedMaxPriorityFeePerGas ?? '0';
|
||||
|
||||
if (isEIP1559Transaction(primaryTransaction)) {
|
||||
const transactionMaxFeePerGas = primaryTransaction.txParams?.maxFeePerGas;
|
||||
if (isEIP1559Transaction(transaction)) {
|
||||
const transactionMaxFeePerGas = transaction.txParams?.maxFeePerGas;
|
||||
const transactionMaxPriorityFeePerGas =
|
||||
primaryTransaction.txParams?.maxPriorityFeePerGas;
|
||||
transaction.txParams?.maxPriorityFeePerGas;
|
||||
|
||||
temporaryGasSettings.maxFeePerGas =
|
||||
transactionMaxFeePerGas === undefined ||
|
||||
@ -99,7 +97,7 @@ export function useIncrementedGasFees(transactionGroup) {
|
||||
suggestedMaxPriorityFeePerGas,
|
||||
);
|
||||
} else {
|
||||
const transactionGasPrice = primaryTransaction.txParams?.gasPrice;
|
||||
const transactionGasPrice = transaction.txParams?.gasPrice;
|
||||
temporaryGasSettings.gasPrice =
|
||||
transactionGasPrice === undefined || transactionGasPrice.startsWith('-')
|
||||
? '0x0'
|
||||
@ -109,7 +107,7 @@ export function useIncrementedGasFees(transactionGroup) {
|
||||
);
|
||||
}
|
||||
return temporaryGasSettings;
|
||||
}, [primaryTransaction, gasFeeEstimates]);
|
||||
}, [transaction, gasFeeEstimates]);
|
||||
|
||||
return customGasSettings;
|
||||
}
|
||||
|
@ -1,48 +0,0 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useMetricEvent } from './useMetricEvent';
|
||||
import { useIncrementedGasFees } from './useIncrementedGasFees';
|
||||
|
||||
/**
|
||||
* @typedef {Object} RetryTransactionReturnValue
|
||||
* @property {(event: Event) => void} retryTransaction - open edit gas popover
|
||||
* to begin setting retry gas fees
|
||||
* @property {boolean} showRetryEditGasPopover - Whether to show the popover
|
||||
* @property {() => void} closeRetryEditGasPopover - close the popover.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides a reusable hook that, given a transactionGroup, will return
|
||||
* a method for beginning the retry process
|
||||
* @param {Object} transactionGroup - the transaction group
|
||||
* @return {RetryTransactionReturnValue}
|
||||
*/
|
||||
|
||||
export function useRetryTransaction(transactionGroup) {
|
||||
const customRetryGasSettings = useIncrementedGasFees(transactionGroup);
|
||||
const trackMetricsEvent = useMetricEvent({
|
||||
eventOpts: {
|
||||
category: 'Navigation',
|
||||
action: 'Activity Log',
|
||||
name: 'Clicked "Speed Up"',
|
||||
},
|
||||
});
|
||||
const [showRetryEditGasPopover, setShowRetryEditGasPopover] = useState(false);
|
||||
|
||||
const closeRetryEditGasPopover = () => setShowRetryEditGasPopover(false);
|
||||
|
||||
const retryTransaction = useCallback(
|
||||
async (event) => {
|
||||
event.stopPropagation();
|
||||
setShowRetryEditGasPopover(true);
|
||||
trackMetricsEvent();
|
||||
},
|
||||
[trackMetricsEvent],
|
||||
);
|
||||
|
||||
return {
|
||||
retryTransaction,
|
||||
showRetryEditGasPopover,
|
||||
closeRetryEditGasPopover,
|
||||
customRetryGasSettings,
|
||||
};
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
import * as reactRedux from 'react-redux';
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
import sinon from 'sinon';
|
||||
import transactions from '../../test/data/transaction-data.json';
|
||||
import { getIsMainnet } from '../selectors';
|
||||
import * as methodDataHook from './useMethodData';
|
||||
import * as metricEventHook from './useMetricEvent';
|
||||
import { useRetryTransaction } from './useRetryTransaction';
|
||||
|
||||
jest.mock('./useGasFeeEstimates', () => ({
|
||||
useGasFeeEstimates: jest.fn().mockImplementation(() => Promise.resolve({})),
|
||||
}));
|
||||
|
||||
describe('useRetryTransaction', () => {
|
||||
describe('when transaction meets retry enabled criteria', () => {
|
||||
let useSelector;
|
||||
const dispatch = sinon.spy(() => Promise.resolve({ blockTime: 0 }));
|
||||
const trackEvent = sinon.spy();
|
||||
const event = {
|
||||
preventDefault: () => undefined,
|
||||
stopPropagation: () => undefined,
|
||||
};
|
||||
beforeAll(() => {
|
||||
sinon.stub(reactRedux, 'useDispatch').returns(dispatch);
|
||||
sinon.stub(methodDataHook, 'useMethodData').returns({});
|
||||
sinon.stub(metricEventHook, 'useMetricEvent').returns(trackEvent);
|
||||
useSelector = sinon.stub(reactRedux, 'useSelector');
|
||||
useSelector.callsFake((selector) => {
|
||||
if (selector === getIsMainnet) {
|
||||
return true;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
dispatch.resetHistory();
|
||||
trackEvent.resetHistory();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
const retryEnabledTransaction = {
|
||||
...transactions[0],
|
||||
transactions: [
|
||||
{
|
||||
submittedTime: new Date() - 5001,
|
||||
},
|
||||
],
|
||||
hasRetried: false,
|
||||
};
|
||||
|
||||
it('retryTransaction function should track metrics', () => {
|
||||
const { result } = renderHook(() =>
|
||||
useRetryTransaction(retryEnabledTransaction, true),
|
||||
);
|
||||
const { retryTransaction } = result.current;
|
||||
act(() => {
|
||||
retryTransaction(event);
|
||||
});
|
||||
expect(trackEvent.calledOnce).toStrictEqual(true);
|
||||
});
|
||||
|
||||
it('retryTransaction function should show retry popover', async () => {
|
||||
const { result } = renderHook(() =>
|
||||
useRetryTransaction(retryEnabledTransaction, true),
|
||||
);
|
||||
const { retryTransaction } = result.current;
|
||||
await act(async () => {
|
||||
await retryTransaction(event);
|
||||
});
|
||||
expect(result.current.showRetryEditGasPopover).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user