From 0842f9c3c797cd19e84ca9bc0d445717dc4ccb9c Mon Sep 17 00:00:00 2001 From: nikoferro Date: Thu, 18 Jul 2024 11:40:09 +0200 Subject: [PATCH 01/32] chore: updating dependencies to match mobile current versions --- package.json | 19 ++-- src/SwapsController.test.ts | 3 +- yarn.lock | 177 +++++++++++++++++++++++++++++++----- 3 files changed, 168 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index b38b37a8..d7dc7a22 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ }, "peerDependencies": { "@metamask/composable-controller": "^4.0.0", - "@metamask/network-controller": "^3.0.0" + "@metamask/network-controller": "^18.1.0" }, "devDependencies": { "@babel/runtime": "^7.0.0", @@ -45,7 +45,6 @@ "@metamask/eslint-config-nodejs": "^12.1.0", "@metamask/eslint-config-typescript": "^12.1.0", "@metamask/approval-controller": "^5.1.1", - "@metamask/network-controller": "^17.1.0", "@metamask/transaction-controller": "^19.0.1", "@types/jest": "^26.0.22", "@types/node": "^20.10.4", @@ -70,13 +69,12 @@ "typescript": "^5.1.0" }, "dependencies": { - "@metamask/base-controller": "^4.1.1", - "@metamask/controller-utils": "^8.0.2", + "@metamask/base-controller": "^5.0.0", + "@metamask/controller-utils": "^10.0.0", "@metamask/eth-query": "^4.0.0", - "@metamask/gas-fee-controller": "^12.0.0", + "@metamask/gas-fee-controller": "^15.1.2", "@metamask/utils": "^8.3.0", - "abort-controller": "^3.0.0", - "async-mutex": "^0.4.1", + "async-mutex": "^0.5.0", "bignumber.js": "^9.0.1", "bn.js": "^5.2.1", "human-standard-token-abi": "^2.0.0", @@ -90,7 +88,10 @@ "@metamask/transaction-controller>babel-runtime>core-js": false, "babel-runtime>core-js": false, "@metamask/controller-utils>ethereumjs-util>ethereum-cryptography>keccak": false, - "@metamask/controller-utils>ethereumjs-util>ethereum-cryptography>secp256k1": false + "@metamask/controller-utils>ethereumjs-util>ethereum-cryptography>secp256k1": false, + "@metamask/transaction-controller>ethereumjs-util>ethereum-cryptography>keccak": false, + "@metamask/transaction-controller>ethereumjs-util>ethereum-cryptography>secp256k1": false } - } + }, + "packageManager": "yarn@1.22.22+sha256.c17d3797fb9a9115bf375e31bfd30058cac6bc9c3b8807a3d8cb2094794b51ca" } diff --git a/src/SwapsController.test.ts b/src/SwapsController.test.ts index a3e2d117..f2269424 100644 --- a/src/SwapsController.test.ts +++ b/src/SwapsController.test.ts @@ -4,6 +4,7 @@ import SwapsController, { INITIAL_CHAIN_DATA } from './SwapsController'; import * as swapsUtil from './swapsUtil'; import { Quote } from './swapsInterfaces'; import BigNumber from 'bignumber.js'; +import { BaseControllerV1 } from '@metamask/base-controller'; const POLL_COUNT_LIMIT = 3; @@ -178,7 +179,7 @@ describe('SwapsController', () => { pollCountLimit: POLL_COUNT_LIMIT, }, ); - new ComposableController([swapsController]); + new ComposableController([swapsController as BaseControllerV1]); swapsUtilFetchTokens = jest .spyOn(swapsUtil, 'fetchTokens') diff --git a/yarn.lock b/yarn.lock index 21323790..8f8c3511 100644 --- a/yarn.lock +++ b/yarn.lock @@ -968,6 +968,14 @@ "@metamask/utils" "^8.3.0" immer "^9.0.6" +"@metamask/base-controller@^5.0.0", "@metamask/base-controller@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@metamask/base-controller/-/base-controller-5.0.2.tgz#ab3584f67d9f2ff80958df21558e61650074e565" + integrity sha512-izOaXXnLz9OXbdika0ZvIDf24pgsWNPI02Lm0E4eMU61ICpV78bzQB7YyIbMtF6MWnItw1RnX9jN6zNEmp5pdA== + dependencies: + "@metamask/utils" "^8.3.0" + immer "^9.0.6" + "@metamask/composable-controller@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@metamask/composable-controller/-/composable-controller-4.0.0.tgz#ea504abb358e26b1c9791bace3b0d6a07158ce41" @@ -975,7 +983,22 @@ dependencies: "@metamask/base-controller" "^4.0.0" -"@metamask/controller-utils@^8.0.1", "@metamask/controller-utils@^8.0.2", "@metamask/controller-utils@^8.0.4": +"@metamask/controller-utils@^10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-10.0.0.tgz#08f6576c1533ab919b1b15b640146e9598f732ea" + integrity sha512-vO6lwIr3VSkkR/A9VCzxcpgLJhzgMvUvaAU9SF8ulXIhRIh3Eur4VDcXtcKNGNB8oTZcKbKJrsmAJCVfPZQ+zQ== + dependencies: + "@ethereumjs/util" "^8.1.0" + "@metamask/eth-query" "^4.0.0" + "@metamask/ethjs-unit" "^0.3.0" + "@metamask/utils" "^8.3.0" + "@spruceid/siwe-parser" "2.1.0" + "@types/bn.js" "^5.1.5" + bn.js "^5.2.1" + eth-ens-namehash "^2.0.8" + fast-deep-equal "^3.1.3" + +"@metamask/controller-utils@^8.0.1", "@metamask/controller-utils@^8.0.4": version "8.0.4" resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-8.0.4.tgz#78a952301ff4b2a501b31865ab0de434c6ea3cd2" integrity sha512-R0+Q5ROnXKTtxAmiCH4TYHkGfbZTT8qzLeycJQVeJHXhpeYlAPxjs8m5fy1jwW1tX4r0MDWShx9iNUmHZS41jw== @@ -988,6 +1011,21 @@ eth-ens-namehash "^2.0.8" fast-deep-equal "^3.1.3" +"@metamask/controller-utils@^9.1.0": + version "9.1.0" + resolved "https://registry.yarnpkg.com/@metamask/controller-utils/-/controller-utils-9.1.0.tgz#436ff37d339df3f4b0f31458881c6f1b1002c945" + integrity sha512-17XQhyhR1bC7NjQHJF2KhxStVeoFW8liQ/Z526cI3uVcKOgYRxxDwBiRGs+xzv9XAm7f1W73W83wnb8fcBxlxg== + dependencies: + "@ethereumjs/util" "^8.1.0" + "@metamask/eth-query" "^4.0.0" + "@metamask/ethjs-unit" "^0.3.0" + "@metamask/utils" "^8.3.0" + "@spruceid/siwe-parser" "2.1.0" + "@types/bn.js" "^5.1.5" + bn.js "^5.2.1" + eth-ens-namehash "^2.0.8" + fast-deep-equal "^3.1.3" + "@metamask/eslint-config-jest@^12.1.0": version "12.1.0" resolved "https://registry.yarnpkg.com/@metamask/eslint-config-jest/-/eslint-config-jest-12.1.0.tgz#4218dff6f763e7f3bb6b29b4b50fb7d55014b500" @@ -1008,7 +1046,18 @@ resolved "https://registry.yarnpkg.com/@metamask/eslint-config/-/eslint-config-12.2.0.tgz#6cefc8331e4a34d26ae951882437371ecfe4e3c4" integrity sha512-BurYsht8MKdhvW2itUPPF8NkAhYtDdsCGHTSY7EzVvlmGP4jc9XrRZyfNwlt0zhB6MCMjHB1uNWwchtX7vBFjw== -"@metamask/eth-json-rpc-infura@^9.0.0": +"@metamask/eth-block-tracker@^9.0.2", "@metamask/eth-block-tracker@^9.0.3": + version "9.0.3" + resolved "https://registry.yarnpkg.com/@metamask/eth-block-tracker/-/eth-block-tracker-9.0.3.tgz#ceebe9bc720a54c3a4749fc09c8f8bb9fe206255" + integrity sha512-I29IwdhnSJtk/A05vFsiOV92ADXXtBymXfC0naSvHP9TYNNOryG9y2l0jmnSzBsP3+wefCkHJWEzEwF4YSKogw== + dependencies: + "@metamask/eth-json-rpc-provider" "^3.0.2" + "@metamask/safe-event-emitter" "^3.0.0" + "@metamask/utils" "^8.1.0" + json-rpc-random-id "^1.0.1" + pify "^5.0.0" + +"@metamask/eth-json-rpc-infura@^9.0.0", "@metamask/eth-json-rpc-infura@^9.1.0": version "9.1.0" resolved "https://registry.yarnpkg.com/@metamask/eth-json-rpc-infura/-/eth-json-rpc-infura-9.1.0.tgz#8e09588ed58f49058615cab7040dcbce4682a292" integrity sha512-47x7evivl5XUsTsRoF9t27guCXgmfsbQq+pjHHFf87WoISGsgua6wVr91b1iVCv8MzQqupJBewtnG8AzWpwEEQ== @@ -1034,6 +1083,21 @@ pify "^5.0.0" safe-stable-stringify "^2.4.3" +"@metamask/eth-json-rpc-middleware@^12.1.1": + version "12.1.2" + resolved "https://registry.yarnpkg.com/@metamask/eth-json-rpc-middleware/-/eth-json-rpc-middleware-12.1.2.tgz#41b6cfe232cb4e5fdfed24031f606398b4387ce4" + integrity sha512-qhaUq0SenE8P916yuYDj5dbdGRvl/qJDsjPGSlSJOi0QBASFDbKpo5k1st+ban6duJfRUQhrwzERCVyNbtil7w== + dependencies: + "@metamask/eth-block-tracker" "^9.0.3" + "@metamask/eth-json-rpc-provider" "^3.0.2" + "@metamask/eth-sig-util" "^7.0.0" + "@metamask/json-rpc-engine" "^8.0.2" + "@metamask/rpc-errors" "^6.0.0" + "@metamask/utils" "^8.1.0" + klona "^2.0.6" + pify "^5.0.0" + safe-stable-stringify "^2.4.3" + "@metamask/eth-json-rpc-provider@^2.1.0", "@metamask/eth-json-rpc-provider@^2.3.2": version "2.3.2" resolved "https://registry.yarnpkg.com/@metamask/eth-json-rpc-provider/-/eth-json-rpc-provider-2.3.2.tgz#39a3ec6cdf82b6f2ce764ebfd9ff78997a2aa608" @@ -1043,6 +1107,15 @@ "@metamask/safe-event-emitter" "^3.0.0" "@metamask/utils" "^8.3.0" +"@metamask/eth-json-rpc-provider@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@metamask/eth-json-rpc-provider/-/eth-json-rpc-provider-3.0.2.tgz#42e544d227285fe56336e2301961a6283dcfadad" + integrity sha512-ma5bYjKa71bSw5+iibEnIiY25s8wkDnTljrqOnGw5MkTEU4PQDiKnK9YjxfUZSasx2BPEsp1OW2NS+pnrRMO4Q== + dependencies: + "@metamask/json-rpc-engine" "^8.0.2" + "@metamask/safe-event-emitter" "^3.0.0" + "@metamask/utils" "^8.3.0" + "@metamask/eth-query@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@metamask/eth-query/-/eth-query-4.0.0.tgz#a8c1651b69e298da58628b1c09d31dd504a939b3" @@ -1172,6 +1245,23 @@ ethereumjs-util "^7.0.10" uuid "^8.3.2" +"@metamask/gas-fee-controller@^15.1.2": + version "15.1.2" + resolved "https://registry.yarnpkg.com/@metamask/gas-fee-controller/-/gas-fee-controller-15.1.2.tgz#cb8d85906efa1ff42a159ec023287ff31a63e45b" + integrity sha512-9knWAuhWvz0XKLvjImgpjbF1umCs6gNG70dc09VvWlp9BNowrniIEDRzo7bcDGvAectE2Vp0rxaohgqY8c5AOA== + dependencies: + "@metamask/base-controller" "^5.0.2" + "@metamask/controller-utils" "^9.1.0" + "@metamask/eth-query" "^4.0.0" + "@metamask/ethjs-unit" "^0.3.0" + "@metamask/network-controller" "^18.1.0" + "@metamask/polling-controller" "^6.0.2" + "@metamask/utils" "^8.3.0" + "@types/bn.js" "^5.1.5" + "@types/uuid" "^8.3.0" + bn.js "^5.2.1" + uuid "^8.3.2" + "@metamask/json-rpc-engine@^7.1.1", "@metamask/json-rpc-engine@^7.3.2", "@metamask/json-rpc-engine@^7.3.3": version "7.3.3" resolved "https://registry.yarnpkg.com/@metamask/json-rpc-engine/-/json-rpc-engine-7.3.3.tgz#f2b30a2164558014bfcca45db10f5af291d989af" @@ -1181,6 +1271,15 @@ "@metamask/safe-event-emitter" "^3.0.0" "@metamask/utils" "^8.3.0" +"@metamask/json-rpc-engine@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@metamask/json-rpc-engine/-/json-rpc-engine-8.0.2.tgz#29510a871a8edef892f838ee854db18de0bf0d14" + integrity sha512-IoQPmql8q7ABLruW7i4EYVHWUbF74yrp63bRuXV5Zf9BQwcn5H9Ww1eLtROYvI1bUXwOiHZ6qT5CWTrDc/t/AA== + dependencies: + "@metamask/rpc-errors" "^6.2.1" + "@metamask/safe-event-emitter" "^3.0.0" + "@metamask/utils" "^8.3.0" + "@metamask/metamask-eth-abis@^3.0.0": version "3.1.1" resolved "https://registry.yarnpkg.com/@metamask/metamask-eth-abis/-/metamask-eth-abis-3.1.1.tgz#3de904511115c488809ddee25c470332c03db531" @@ -1206,6 +1305,26 @@ immer "^9.0.6" uuid "^8.3.2" +"@metamask/network-controller@^18.1.0": + version "18.1.3" + resolved "https://registry.yarnpkg.com/@metamask/network-controller/-/network-controller-18.1.3.tgz#0aa7dbaf06c7ccf1f381a151d13850eb32d197b0" + integrity sha512-B79qGwhdNcmGtYOQWMZXKVSt88dowyP4Nf979QEX0opYe6Z4eZMZnGBezdl74cAcezEiDE1ro6X8UahB11IOTg== + dependencies: + "@metamask/base-controller" "^5.0.2" + "@metamask/controller-utils" "^10.0.0" + "@metamask/eth-block-tracker" "^9.0.2" + "@metamask/eth-json-rpc-infura" "^9.1.0" + "@metamask/eth-json-rpc-middleware" "^12.1.1" + "@metamask/eth-json-rpc-provider" "^3.0.2" + "@metamask/eth-query" "^4.0.0" + "@metamask/json-rpc-engine" "^8.0.2" + "@metamask/rpc-errors" "^6.2.1" + "@metamask/swappable-obj-proxy" "^2.2.0" + "@metamask/utils" "^8.3.0" + async-mutex "^0.5.0" + immer "^9.0.6" + uuid "^8.3.2" + "@metamask/number-to-bn@^1.7.1": version "1.7.1" resolved "https://registry.yarnpkg.com/@metamask/number-to-bn/-/number-to-bn-1.7.1.tgz#a449ec8b2edba211e0dc3e1e0428ff2cc2bf7ab4" @@ -1227,6 +1346,19 @@ fast-json-stable-stringify "^2.1.0" uuid "^8.3.2" +"@metamask/polling-controller@^6.0.2": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@metamask/polling-controller/-/polling-controller-6.0.2.tgz#dbe3c7d6610729db0749d37ce0eebf846894bd0e" + integrity sha512-q8LsV9JhV+heyO+6IIOUyaT6SqQKbyZOopRvhFNcZa37yATiJbv7TqVxy3qynVgyddguKdCQQtnxlL49Lc7Q2Q== + dependencies: + "@metamask/base-controller" "^5.0.2" + "@metamask/controller-utils" "^9.1.0" + "@metamask/network-controller" "^18.1.0" + "@metamask/utils" "^8.3.0" + "@types/uuid" "^8.3.0" + fast-json-stable-stringify "^2.1.0" + uuid "^8.3.2" + "@metamask/rpc-errors@^6.0.0", "@metamask/rpc-errors@^6.1.0", "@metamask/rpc-errors@^6.2.1": version "6.2.1" resolved "https://registry.yarnpkg.com/@metamask/rpc-errors/-/rpc-errors-6.2.1.tgz#f5daf429ededa7cb83069dc621bd5738fe2a1d80" @@ -1297,7 +1429,7 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== -"@noble/hashes@^1.3.1": +"@noble/hashes@^1.1.2", "@noble/hashes@^1.3.1": version "1.4.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== @@ -1444,6 +1576,16 @@ dependencies: apg-js "^4.1.1" +"@spruceid/siwe-parser@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@spruceid/siwe-parser/-/siwe-parser-2.1.0.tgz#59859ccfd02403179bcf115d9e02a7dc953a0820" + integrity sha512-tFQwY2oQLa4qvHE6npKsVgVdVLQOCGP1zJM3yjZOHut43LqCwdSwitZndFLrJHZLpqru9FnmYHRakvsPvrI+qA== + dependencies: + "@noble/hashes" "^1.1.2" + apg-js "^4.1.1" + uri-js "^4.4.1" + valid-url "^1.0.9" + "@tootallnate/once@2": version "2.0.0" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" @@ -1502,7 +1644,7 @@ dependencies: "@babel/types" "^7.20.7" -"@types/bn.js@^5.1.0": +"@types/bn.js@^5.1.0", "@types/bn.js@^5.1.5": version "5.1.5" resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.5.tgz#2e0dacdcce2c0f16b905d20ff87aedbc6f7b4bf0" integrity sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A== @@ -1745,13 +1887,6 @@ abitype@0.7.1: resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.7.1.tgz#16db20abe67de80f6183cf75f3de1ff86453b745" integrity sha512-VBkRHTDZf9Myaek/dO3yMmOzB/y2s3Zo6nVU7yaw1G+TvCHAjwaJzNGN9yo4K5D8bU/VZXKP1EJpRhFr862PlQ== -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - acorn-globals@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-7.0.1.tgz#0dbf05c44fa7c94332914c02066d5beff62c40c3" @@ -1946,10 +2081,10 @@ async-mutex@^0.3.1: dependencies: tslib "^2.3.1" -async-mutex@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.4.1.tgz#bccf55b96f2baf8df90ed798cb5544a1f6ee4c2c" - integrity sha512-WfoBo4E/TbCX1G95XTjbWTE3X2XLG0m1Xbv2cwOtuPdyH9CZvnaA5nCt1ucjaKEgW2A5IF71hxrRhr83Je5xjA== +async-mutex@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.5.0.tgz#353c69a0b9e75250971a64ac203b0ebfddd75482" + integrity sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA== dependencies: tslib "^2.4.0" @@ -3123,11 +3258,6 @@ ethjs-schema@0.2.1: resolved "https://registry.yarnpkg.com/ethjs-schema/-/ethjs-schema-0.2.1.tgz#47e138920421453617069034684642e26bb310f4" integrity sha512-DXd8lwNrhT9sjsh/Vd2Z+4pfyGxhc0POVnLBUfwk5udtdoBzADyq+sK39dcb48+ZU+2VgtwHxtGWnLnCfmfW5g== -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - eventemitter3@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" @@ -5984,7 +6114,7 @@ update-browserslist-db@^1.0.13: escalade "^3.1.1" picocolors "^1.0.0" -uri-js@^4.2.2: +uri-js@^4.2.2, uri-js@^4.4.1: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== @@ -6039,6 +6169,11 @@ v8-to-istanbul@^9.0.1: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^2.0.0" +valid-url@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/valid-url/-/valid-url-1.0.9.tgz#1c14479b40f1397a75782f115e4086447433a200" + integrity sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA== + validate-npm-package-license@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" From 31d63a1c2f8597ef430823163a0c412832d856f9 Mon Sep 17 00:00:00 2001 From: nikoferro Date: Mon, 19 Aug 2024 16:12:30 +0200 Subject: [PATCH 02/32] feat: basecontrollerv2 refactor --- src/SwapsController.test.ts | 205 +++---- src/SwapsController.ts | 517 +++++++++--------- ...Interfaces.ts => SwapsController.types.ts} | 135 ++++- src/constants.ts | 66 ++- src/swapsUtil.test.ts | 17 +- src/swapsUtil.ts | 10 +- 6 files changed, 563 insertions(+), 387 deletions(-) rename src/{swapsInterfaces.ts => SwapsController.types.ts} (72%) diff --git a/src/SwapsController.test.ts b/src/SwapsController.test.ts index 644d3409..0a658b0b 100644 --- a/src/SwapsController.test.ts +++ b/src/SwapsController.test.ts @@ -1,18 +1,12 @@ -import { ComposableController } from '@metamask/composable-controller'; - +import { ChainId } from '@metamask/controller-utils'; +import { GasFeeEstimates } from '@metamask/gas-fee-controller'; import SwapsController, { INITIAL_CHAIN_DATA, isGasFeeStateEthGasPrice, isGasFeeStateLegacy, } from './SwapsController'; +import { Quote, SwapsControllerMessenger } from './SwapsController.types'; import * as swapsUtil from './swapsUtil'; -import { Quote } from './swapsInterfaces'; -import BigNumber from 'bignumber.js'; -import { BaseControllerV1 } from '@metamask/base-controller'; -import { GasFeeEstimates } from '@metamask/gas-fee-controller'; -import { ChainId } from '@metamask/controller-utils'; - -const POLL_COUNT_LIMIT = 3; const API_TRADES: { [key: string]: Quote; @@ -46,10 +40,10 @@ const API_TRADES: { gasMultiplier: 1.5, quoteRefreshSeconds: 60, savings: { - total: new BigNumber(0), - performance: new BigNumber(0), - fee: new BigNumber(0), - medianMetaMaskFee: new BigNumber(0), + total: '0', + performance: '0', + fee: '0', + medianMetaMaskFee: '0', }, gasEstimate: '100000', gasEstimateWithRefund: '90000', @@ -86,10 +80,10 @@ const API_TRADES: { gasMultiplier: 1.5, quoteRefreshSeconds: 60, savings: { - total: new BigNumber(0), - performance: new BigNumber(0), - fee: new BigNumber(0), - medianMetaMaskFee: new BigNumber(0), + total: '0', + performance: '0', + fee: '0', + medianMetaMaskFee: '0', }, gasEstimate: '100000', gasEstimateWithRefund: '90000', @@ -99,18 +93,18 @@ const API_TRADES: { }, }; -const mockFlags: { [key: string]: any } = { - estimateGas: null, -}; +// Create a single mock object +const messengerMock = { + call: jest.fn(), + registerActionHandler: jest.fn(), + registerInitialEventPayload: jest.fn(), + publish: jest.fn(), +} as unknown as jest.Mocked; jest.mock('@metamask/eth-query', () => jest.fn().mockImplementation(() => { return { estimateGas: (_transaction: any, callback: any) => { - if (mockFlags.estimateGas) { - callback(new Error(mockFlags.estimateGas)); - return; - } callback(undefined, '0x0'); }, gasPrice: (callback: any) => { @@ -174,18 +168,15 @@ describe('SwapsController', () => { gasEstimateType: 'none', })); - fetchGasFeeEstimates = jest.fn(); - swapsController = new SwapsController( { + messenger: messengerMock, + // TODO: Remove once GasFeeController exports this action type fetchGasFeeEstimates, fetchEstimatedMultiLayerL1Fee, }, - { - pollCountLimit: POLL_COUNT_LIMIT, - }, + swapsUtil.getDefaultSwapsControllerState(), ); - new ComposableController([swapsController as BaseControllerV1]); swapsUtilFetchTokens = jest .spyOn(swapsUtil, 'fetchTokens') @@ -218,8 +209,10 @@ describe('SwapsController', () => { }); it('should set default config', () => { - expect(swapsController.config).toStrictEqual(swapsController.defaultConfig); - expect(swapsController.config).toStrictEqual({ + expect(swapsController.state.config).toStrictEqual( + swapsUtil.getDefaultSwapsControllerState().config, + ); + expect(swapsController.state.config).toStrictEqual({ chainId: '0x1', supportedChainIds: ['0x1', '0x38', '0x539', '0x89', '0xa86a'], maxGasLimit: 2500000, @@ -233,7 +226,9 @@ describe('SwapsController', () => { }); it('should set default state', () => { - expect(swapsController.state).toStrictEqual(swapsController.defaultState); + expect(swapsController.state).toStrictEqual( + swapsUtil.getDefaultSwapsControllerState(), + ); expect(swapsController.state).toStrictEqual({ quotes: {}, quoteValues: {}, @@ -282,12 +277,24 @@ describe('SwapsController', () => { topAssets: null, }, }, + config: { + chainId: '0x1', + supportedChainIds: ['0x1', '0x38', '0x539', '0x89', '0xa86a'], + maxGasLimit: 2500000, + pollCountLimit: 3, + fetchAggregatorMetadataThreshold: 1000 * 60 * 60 * 24 * 15, + fetchTokensThreshold: 1000 * 60 * 60 * 24, + fetchTopAssetsThreshold: 1000 * 60 * 30, + provider: undefined, + clientId: undefined, + }, }); }); it('should set a default value for pollingCyclesLeft', () => { swapsController = new SwapsController( { + messenger: messengerMock, fetchGasFeeEstimates, fetchEstimatedMultiLayerL1Fee, }, @@ -299,17 +306,15 @@ describe('SwapsController', () => { it('should use INITIAL_CHAIN_DATA when chainCache does not have data for the chainId', () => { const chainId = ChainId.aurora; - // add to supportedChainIds swapsController.configure({ supportedChainIds: [chainId], }); - // clear chainCache - swapsController.update({ + // add to supportedChainIds, clear chainCache and set chainId + swapsController.__test__updateState({ chainCache: {}, }); - // set chainId swapsController.configure({ chainId }); const cachedData = swapsController.state.chainCache[chainId]; @@ -324,17 +329,17 @@ describe('SwapsController', () => { chainId: '0x1', rpcUrl: 'test', }; - expect(swapsController.defaultConfig.provider).toBeUndefined(); - swapsController.configure({ - provider, - }); - expect(swapsController.defaultConfig.provider.name).toBe(provider.name); + expect(swapsController.state.config.provider).toBeUndefined(); + + swapsController.configure({ provider }); + + expect(swapsController.state.config.provider.name).toBe(provider.name); }); }); describe('chain cache', () => { it('should update cache configuration', () => { - expect(swapsController.config).toMatchObject({ + expect(swapsController.state.config).toMatchObject({ fetchAggregatorMetadataThreshold: 1000 * 60 * 60 * 24 * 15, fetchTokensThreshold: 1000 * 60 * 60 * 24, fetchTopAssetsThreshold: 1000 * 60 * 30, @@ -346,7 +351,8 @@ describe('SwapsController', () => { fetchTopAssetsThreshold: 0, }); - expect(swapsController.config).toMatchObject({ + expect(swapsController.state.config).toMatchObject({ + ...swapsController.state.config, fetchAggregatorMetadataThreshold: 0, fetchTokensThreshold: 0, fetchTopAssetsThreshold: 0, @@ -354,14 +360,17 @@ describe('SwapsController', () => { }); it('should update chainId configuration', () => { + swapsController.configure({ + supportedChainIds: ['0x23', '0x24', '0x291'], + }); swapsController.configure({ chainId: '0x23' }); - expect(swapsController.config.chainId).toBe('0x23'); + expect(swapsController.state.config.chainId).toBe('0x23'); swapsController.configure({ chainId: '0x24' }); - expect(swapsController.config.chainId).toBe('0x24'); + expect(swapsController.state.config.chainId).toBe('0x24'); swapsController.configure({ chainId: '0x291' }); - expect(swapsController.config.chainId).toBe('0x291'); + expect(swapsController.state.config.chainId).toBe('0x291'); }); it('should create default cache for supported chainIds', () => { @@ -384,7 +393,7 @@ describe('SwapsController', () => { ); }); - it('should not create default cache for supported chainIds', () => { + it('should not create default cache for unsupported chainIds', () => { swapsController.configure({ chainId: '0x23' }); expect(swapsController.state.chainCache['0x23']).toBeUndefined(); @@ -396,6 +405,10 @@ describe('SwapsController', () => { }); it('should load existing cache for chainId', () => { + swapsController.configure({ + supportedChainIds: ['0x23', '0x24', '0x291'], + }); + const chainData23 = { ...INITIAL_CHAIN_DATA, tokensLastFetched: 231, @@ -415,7 +428,7 @@ describe('SwapsController', () => { aggregatorMetadataLastFetched: 2913, }; - swapsController.update({ + swapsController.__test__updateState({ chainCache: { '0x23': chainData23, '0x24': chainData24, @@ -442,7 +455,7 @@ describe('SwapsController', () => { describe('tokens cache', () => { it('should fetch tokens when no tokens in state', async () => { - swapsController.update({ + swapsController.__test__updateState({ tokens: [], }); await swapsController.fetchTokenWithCache(); @@ -450,7 +463,7 @@ describe('SwapsController', () => { }); it('should fetch tokens when last fetched is 0', async () => { - swapsController.update({ + swapsController.__test__updateState({ tokens: [], tokensLastFetched: 0, }); @@ -461,7 +474,7 @@ describe('SwapsController', () => { it('should fetch tokens when last fetched is over threshold', async () => { const threshold = 5000; swapsController.configure({ fetchTokensThreshold: threshold }); - swapsController.update({ + swapsController.__test__updateState({ tokens: [], tokensLastFetched: Date.now() - threshold - 1, }); @@ -470,7 +483,7 @@ describe('SwapsController', () => { }); it('should not fetch tokens when no threshold reached', async () => { - swapsController.update({ + swapsController.__test__updateState({ tokens: [], tokensLastFetched: Date.now(), }); @@ -479,7 +492,7 @@ describe('SwapsController', () => { }); it('should not fetch tokens when no threshold reached or tokens are available', async () => { - swapsController.update({ + swapsController.__test__updateState({ tokens: [], tokensLastFetched: Date.now(), }); @@ -493,7 +506,7 @@ describe('SwapsController', () => { }); const threshold = 5000; swapsController.configure({ fetchTokensThreshold: threshold }); - swapsController.update({ + swapsController.__test__updateState({ tokens: [], tokensLastFetched: Date.now() - threshold - 1, }); @@ -503,12 +516,12 @@ describe('SwapsController', () => { }); it('should not fetch tokens if chain id is not supported', async () => { - swapsController.configure({ - supportedChainIds: ['0x1'], + swapsController.__test__updateState({ + tokens: [], + tokensLastFetched: 0, }); - swapsController.state.tokens = []; - swapsController.state.tokensLastFetched = 0; - swapsController.configure({ chainId: '0x2' }); + swapsController.configure({ supportedChainIds: ['0x1'], chainId: '0x2' }); + await swapsController.fetchTokenWithCache(); expect(swapsUtilFetchTokens).not.toHaveBeenCalled(); }); @@ -516,7 +529,7 @@ describe('SwapsController', () => { describe('top assets cache', () => { it('should fetch top assets when no top assets in state', async () => { - swapsController.update({ + swapsController.__test__updateState({ topAssets: null, }); await swapsController.fetchTopAssetsWithCache(); @@ -524,7 +537,7 @@ describe('SwapsController', () => { }); it('should fetch top assets when last fetched is 0', async () => { - swapsController.update({ + swapsController.__test__updateState({ topAssets: [], topAssetsLastFetched: 0, }); @@ -535,7 +548,7 @@ describe('SwapsController', () => { it('should fetch top assets when last fetched is over threshold', async () => { const threshold = 5000; swapsController.configure({ fetchTopAssetsThreshold: threshold }); - swapsController.update({ + swapsController.__test__updateState({ topAssets: [], topAssetsLastFetched: Date.now() - threshold - 1, }); @@ -544,7 +557,7 @@ describe('SwapsController', () => { }); it('should not fetch top assets when no threshold reached', async () => { - swapsController.update({ + swapsController.__test__updateState({ topAssets: [], topAssetsLastFetched: Date.now(), }); @@ -553,7 +566,7 @@ describe('SwapsController', () => { }); it('should not fetch top assets when no threshold reached or tokens are available', async () => { - swapsController.update({ + swapsController.__test__updateState({ topAssets: [], topAssetsLastFetched: Date.now(), }); @@ -567,7 +580,7 @@ describe('SwapsController', () => { }); const threshold = 5000; swapsController.configure({ fetchTopAssetsThreshold: threshold }); - swapsController.update({ + swapsController.__test__updateState({ topAssets: [], topAssetsLastFetched: Date.now() - threshold - 1, }); @@ -580,8 +593,11 @@ describe('SwapsController', () => { swapsController.configure({ supportedChainIds: ['0x1'], }); - swapsController.state.topAssets = []; - swapsController.state.topAssetsLastFetched = 0; + swapsController.__test__updateState({ + topAssets: [], + topAssetsLastFetched: 0, + }); + swapsController.configure({ chainId: '0x2' }); await swapsController.fetchTopAssetsWithCache(); expect(swapsUtilFetchTopAssets).not.toHaveBeenCalled(); @@ -590,7 +606,7 @@ describe('SwapsController', () => { describe('aggregator metadata cache', () => { it('should fetch aggregator metadata when no aggregator metadata in state', async () => { - swapsController.update({ + swapsController.__test__updateState({ aggregatorMetadata: null, }); await swapsController.fetchAggregatorMetadataWithCache(); @@ -598,7 +614,7 @@ describe('SwapsController', () => { }); it('should fetch aggregator metadata when last fetched is 0', async () => { - swapsController.update({ + swapsController.__test__updateState({ aggregatorMetadata: {}, aggregatorMetadataLastFetched: 0, }); @@ -611,7 +627,7 @@ describe('SwapsController', () => { swapsController.configure({ fetchAggregatorMetadataThreshold: threshold, }); - swapsController.update({ + swapsController.__test__updateState({ aggregatorMetadata: {}, aggregatorMetadataLastFetched: Date.now() - threshold - 1, }); @@ -620,7 +636,7 @@ describe('SwapsController', () => { }); it('should not fetch aggregator metadata when no threshold reached', async () => { - swapsController.update({ + swapsController.__test__updateState({ aggregatorMetadata: {}, aggregatorMetadataLastFetched: Date.now(), }); @@ -629,7 +645,7 @@ describe('SwapsController', () => { }); it('should not fetch aggregator metadata when no threshold reached or tokens are available', async () => { - swapsController.update({ + swapsController.__test__updateState({ aggregatorMetadata: {}, aggregatorMetadataLastFetched: Date.now(), }); @@ -645,7 +661,7 @@ describe('SwapsController', () => { swapsController.configure({ fetchAggregatorMetadataThreshold: threshold, }); - swapsController.update({ + swapsController.__test__updateState({ aggregatorMetadata: {}, aggregatorMetadataLastFetched: Date.now() - threshold - 1, }); @@ -657,8 +673,10 @@ describe('SwapsController', () => { swapsController.configure({ supportedChainIds: ['0x1'], }); - swapsController.state.aggregatorMetadata = {}; - swapsController.state.aggregatorMetadataLastFetched = 0; + swapsController.__test__updateState({ + aggregatorMetadata: {}, + aggregatorMetadataLastFetched: 0, + }); swapsController.configure({ chainId: '0x2' }); await swapsController.fetchAggregatorMetadataWithCache(); expect(swapsUtilFetchAggregatorMetadata).not.toHaveBeenCalled(); @@ -821,11 +839,10 @@ describe('SwapsController', () => { swapsController = new SwapsController( { + messenger: messengerMock, fetchGasFeeEstimates, }, - { - pollCountLimit: POLL_COUNT_LIMIT, - }, + swapsUtil.getDefaultSwapsControllerState(), ); }); @@ -852,11 +869,10 @@ describe('SwapsController', () => { swapsController = new SwapsController( { + messenger: messengerMock, fetchGasFeeEstimates, }, - { - pollCountLimit: POLL_COUNT_LIMIT, - }, + swapsUtil.getDefaultSwapsControllerState(), ); // @ts-expect-error - testing private method @@ -868,11 +884,10 @@ describe('SwapsController', () => { it('should fetch gas price from fetchGasPrices if fetchGasFeeEstimates is not defined', async () => { swapsController = new SwapsController( { + messenger: messengerMock, fetchGasFeeEstimates: undefined, }, - { - pollCountLimit: POLL_COUNT_LIMIT, - }, + swapsUtil.getDefaultSwapsControllerState(), ); const fetchGasPricesSpy = jest @@ -910,11 +925,10 @@ describe('SwapsController', () => { swapsController = new SwapsController( { + messenger: messengerMock, fetchGasFeeEstimates, }, - { - pollCountLimit: POLL_COUNT_LIMIT, - }, + swapsUtil.getDefaultSwapsControllerState(), ); }); @@ -1023,11 +1037,10 @@ describe('SwapsController', () => { swapsController = new SwapsController( { + messenger: messengerMock, fetchGasFeeEstimates, }, - { - pollCountLimit: POLL_COUNT_LIMIT, - }, + swapsUtil.getDefaultSwapsControllerState(), ); }); @@ -1104,11 +1117,10 @@ describe('SwapsController', () => { swapsController = new SwapsController( { + messenger: messengerMock, fetchGasFeeEstimates, }, - { - pollCountLimit: POLL_COUNT_LIMIT, - }, + swapsUtil.getDefaultSwapsControllerState(), ); }); @@ -1150,11 +1162,10 @@ describe('SwapsController', () => { swapsController = new SwapsController( { + messenger: messengerMock, fetchGasFeeEstimates, }, - { - pollCountLimit: POLL_COUNT_LIMIT, - }, + swapsUtil.getDefaultSwapsControllerState(), ); }); diff --git a/src/SwapsController.ts b/src/SwapsController.ts index 92c0f783..c0b31451 100644 --- a/src/SwapsController.ts +++ b/src/SwapsController.ts @@ -1,5 +1,4 @@ -import type { BaseConfig, BaseState } from '@metamask/base-controller'; -import { BaseControllerV1 } from '@metamask/base-controller'; +import { BaseController, StateMetadata } from '@metamask/base-controller'; import { gweiDecToWEIBN, query, @@ -16,52 +15,76 @@ import type { GasFeeStateLegacy, } from '@metamask/gas-fee-controller'; import { GAS_ESTIMATE_TYPES } from '@metamask/gas-fee-controller'; -import type { TransactionParams } from '@metamask/transaction-controller'; import type { Hex } from '@metamask/utils'; import { Mutex } from 'async-mutex'; import { BigNumber } from 'bignumber.js'; import abiERC20 from 'human-standard-token-abi'; -import * as web3 from 'web3'; import type { Web3 as Web3Type } from 'web3'; +import * as web3 from 'web3'; import type { - APIAggregatorMetadata, APIFetchQuotesMetadata, APIFetchQuotesParams, - ChainData, ChainCache, + ChainData, + CustomEthGasPriceEstimate, + CustomGasFee, Quote, - QuoteSavings, QuoteValues, - SwapsAsset, - SwapsToken, -} from './swapsInterfaces'; + SwapsConfig, + SwapsControllerMessenger, + SwapsControllerOptions, + SwapsControllerState, + TxParams, +} from './SwapsController.types'; import { calcTokenAmount, calculateGasEstimateWithRefund, calculateGasLimits, + controllerName, + DEFAULT_ERC20_APPROVE_GAS, estimateGas, fetchAggregatorMetadata, fetchGasPrices, fetchTokens, fetchTopAssets, fetchTradesInfo, + getDefaultSwapsControllerState, getSwapsContractAddress, - SwapsError, - DEFAULT_ERC20_APPROVE_GAS, NATIVE_SWAPS_TOKEN_ADDRESS, - ETH_CHAIN_ID, - BSC_CHAIN_ID, - SWAPS_TESTNET_CHAIN_ID, - POLYGON_CHAIN_ID, - AVALANCHE_CHAIN_ID, OPTIMISM_CHAIN_ID, shouldEnableDirectWrapping, + SwapsError, } from './swapsUtil'; // Hack to fix the issue with the web3 import that works different in app vs tests const Web3 = web3.Web3 === undefined ? web3.default : web3.Web3; +const metadata: StateMetadata = { + quotes: { persist: false, anonymous: false }, + quoteValues: { persist: false, anonymous: false }, + fetchParams: { persist: false, anonymous: false }, + fetchParamsMetaData: { persist: false, anonymous: false }, + topAggSavings: { persist: false, anonymous: false }, + aggregatorMetadata: { persist: false, anonymous: false }, + tokens: { persist: false, anonymous: false }, + topAssets: { persist: false, anonymous: false }, + approvalTransaction: { persist: false, anonymous: false }, + aggregatorMetadataLastFetched: { persist: false, anonymous: false }, + quotesLastFetched: { persist: false, anonymous: false }, + topAssetsLastFetched: { persist: false, anonymous: false }, + error: { persist: false, anonymous: false }, + topAggId: { persist: false, anonymous: false }, + tokensLastFetched: { persist: false, anonymous: false }, + isInPolling: { persist: false, anonymous: false }, + pollingCyclesLeft: { persist: false, anonymous: false }, + quoteRefreshSeconds: { persist: false, anonymous: false }, + usedGasEstimate: { persist: false, anonymous: false }, + usedCustomGas: { persist: false, anonymous: false }, + chainCache: { persist: false, anonymous: false }, + config: { persist: false, anonymous: false }, +}; + // Functions to determine type of the return value from GasFeeController /** @@ -97,19 +120,6 @@ export function isGasFeeStateLegacy( return object.gasEstimateType === GAS_ESTIMATE_TYPES.LEGACY; } -// Custom types for custom gas values -type CustomEthGasPriceEstimate = { - gasPrice: string; // a GWEI dec string - selected?: 'low' | 'medium' | 'high'; -}; - -type CustomGasFee = { - maxFeePerGas: string; // a GWEI dec string - maxPriorityFeePerGas: string; // a GWEI dec string - estimatedBaseFee?: string; // a GWEI dec string - selected?: 'low' | 'medium' | 'high'; -}; - /** * Determines if the given object is of type EthGasPriceEstimate. * @param object - The object to be evaluated. @@ -143,52 +153,6 @@ function isCustomGasFee(object: any): object is CustomGasFee { ); } -export type SwapsConfig = { - clientId?: string; - maxGasLimit: number; - pollCountLimit: number; - fetchAggregatorMetadataThreshold: number; - fetchTokensThreshold: number; - fetchTopAssetsThreshold: number; - provider: any; - chainId: Hex; - supportedChainIds: Hex[]; -} & BaseConfig; - -export type SwapsState = { - quotes: { [key: string]: Quote }; - fetchParams: APIFetchQuotesParams; - fetchParamsMetaData: APIFetchQuotesMetadata; - topAggSavings: QuoteSavings | null; - quotesLastFetched: null | number; - error: { key: null | SwapsError; description: null | string }; - topAggId: null | string; - isInPolling: boolean; - pollingCyclesLeft: number; - approvalTransaction: TransactionParams | null; - quoteValues: { [key: string]: QuoteValues } | null; - quoteRefreshSeconds: number | null; - usedGasEstimate: EthGasPriceEstimate | GasFeeEstimates | null; - usedCustomGas: CustomEthGasPriceEstimate | CustomGasFee | null; - aggregatorMetadata: null | { [key: string]: APIAggregatorMetadata }; - aggregatorMetadataLastFetched: number; - tokens: null | SwapsToken[]; - tokensLastFetched: number; - topAssets: null | SwapsAsset[]; - topAssetsLastFetched: number; - chainCache: ChainCache; -} & BaseState; - -type SwapsNextState = { - quotes: { [key: string]: Quote }; - quotesLastFetched: null | number; - approvalTransaction: TransactionParams | null; - topAggId: null | string; - topAggSavings?: QuoteSavings | null; - quoteValues: { [key: string]: QuoteValues } | null; - quoteRefreshSeconds: number | null; -}; - export const INITIAL_CHAIN_DATA: ChainData = { aggregatorMetadata: null, tokens: null, @@ -219,9 +183,10 @@ function getNewChainCache( }; } -export default class SwapsController extends BaseControllerV1< - SwapsConfig, - SwapsState +export default class SwapsController extends BaseController< + typeof controllerName, + SwapsControllerState, + SwapsControllerMessenger > { private handle?: NodeJS.Timeout; @@ -242,7 +207,7 @@ export default class SwapsController extends BaseControllerV1< private readonly fetchEstimatedMultiLayerL1Fee?: ( eth: any, options: { - txParams: TransactionParams; + txParams: TxParams; chainId: Hex; }, ) => Promise; @@ -275,8 +240,8 @@ export default class SwapsController extends BaseControllerV1< try { const { proposedGasPrice } = await fetchGasPrices( - this.config.chainId, - this.config.clientId, + this.state.config.chainId, + this.state.config.clientId, ); return { gasPrice: proposedGasPrice }; } catch (error) { @@ -558,7 +523,10 @@ export default class SwapsController extends BaseControllerV1< const allowancePromise = async () => { const result: bigint = await contract.methods - .allowance(walletAddress, getSwapsContractAddress(this.config.chainId)) + .allowance( + walletAddress, + getSwapsContractAddress(this.state.config.chainId), + ) .call(); return new BigNumber(result.toString()); }; @@ -568,7 +536,7 @@ export default class SwapsController extends BaseControllerV1< /* istanbul ignore next */ private async timedoutGasReturn( - tradeTxParams: TransactionParams | null, + tradeTxParams: TxParams | null, ): Promise<{ gas: string | null }> { if (!tradeTxParams) { return { gas: null }; @@ -586,7 +554,7 @@ export default class SwapsController extends BaseControllerV1< from: tradeTxParams.from, to: tradeTxParams.to, value: tradeTxParams.value, - }, + } as TxParams, this.ethQuery, ), gasTimeout, @@ -606,22 +574,38 @@ export default class SwapsController extends BaseControllerV1< this.handle = undefined; } - if (this.pollCount < Number(this.config.pollCountLimit) + 1) { + if (this.pollCount < Number(this.state.config.pollCountLimit) + 1) { if (!this.state.isInPolling) { - this.update({ isInPolling: true }); + this.update((_state) => { + _state.isInPolling = true; + }); } const { nextQuotesState, threshold, usedGasEstimate } = await this.fetchQuotes(); - this.update({ - pollingCyclesLeft: this.config.pollCountLimit - this.pollCount, + this.update((_state) => { + _state.pollingCyclesLeft = + _state.config.pollCountLimit - this.pollCount; }); if (threshold && nextQuotesState?.quoteRefreshSeconds) { - this.update({ ...this.state, ...nextQuotesState, usedGasEstimate }); + // this.update({ ...this.state, ...nextQuotesState, usedGasEstimate }); + this.update((_state) => { + _state.quotes = nextQuotesState.quotes ?? _state.quotes; + _state.quotesLastFetched = nextQuotesState.quotesLastFetched ?? 0; + _state.approvalTransaction = + nextQuotesState.approvalTransaction ?? null; + _state.topAggId = nextQuotesState.topAggId ?? _state.topAggId; + _state.quoteValues = + nextQuotesState.quoteValues ?? _state.quoteValues; + _state.quoteRefreshSeconds = nextQuotesState.quoteRefreshSeconds ?? 0; + _state.usedGasEstimate = usedGasEstimate; + }); this.handle = setTimeout(() => { this.pollForNewQuotesWithThreshold(threshold).catch(() => { - this.update({ isInPolling: false }); + this.update((_state) => { + _state.isInPolling = false; + }); }); }, nextQuotesState.quoteRefreshSeconds * 1000 - threshold); } @@ -670,13 +654,13 @@ export default class SwapsController extends BaseControllerV1< /* istanbul ignore next */ private async fetchQuotes(): Promise<{ - nextQuotesState: SwapsNextState | null; + nextQuotesState: Partial | null; threshold: number | null; usedGasEstimate: EthGasPriceEstimate | GasFeeEstimates | null; }> { const timeStarted = Date.now(); const { fetchParams } = this.state; - const { clientId, chainId } = this.config; + const { clientId, chainId } = this.state.config; try { /** We need to abort quotes fetch if stopPollingAndResetState is called while getting quotes */ this.abortController = new AbortController(); @@ -703,19 +687,15 @@ export default class SwapsController extends BaseControllerV1< chainId, }); // eslint-disable-next-line require-atomic-updates - quote.multiLayerL1TradeFeeTotal = multiLayerL1TradeFeeTotal; + quote.multiLayerL1TradeFeeTotal = + multiLayerL1TradeFeeTotal ?? null; } return quote; }), ); } - let approvalTransaction: { - data?: string; - from: string; - to?: string; - gas?: string; - } | null = null; + let approvalTransaction: TxParams | null = null; const enableDirectWrappingParam = shouldEnableDirectWrapping( chainId, @@ -750,11 +730,12 @@ export default class SwapsController extends BaseControllerV1< if (!approvalTransaction) { throw new Error(SwapsError.SWAPS_ALLOWANCE_ERROR); } + const { gas: approvalGas } = await this.timedoutGasReturn({ data: approvalTransaction.data, from: approvalTransaction.from, to: approvalTransaction.to, - }); + } as TxParams); approvalTransaction = { ...approvalTransaction, @@ -775,7 +756,7 @@ export default class SwapsController extends BaseControllerV1< const quotesLastFetched = Date.now(); - const nextQuotesState: SwapsNextState = { + const nextQuotesState: Partial = { quotes, quotesLastFetched, approvalTransaction, @@ -801,140 +782,38 @@ export default class SwapsController extends BaseControllerV1< } } - /** - * Name of this controller used during composition - */ - name = 'SwapsController'; - - /** - * List of required sibling controllers this controller needs to function - */ - requiredControllers = []; - /** * Creates a SwapsController instance. * @param options - Constructor options. * @param options.fetchGasFeeEstimates - Fetches gas fee estimates from GasFeeController. * @param options.fetchEstimatedMultiLayerL1Fee - Fetches an L1 fee for a given transaction. + * @param options.messenger - The messaging system used by the controller. * @param config - Initial options used to configure this controller. * @param state - Initial state to set on this controller. */ constructor( - { - fetchGasFeeEstimates, - fetchEstimatedMultiLayerL1Fee, - }: { - fetchGasFeeEstimates?: () => Promise; - fetchEstimatedMultiLayerL1Fee?: ( - eth: EthQuery, - options: { - txParams: TransactionParams; - chainId: Hex; - }, - ) => Promise; - }, - config?: Partial, - state?: Partial, + opts: SwapsControllerOptions, + state: Partial, ) { - super(config, state); - this.defaultConfig = { - maxGasLimit: 2500000, - pollCountLimit: 3, - fetchAggregatorMetadataThreshold: 1000 * 60 * 60 * 24 * 15, - fetchTokensThreshold: 1000 * 60 * 60 * 24, - fetchTopAssetsThreshold: 1000 * 60 * 30, - provider: undefined, - chainId: '0x1', - supportedChainIds: [ - ETH_CHAIN_ID, - BSC_CHAIN_ID, - SWAPS_TESTNET_CHAIN_ID, - POLYGON_CHAIN_ID, - AVALANCHE_CHAIN_ID, - ], - clientId: undefined, - }; - - this.defaultState = { - quotes: {}, - quoteValues: {}, - fetchParams: { - slippage: 0, - sourceToken: '', - sourceAmount: 0, - destinationToken: '', - walletAddress: '', - }, - fetchParamsMetaData: { - sourceTokenInfo: { - decimals: 0, - address: '', - symbol: '', - }, - destinationTokenInfo: { - decimals: 0, - address: '', - symbol: '', - }, + super({ + name: controllerName, + metadata, + messenger: opts.messenger, + state: { + ...getDefaultSwapsControllerState(), + ...state, }, - topAggSavings: null, - aggregatorMetadata: null, - tokens: null, - topAssets: null, - approvalTransaction: null, - aggregatorMetadataLastFetched: 0, - quotesLastFetched: 0, - topAssetsLastFetched: 0, - error: { key: null, description: null }, - topAggId: null, - tokensLastFetched: 0, - isInPolling: false, - pollingCyclesLeft: config?.pollCountLimit ?? 3, - quoteRefreshSeconds: null, - usedGasEstimate: null, - usedCustomGas: null, - chainCache: { - '0x1': INITIAL_CHAIN_DATA, - }, - }; - - this.fetchGasFeeEstimates = fetchGasFeeEstimates; - this.fetchEstimatedMultiLayerL1Fee = fetchEstimatedMultiLayerL1Fee; - this.initialize(); - } - - set provider(provider: any) { - if (provider) { - this.ethQuery = new EthQuery(provider); - this.web3 = new Web3(provider); - } - } - - set chainId(chainId: Hex) { - if (!this.config.supportedChainIds.includes(chainId)) { - return; - } - - const { chainCache } = this.state; - if (!chainCache?.[chainId]) { - this.update({ - ...INITIAL_CHAIN_DATA, - chainCache: getNewChainCache(chainCache, chainId, INITIAL_CHAIN_DATA), - }); - return; - } - - const cachedData = chainCache[chainId]; - this.update({ - ...cachedData, }); + + this.fetchGasFeeEstimates = opts.fetchGasFeeEstimates; + this.fetchEstimatedMultiLayerL1Fee = opts.fetchEstimatedMultiLayerL1Fee; } /** * Updates all quotes with a new custom gas price. * @param customGasFee - Custom gas price in dec gwei format. */ - updateQuotesWithGasPrice( + public updateQuotesWithGasPrice( customGasFee: CustomEthGasPriceEstimate | CustomGasFee, ): void { const { quotes, usedGasEstimate } = this.state; @@ -946,14 +825,18 @@ export default class SwapsController extends BaseControllerV1< usedGasEstimate, customGasFee, ); - this.update({ topAggId, quoteValues, usedCustomGas: customGasFee }); + this.update((_state) => { + _state.quoteValues = quoteValues; + _state.topAggId = topAggId; + _state.usedCustomGas = customGasFee; + }); } /** * Updates the selected quote maxEthFee param according to a custom gas limit. * @param customGasLimit - Custom gas limit in hex format. */ - updateSelectedQuoteWithGasLimit(customGasLimit: string): void { + public updateSelectedQuoteWithGasLimit(customGasLimit: string): void { const { topAggId, quotes, quoteValues, usedGasEstimate, usedCustomGas } = this.state; if (!topAggId || !quoteValues || !usedGasEstimate) { @@ -966,10 +849,19 @@ export default class SwapsController extends BaseControllerV1< customGasLimit, ); quoteValues[selectedQuote.aggregator].maxEthFee = maxEthFee; - this.update({ topAggId, quoteValues }); + + this.update((_state) => { + _state.topAggId = topAggId; + _state.quoteValues = quoteValues; + }); } - startFetchAndSetQuotes( + /** + * Starts the polling process. + * @param fetchParams - Parameters to fetch quotes. + * @param fetchParamsMetaData - Metadata for the fetchParams. + */ + public startFetchAndSetQuotes( fetchParams?: APIFetchQuotesParams, fetchParamsMetaData?: APIFetchQuotesMetadata, ) { @@ -982,16 +874,23 @@ export default class SwapsController extends BaseControllerV1< // of quotes with these new params. this.pollCount = 0; - this.update({ fetchParams, fetchParamsMetaData }); + this.update((_state) => { + _state.fetchParams = fetchParams; + _state.fetchParamsMetaData = + fetchParamsMetaData ?? _state.fetchParamsMetaData; + }); // ignoring rule since otherwise we need to change the behavior of the function // eslint-disable-next-line @typescript-eslint/no-floating-promises this.pollForNewQuotesWithThreshold(); } + /** + * Fetches the tokens and updates the state with them. + */ async fetchTokenWithCache() { const { chainId, clientId, fetchTokensThreshold, supportedChainIds } = - this.config; + this.state.config; const { tokens, tokensLastFetched } = this.state; if (!supportedChainIds.includes(chainId)) { @@ -1002,16 +901,20 @@ export default class SwapsController extends BaseControllerV1< const releaseLock = await this.mutex.acquire(); try { const newTokens = await fetchTokens(chainId, clientId); - const data = { tokens: newTokens, tokensLastFetched: Date.now() }; - this.update({ - ...data, - chainCache: getNewChainCache(this.state.chainCache, chainId, data), + this.update((_state) => { + _state.tokens = newTokens; + _state.tokensLastFetched = Date.now(); + _state.chainCache = getNewChainCache(_state.chainCache, chainId, { + tokens: newTokens, + tokensLastFetched: Date.now(), + }); }); } catch { - const data = { tokensLastFetched: 0 }; - this.update({ - ...data, - chainCache: getNewChainCache(this.state.chainCache, chainId, data), + this.update((_state) => { + _state.tokensLastFetched = 0; + _state.chainCache = getNewChainCache(_state.chainCache, chainId, { + tokensLastFetched: 0, + }); }); } finally { releaseLock(); @@ -1019,9 +922,12 @@ export default class SwapsController extends BaseControllerV1< } } - async fetchTopAssetsWithCache() { + /** + * Fetches the top assets and updates the state with them. + */ + public async fetchTopAssetsWithCache() { const { chainId, clientId, fetchTopAssetsThreshold, supportedChainIds } = - this.config; + this.state.config; const { topAssets, topAssetsLastFetched } = this.state; if (!supportedChainIds.includes(chainId)) { @@ -1039,15 +945,24 @@ export default class SwapsController extends BaseControllerV1< topAssets: newTopAssets, topAssetsLastFetched: Date.now(), }; - this.update({ - ...data, - chainCache: getNewChainCache(this.state.chainCache, chainId, data), + this.update((_state) => { + _state.topAssets = data.topAssets; + _state.topAssetsLastFetched = data.topAssetsLastFetched; + _state.chainCache = getNewChainCache( + _state.chainCache, + chainId, + data, + ); }); } catch { const data = { topAssetsLastFetched: 0 }; - this.update({ - ...data, - chainCache: getNewChainCache(this.state.chainCache, chainId, data), + this.update((_state) => { + _state.topAssetsLastFetched = data.topAssetsLastFetched; + _state.chainCache = getNewChainCache( + _state.chainCache, + chainId, + data, + ); }); } finally { releaseLock(); @@ -1055,13 +970,16 @@ export default class SwapsController extends BaseControllerV1< } } - async fetchAggregatorMetadataWithCache() { + /** + * Fetches the aggregator metadata and updates the state with it. + */ + public async fetchAggregatorMetadataWithCache() { const { chainId, clientId, fetchAggregatorMetadataThreshold, supportedChainIds, - } = this.config; + } = this.state.config; const { aggregatorMetadata, aggregatorMetadataLastFetched } = this.state; if (!supportedChainIds.includes(chainId)) { @@ -1083,15 +1001,26 @@ export default class SwapsController extends BaseControllerV1< aggregatorMetadata: newAggregatorMetada, aggregatorMetadataLastFetched: Date.now(), }; - this.update({ - ...data, - chainCache: getNewChainCache(this.state.chainCache, chainId, data), + this.update((_state) => { + _state.aggregatorMetadata = data.aggregatorMetadata; + _state.aggregatorMetadataLastFetched = + data.aggregatorMetadataLastFetched; + _state.chainCache = getNewChainCache( + _state.chainCache, + chainId, + data, + ); }); } catch { const data = { aggregatorMetadataLastFetched: 0 }; - this.update({ - ...data, - chainCache: getNewChainCache(this.state.chainCache, chainId, data), + this.update((_state) => { + _state.aggregatorMetadataLastFetched = + data.aggregatorMetadataLastFetched; + _state.chainCache = getNewChainCache( + _state.chainCache, + chainId, + data, + ); }); } finally { releaseLock(); @@ -1105,7 +1034,7 @@ export default class SwapsController extends BaseControllerV1< * @param error.key - Error key. * @param error.description - Error description. */ - stopPollingAndResetState( + public stopPollingAndResetState( error: { key: SwapsError | null; description: string | null; @@ -1116,18 +1045,84 @@ export default class SwapsController extends BaseControllerV1< ) { this.abortController && this.abortController.abort(); this.handle && clearTimeout(this.handle); - this.pollCount = Number(this.config.pollCountLimit) + 1; - this.update({ - ...this.defaultState, - isInPolling: false, - tokensLastFetched: this.state.tokensLastFetched, - topAssetsLastFetched: this.state.topAssetsLastFetched, - aggregatorMetadataLastFetched: this.state.aggregatorMetadataLastFetched, - tokens: this.state.tokens, - topAssets: this.state.topAssets, - aggregatorMetadata: this.state.aggregatorMetadata, - chainCache: this.state.chainCache, - error, + this.pollCount = Number(this.state.config.pollCountLimit) + 1; + this.update((_state) => { + const defaultState = getDefaultSwapsControllerState(); + Object.keys(defaultState).forEach((key) => { + const typedKey = key as keyof typeof defaultState; + (_state as any)[typedKey] = defaultState[typedKey]; + }); + _state.error.key = error.key; + _state.error.description = error.description; + }); + } + + /** + * Internal method used to set the chainId in the state. Users should use the `configure` method instead. + * @param chainId - The chainId to set. + */ + #setChainId(chainId: Hex) { + if (!this.state.config.supportedChainIds.includes(chainId)) { + return; + } + + const { chainCache } = this.state; + if (!chainCache?.[chainId]) { + this.update((_state) => { + _state.config.chainId = chainId; + _state.aggregatorMetadata = null; + _state.tokens = null; + _state.topAssets = null; + _state.aggregatorMetadataLastFetched = 0; + _state.topAssetsLastFetched = 0; + _state.tokensLastFetched = 0; + _state.chainCache = getNewChainCache(chainCache, chainId, { + ...INITIAL_CHAIN_DATA, + }); + }); + return; + } + + const cachedData = chainCache[chainId]; + + this.update((_state) => { + _state.config.chainId = chainId; + _state.aggregatorMetadata = cachedData.aggregatorMetadata; + _state.tokens = cachedData.tokens; + _state.topAssets = cachedData.topAssets; + _state.aggregatorMetadataLastFetched = + cachedData.aggregatorMetadataLastFetched; + _state.topAssetsLastFetched = cachedData.topAssetsLastFetched; + _state.tokensLastFetched = cachedData.tokensLastFetched; }); } + + public configure(config: Partial) { + const { chainId, provider } = config; + if (chainId) { + this.#setChainId(chainId); + } + if (provider) { + this.web3 = new Web3(provider); + this.ethQuery = new EthQuery(provider); + } + this.update((_state) => { + _state.config = { + ..._state.config, + ...config, + }; + }); + } + + /** + * This method is used to update the state of the controller for testing purposes. + * DO NOT USE OUTSIDE OF TESTING + * + * @param newState - The new state to set + */ + public __test__updateState = (newState: Partial) => { + this.update((oldState) => { + return { ...oldState, ...newState }; + }); + }; } diff --git a/src/swapsInterfaces.ts b/src/SwapsController.types.ts similarity index 72% rename from src/swapsInterfaces.ts rename to src/SwapsController.types.ts index 03f77313..fef53cfa 100644 --- a/src/swapsInterfaces.ts +++ b/src/SwapsController.types.ts @@ -1,5 +1,12 @@ -import type { TransactionParams } from '@metamask/transaction-controller'; -import type { BigNumber } from 'bignumber.js'; +import { RestrictedControllerMessenger } from '@metamask/base-controller'; +import EthQuery from '@metamask/eth-query'; +import { + EthGasPriceEstimate, + GasFeeEstimates, + GasFeeState, +} from '@metamask/gas-fee-controller'; +import { Hex } from '@metamask/utils'; +import { controllerName, SwapsError } from './swapsUtil'; export enum APIType { TRADES = 'TRADES', @@ -116,17 +123,17 @@ export type APIAggregatorMetadata = { type QuoteTransaction = { value: string; -} & TransactionParams; +} & TxParams; /** * Savings of a quote * @interface QuoteSavings */ export type QuoteSavings = { - total: BigNumber; - performance: BigNumber; - fee: BigNumber; - medianMetaMaskFee: BigNumber; + total: string; + performance: string; + fee: string; + medianMetaMaskFee: string; }; /** @@ -158,15 +165,14 @@ export type QuoteSavings = { */ export type Quote = { trade: QuoteTransaction; - approvalNeeded: null | { - data: string; - to: string; - from: string; - gas: string; - }; + approvalNeeded: TxParams | null; sourceAmount: string; destinationAmount: number; - error: null | Error; + error: { + name: string; + message: string; + stack: string; + } | null; sourceToken: string; destinationToken: string; maxGas: number; @@ -183,7 +189,7 @@ export type Quote = { gasEstimateWithRefund: string | null; destinationTokenRate: number | null; sourceTokenRate: number | null; - multiLayerL1TradeFeeTotal: string | undefined; + multiLayerL1TradeFeeTotal: string | null; }; /** @@ -238,6 +244,16 @@ export type TransactionReceipt = { status: string; }; +export type TxParams = { + from: string; + to: string; + value?: string; + data?: string; + gas: string; + gasPrice?: string; + nonce?: string; +}; + export type ChainData = { aggregatorMetadata: null | { [key: string]: APIAggregatorMetadata }; tokens: null | SwapsToken[]; @@ -250,3 +266,92 @@ export type ChainData = { export type ChainCache = { [key: string]: ChainData; }; + +// Custom types for custom gas values +export type CustomEthGasPriceEstimate = { + gasPrice: string; // a GWEI dec string + selected?: 'low' | 'medium' | 'high'; +}; + +export type CustomGasFee = { + maxFeePerGas: string; // a GWEI dec string + maxPriorityFeePerGas: string; // a GWEI dec string + estimatedBaseFee?: string; // a GWEI dec string + selected?: 'low' | 'medium' | 'high'; +}; + +export type SwapsConfig = { + clientId?: string; + maxGasLimit: number; + pollCountLimit: number; + fetchAggregatorMetadataThreshold: number; + fetchTokensThreshold: number; + fetchTopAssetsThreshold: number; + provider: any; + chainId: Hex; + supportedChainIds: Hex[]; +}; + +export type SwapsControllerState = { + quotes: { [key: string]: Quote }; + fetchParams: APIFetchQuotesParams; + fetchParamsMetaData: APIFetchQuotesMetadata; + topAggSavings: QuoteSavings | null; + quotesLastFetched: null | number; + error: { key: null | SwapsError; description: null | string }; + topAggId: null | string; + isInPolling: boolean; + pollingCyclesLeft: number; + approvalTransaction: TxParams | null; + quoteValues: { [key: string]: QuoteValues } | null; + quoteRefreshSeconds: number | null; + usedGasEstimate: EthGasPriceEstimate | GasFeeEstimates | null; + usedCustomGas: CustomEthGasPriceEstimate | CustomGasFee | null; + aggregatorMetadata: null | { [key: string]: APIAggregatorMetadata }; + aggregatorMetadataLastFetched: number; + tokens: null | SwapsToken[]; + tokensLastFetched: number; + topAssets: null | SwapsAsset[]; + topAssetsLastFetched: number; + chainCache: ChainCache; + config: SwapsConfig; +}; + +/** + * The external actions available to the {@link SwapsController}. + */ +export type AllowedActions = never; + +/** + * The internal actions available to the SwapsController. + */ +export type SwapsControllerActions = never; + +/** + * The events that the SwapsController can emit. + */ +export type SwapsControllerEvents = never; + +/** + * The messenger for the SwapsController. + */ +export type SwapsControllerMessenger = RestrictedControllerMessenger< + typeof controllerName, + SwapsControllerActions | AllowedActions, + SwapsControllerEvents, + AllowedActions['type'], + never +>; + +export type SwapsControllerOptions = { + // TODO: Remove once GasFeeController exports this action type + fetchGasFeeEstimates?: () => Promise; + fetchEstimatedMultiLayerL1Fee?: ( + eth: EthQuery, + options: { + txParams: TxParams; + chainId: Hex; + }, + ) => Promise; + messenger: SwapsControllerMessenger; +}; diff --git a/src/constants.ts b/src/constants.ts index 8ae29925..77255b0f 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,6 +1,8 @@ import { toHex } from '@metamask/controller-utils'; -import type { SwapsToken } from './swapsInterfaces'; +import { INITIAL_CHAIN_DATA } from './SwapsController'; +import type { SwapsToken } from './SwapsController.types'; +import { SwapsControllerState } from './SwapsController.types'; //* Chain IDs and names @@ -218,3 +220,65 @@ export const DEFAULT_ERC20_APPROVE_GAS = '0x1d4c0'; // The MAX_GAS_LIMIT is a number that is higher than the maximum gas costs we have observed on any aggregator export const MAX_GAS_LIMIT = 2500000; + +export const controllerName = 'SwapsController'; + +export const getDefaultSwapsControllerState = (): SwapsControllerState => ({ + quotes: {}, + quoteValues: {}, + fetchParams: { + slippage: 0, + sourceToken: '', + sourceAmount: 0, + destinationToken: '', + walletAddress: '', + }, + fetchParamsMetaData: { + sourceTokenInfo: { + decimals: 0, + address: '', + symbol: '', + }, + destinationTokenInfo: { + decimals: 0, + address: '', + symbol: '', + }, + }, + topAggSavings: null, + aggregatorMetadata: null, + tokens: null, + topAssets: null, + approvalTransaction: null, + aggregatorMetadataLastFetched: 0, + quotesLastFetched: 0, + topAssetsLastFetched: 0, + error: { key: null, description: null }, + topAggId: null, + tokensLastFetched: 0, + isInPolling: false, + pollingCyclesLeft: 3, + quoteRefreshSeconds: null, + usedGasEstimate: null, + usedCustomGas: null, + chainCache: { + '0x1': INITIAL_CHAIN_DATA, + }, + config: { + maxGasLimit: 2500000, + pollCountLimit: 3, + fetchAggregatorMetadataThreshold: 1000 * 60 * 60 * 24 * 15, + fetchTokensThreshold: 1000 * 60 * 60 * 24, + fetchTopAssetsThreshold: 1000 * 60 * 30, + provider: undefined, + chainId: '0x1', + supportedChainIds: [ + ETH_CHAIN_ID, + BSC_CHAIN_ID, + SWAPS_TESTNET_CHAIN_ID, + POLYGON_CHAIN_ID, + AVALANCHE_CHAIN_ID, + ], + clientId: undefined, + }, +}); diff --git a/src/swapsUtil.test.ts b/src/swapsUtil.test.ts index a8bc1d43..15741a1e 100644 --- a/src/swapsUtil.test.ts +++ b/src/swapsUtil.test.ts @@ -1,12 +1,15 @@ import { BigNumber } from 'bignumber.js'; -import type { QuoteValues, SwapsToken } from './swapsInterfaces'; -import { APIType } from './swapsInterfaces'; -import * as swapsUtil from './swapsUtil'; import { BNToHex, query, toHex } from '@metamask/controller-utils'; -import { BN } from 'bn.js'; -import { TransactionParams } from '@metamask/transaction-controller'; import { add0x } from '@metamask/utils'; +import { BN } from 'bn.js'; +import type { + QuoteValues, + SwapsToken, + TxParams, +} from './SwapsController.types'; +import { APIType } from './SwapsController.types'; +import * as swapsUtil from './swapsUtil'; /** * Mocks the fetch function for testing purposes. @@ -1436,7 +1439,7 @@ describe('SwapsUtil', () => { }); it('should estimate gas correctly for a given transaction', async () => { - const transaction: TransactionParams = { + const transaction: TxParams = { from: '0x1234', to: '0x5678', value: '0x0', @@ -1461,7 +1464,7 @@ describe('SwapsUtil', () => { }); it('should handle transactions without data correctly', async () => { - const transaction: TransactionParams = { + const transaction: TxParams = { from: '0x1234', to: '0x5678', value: '0x0', diff --git a/src/swapsUtil.ts b/src/swapsUtil.ts index a4915139..07b497fa 100644 --- a/src/swapsUtil.ts +++ b/src/swapsUtil.ts @@ -37,8 +37,9 @@ import type { SwapsAsset, SwapsToken, TransactionReceipt, -} from './swapsInterfaces'; -import { APIType } from './swapsInterfaces'; +} from './SwapsController.types'; +import { APIType } from './SwapsController.types'; +import { TxParams } from './SwapsController.types'; // / // / BEGIN: Lifted from now unexported normalizeTransaction in @metamask/transaction-controller@3.0.0 @@ -710,10 +711,7 @@ export function calcTokenAmount(value: number | BigNumber, decimals: number) { * @param ethQuery - The ethQuery object. * @returns Promise resolving to an object containing gas and gasPrice. */ -export async function estimateGas( - transaction: TransactionParams, - ethQuery: any, -) { +export async function estimateGas(transaction: TxParams, ethQuery: any) { const estimatedTransaction = { ...transaction }; const { value, data } = estimatedTransaction; const { gasLimit } = await query(ethQuery, 'getBlockByNumber', [ From 647573d934c8d25a38015a355404380df7ea2868 Mon Sep 17 00:00:00 2001 From: nikoferro Date: Tue, 20 Aug 2024 09:57:02 +0200 Subject: [PATCH 03/32] fix: linter fix --- src/SwapsController.ts | 25 ++++++++++++++----------- src/SwapsController.types.ts | 28 ++++++++++++++-------------- src/constants.ts | 3 +-- src/swapsUtil.test.ts | 27 ++++++++++++++++++--------- src/swapsUtil.ts | 14 ++++++++++++-- 5 files changed, 59 insertions(+), 38 deletions(-) diff --git a/src/SwapsController.ts b/src/SwapsController.ts index c0b31451..16915b8b 100644 --- a/src/SwapsController.ts +++ b/src/SwapsController.ts @@ -1,4 +1,5 @@ -import { BaseController, StateMetadata } from '@metamask/base-controller'; +import { BaseController } from '@metamask/base-controller'; +import type { StateMetadata } from '@metamask/base-controller'; import { gweiDecToWEIBN, query, @@ -784,11 +785,10 @@ export default class SwapsController extends BaseController< /** * Creates a SwapsController instance. - * @param options - Constructor options. - * @param options.fetchGasFeeEstimates - Fetches gas fee estimates from GasFeeController. - * @param options.fetchEstimatedMultiLayerL1Fee - Fetches an L1 fee for a given transaction. - * @param options.messenger - The messaging system used by the controller. - * @param config - Initial options used to configure this controller. + * @param opts - Constructor options. + * @param opts.fetchGasFeeEstimates - Fetches gas fee estimates from GasFeeController. + * @param opts.fetchEstimatedMultiLayerL1Fee - Fetches an L1 fee for a given transaction. + * @param opts.messenger - The messaging system used by the controller. * @param state - Initial state to set on this controller. */ constructor( @@ -860,6 +860,7 @@ export default class SwapsController extends BaseController< * Starts the polling process. * @param fetchParams - Parameters to fetch quotes. * @param fetchParamsMetaData - Metadata for the fetchParams. + * @returns Promise resolving when this operation completes. */ public startFetchAndSetQuotes( fetchParams?: APIFetchQuotesParams, @@ -1115,12 +1116,14 @@ export default class SwapsController extends BaseController< } /** - * This method is used to update the state of the controller for testing purposes. - * DO NOT USE OUTSIDE OF TESTING - * - * @param newState - The new state to set + * Updates the state of the controller for testing purposes. + * This method should not be used outside of testing. + * @param newState - The new state to set. */ - public __test__updateState = (newState: Partial) => { + // eslint-disable-next-line @typescript-eslint/naming-convention + public __test__updateState = ( + newState: Partial, + ): void => { this.update((oldState) => { return { ...oldState, ...newState }; }); diff --git a/src/SwapsController.types.ts b/src/SwapsController.types.ts index fef53cfa..4fa108a8 100644 --- a/src/SwapsController.types.ts +++ b/src/SwapsController.types.ts @@ -1,22 +1,13 @@ -import { RestrictedControllerMessenger } from '@metamask/base-controller'; -import EthQuery from '@metamask/eth-query'; -import { +import type { RestrictedControllerMessenger } from '@metamask/base-controller'; +import type EthQuery from '@metamask/eth-query'; +import type { EthGasPriceEstimate, GasFeeEstimates, GasFeeState, } from '@metamask/gas-fee-controller'; -import { Hex } from '@metamask/utils'; -import { controllerName, SwapsError } from './swapsUtil'; +import type { Hex } from '@metamask/utils'; -export enum APIType { - TRADES = 'TRADES', - TOKENS = 'TOKENS', - TOP_ASSETS = 'TOP_ASSETS', - FEATURE_FLAG = 'FEATURE_FLAG', - AGGREGATOR_METADATA = 'AGGREGATOR_METADATA', - TOKEN = 'TOKEN', - GAS_PRICES = 'GAS_PRICES', -} +import type { controllerName, SwapsError } from './swapsUtil'; export type SwapsAsset = { address: string; @@ -31,8 +22,11 @@ export type SwapsToken = { } & SwapsAsset; export type NetworkFeatureFlags = { + // eslint-disable-next-line @typescript-eslint/naming-convention mobile_active: boolean; + // eslint-disable-next-line @typescript-eslint/naming-convention extension_active: boolean; + // eslint-disable-next-line @typescript-eslint/naming-convention fallback_to_v1?: boolean; }; @@ -41,8 +35,11 @@ export type NetworksFeatureStatus = { }; export type NetworkFeatureFlagsAll = { + // eslint-disable-next-line @typescript-eslint/naming-convention mobile_active: boolean; + // eslint-disable-next-line @typescript-eslint/naming-convention extension_active: boolean; + // eslint-disable-next-line @typescript-eslint/naming-convention fallback_to_v1?: boolean; fallbackToV1: boolean; mobileActive: boolean; @@ -62,8 +59,11 @@ export type NetworksFeatureStatusAll = { }; export type GlobalFeatureFlags = { + // eslint-disable-next-line @typescript-eslint/naming-convention smart_transactions: { + // eslint-disable-next-line @typescript-eslint/naming-convention mobile_active: boolean; + // eslint-disable-next-line @typescript-eslint/naming-convention extension_active: boolean; }; smartTransactions: { diff --git a/src/constants.ts b/src/constants.ts index 77255b0f..916fffd0 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,8 +1,7 @@ import { toHex } from '@metamask/controller-utils'; import { INITIAL_CHAIN_DATA } from './SwapsController'; -import type { SwapsToken } from './SwapsController.types'; -import { SwapsControllerState } from './SwapsController.types'; +import type { SwapsToken, SwapsControllerState } from './SwapsController.types'; //* Chain IDs and names diff --git a/src/swapsUtil.test.ts b/src/swapsUtil.test.ts index 15741a1e..f30cd52a 100644 --- a/src/swapsUtil.test.ts +++ b/src/swapsUtil.test.ts @@ -8,7 +8,6 @@ import type { SwapsToken, TxParams, } from './SwapsController.types'; -import { APIType } from './SwapsController.types'; import * as swapsUtil from './swapsUtil'; /** @@ -174,20 +173,30 @@ const FAKE_SWAPS_TOKEN = { describe('SwapsUtil', () => { describe('getBaseApiURL', () => { it('should return expected values', () => { - expect(swapsUtil.getBaseApiURL(APIType.TRADES, '0x1')).toBeDefined(); - expect(swapsUtil.getBaseApiURL(APIType.TOKENS, '0x1')).toBeDefined(); - expect(swapsUtil.getBaseApiURL(APIType.TOKEN, '0x1')).toBeDefined(); - expect(swapsUtil.getBaseApiURL(APIType.TOP_ASSETS, '0x1')).toBeDefined(); expect( - swapsUtil.getBaseApiURL(APIType.FEATURE_FLAG, '0x1'), + swapsUtil.getBaseApiURL(swapsUtil.APIType.TRADES, '0x1'), + ).toBeDefined(); + expect( + swapsUtil.getBaseApiURL(swapsUtil.APIType.TOKENS, '0x1'), + ).toBeDefined(); + expect( + swapsUtil.getBaseApiURL(swapsUtil.APIType.TOKEN, '0x1'), + ).toBeDefined(); + expect( + swapsUtil.getBaseApiURL(swapsUtil.APIType.TOP_ASSETS, '0x1'), + ).toBeDefined(); + expect( + swapsUtil.getBaseApiURL(swapsUtil.APIType.FEATURE_FLAG, '0x1'), ).toBeDefined(); expect( - swapsUtil.getBaseApiURL(APIType.AGGREGATOR_METADATA, '0x1'), + swapsUtil.getBaseApiURL(swapsUtil.APIType.AGGREGATOR_METADATA, '0x1'), + ).toBeDefined(); + expect( + swapsUtil.getBaseApiURL(swapsUtil.APIType.GAS_PRICES, '0x1'), ).toBeDefined(); - expect(swapsUtil.getBaseApiURL(APIType.GAS_PRICES, '0x1')).toBeDefined(); expect(() => - swapsUtil.getBaseApiURL('error value' as APIType, '0x1'), + swapsUtil.getBaseApiURL('error value' as swapsUtil.APIType, '0x1'), ).toThrow(); }); }); diff --git a/src/swapsUtil.ts b/src/swapsUtil.ts index 07b497fa..648ea04f 100644 --- a/src/swapsUtil.ts +++ b/src/swapsUtil.ts @@ -37,9 +37,8 @@ import type { SwapsAsset, SwapsToken, TransactionReceipt, + TxParams, } from './SwapsController.types'; -import { APIType } from './SwapsController.types'; -import { TxParams } from './SwapsController.types'; // / // / BEGIN: Lifted from now unexported normalizeTransaction in @metamask/transaction-controller@3.0.0 @@ -94,6 +93,17 @@ export enum SwapsError { SWAPS_ALLOWANCE_TIMEOUT = 'swaps-allowance-timeout', SWAPS_ALLOWANCE_ERROR = 'swaps-allowance-error', } + +export enum APIType { + TRADES = 'TRADES', + TOKENS = 'TOKENS', + TOP_ASSETS = 'TOP_ASSETS', + FEATURE_FLAG = 'FEATURE_FLAG', + AGGREGATOR_METADATA = 'AGGREGATOR_METADATA', + TOKEN = 'TOKEN', + GAS_PRICES = 'GAS_PRICES', +} + // Functions /** * Returns the client ID header. From f9fbd549f35ef2917406389587776d45a2bf9974 Mon Sep 17 00:00:00 2001 From: nikoferro Date: Tue, 20 Aug 2024 10:15:00 +0200 Subject: [PATCH 04/32] fix: test fix --- src/SwapsController.test.ts | 41 +++++++++++++++++++++++-------------- src/SwapsController.ts | 12 +++++++++-- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/SwapsController.test.ts b/src/SwapsController.test.ts index 0a658b0b..f9d55abc 100644 --- a/src/SwapsController.test.ts +++ b/src/SwapsController.test.ts @@ -691,8 +691,10 @@ describe('SwapsController', () => { gasPrice: '20', }; - swapsController.state.quotes = API_TRADES; - swapsController.state.usedGasEstimate = usedGasEstimate; + swapsController.__test__updateState({ + quotes: API_TRADES, + usedGasEstimate, + }); swapsController.updateQuotesWithGasPrice(customGasFee); @@ -704,7 +706,10 @@ describe('SwapsController', () => { const customGasFee = { gasPrice: '10', }; - swapsController.state.usedGasEstimate = null; + + swapsController.__test__updateState({ + usedGasEstimate: null, + }); swapsController.updateQuotesWithGasPrice(customGasFee); @@ -715,17 +720,20 @@ describe('SwapsController', () => { describe('updateSelectedQuoteWithGasLimit', () => { it('should update selected quote with custom gas limit', () => { const customGasLimit = '0x5208'; // 21000 in hex - swapsController.state.topAggId = 'paraswap'; - swapsController.state.quotes = API_TRADES; - swapsController.state.quoteValues = { - paraswap: { - ...swapsController.state.quoteValues!.paraswap, - maxEthFee: '0', + + swapsController.__test__updateState({ + topAggId: 'paraswap', + quotes: API_TRADES, + quoteValues: { + paraswap: { + ...swapsController.state.quoteValues!.paraswap, + maxEthFee: '0', + }, }, - }; - swapsController.state.usedGasEstimate = { - gasPrice: '20', - }; + usedGasEstimate: { + gasPrice: '20', + }, + }); swapsController.updateSelectedQuoteWithGasLimit(customGasLimit); @@ -735,8 +743,11 @@ describe('SwapsController', () => { it('should not update selected quote if topAggId or usedGasEstimate is null', () => { const customGasLimit = '0x5208'; // 21000 in hex - swapsController.state.topAggId = null; - swapsController.state.usedGasEstimate = null; + + swapsController.__test__updateState({ + topAggId: null, + usedGasEstimate: null, + }); swapsController.updateSelectedQuoteWithGasLimit(customGasLimit); diff --git a/src/SwapsController.ts b/src/SwapsController.ts index 16915b8b..a0757906 100644 --- a/src/SwapsController.ts +++ b/src/SwapsController.ts @@ -848,11 +848,19 @@ export default class SwapsController extends BaseController< usedCustomGas ?? usedGasEstimate, customGasLimit, ); - quoteValues[selectedQuote.aggregator].maxEthFee = maxEthFee; + const clonedQuoteValues = { + ...quoteValues, + [selectedQuote.aggregator]: { + ...quoteValues[selectedQuote.aggregator], + maxEthFee, + }, + }; + + clonedQuoteValues[selectedQuote.aggregator].maxEthFee = maxEthFee; this.update((_state) => { _state.topAggId = topAggId; - _state.quoteValues = quoteValues; + _state.quoteValues = clonedQuoteValues; }); } From eec54a68dec8353900a0b885fa3c8ba41c9c9a0f Mon Sep 17 00:00:00 2001 From: nikoferro Date: Tue, 20 Aug 2024 11:23:18 +0200 Subject: [PATCH 05/32] chore: exposing types of actions and events --- src/SwapsController.test.ts | 32 +++-- src/SwapsController.ts | 218 +++++++++++++---------------------- src/SwapsController.types.ts | 101 +++++++++++++++- src/constants.ts | 16 ++- src/swapsUtil.ts | 109 +++++++++++++++++- 5 files changed, 315 insertions(+), 161 deletions(-) diff --git a/src/SwapsController.test.ts b/src/SwapsController.test.ts index f9d55abc..b9347761 100644 --- a/src/SwapsController.test.ts +++ b/src/SwapsController.test.ts @@ -1,10 +1,6 @@ import { ChainId } from '@metamask/controller-utils'; import { GasFeeEstimates } from '@metamask/gas-fee-controller'; -import SwapsController, { - INITIAL_CHAIN_DATA, - isGasFeeStateEthGasPrice, - isGasFeeStateLegacy, -} from './SwapsController'; +import SwapsController from './SwapsController'; import { Quote, SwapsControllerMessenger } from './SwapsController.types'; import * as swapsUtil from './swapsUtil'; @@ -303,7 +299,7 @@ describe('SwapsController', () => { expect(swapsController.state.pollingCyclesLeft).toBe(3); }); - it('should use INITIAL_CHAIN_DATA when chainCache does not have data for the chainId', () => { + it('should use swapsUtil.INITIAL_CHAIN_DATA when chainCache does not have data for the chainId', () => { const chainId = ChainId.aurora; swapsController.configure({ @@ -318,7 +314,7 @@ describe('SwapsController', () => { swapsController.configure({ chainId }); const cachedData = swapsController.state.chainCache[chainId]; - expect(cachedData).toEqual(INITIAL_CHAIN_DATA); + expect(cachedData).toEqual(swapsUtil.INITIAL_CHAIN_DATA); }); describe('provider', () => { @@ -379,17 +375,17 @@ describe('SwapsController', () => { }); swapsController.configure({ chainId: '0x23' }); expect(swapsController.state.chainCache['0x23']).toStrictEqual( - INITIAL_CHAIN_DATA, + swapsUtil.INITIAL_CHAIN_DATA, ); swapsController.configure({ chainId: '0x24' }); expect(swapsController.state.chainCache['0x24']).toStrictEqual( - INITIAL_CHAIN_DATA, + swapsUtil.INITIAL_CHAIN_DATA, ); swapsController.configure({ chainId: '0x291' }); expect(swapsController.state.chainCache['0x291']).toStrictEqual( - INITIAL_CHAIN_DATA, + swapsUtil.INITIAL_CHAIN_DATA, ); }); @@ -410,19 +406,19 @@ describe('SwapsController', () => { }); const chainData23 = { - ...INITIAL_CHAIN_DATA, + ...swapsUtil.INITIAL_CHAIN_DATA, tokensLastFetched: 231, topAssetsLastFetched: 232, aggregatorMetadataLastFetched: 233, }; const chainData24 = { - ...INITIAL_CHAIN_DATA, + ...swapsUtil.INITIAL_CHAIN_DATA, tokensLastFetched: 241, topAssetsLastFetched: 242, aggregatorMetadataLastFetched: 243, }; const chainData0x123 = { - ...INITIAL_CHAIN_DATA, + ...swapsUtil.INITIAL_CHAIN_DATA, tokensLastFetched: 2911, topAssetsLastFetched: 2912, aggregatorMetadataLastFetched: 2913, @@ -1144,7 +1140,7 @@ describe('SwapsController', () => { }; // @ts-expect-error - incomplete type - const result = isGasFeeStateEthGasPrice(gasFeeState); + const result = swapsUtil.isGasFeeStateEthGasPrice(gasFeeState); expect(result).toBe(true); }); @@ -1161,7 +1157,7 @@ describe('SwapsController', () => { }; // @ts-expect-error - incomplete type - const result = isGasFeeStateEthGasPrice(gasFeeState); + const result = swapsUtil.isGasFeeStateEthGasPrice(gasFeeState); expect(result).toBe(false); }); @@ -1189,7 +1185,7 @@ describe('SwapsController', () => { }; // @ts-expect-error - incomplete type - const result = isGasFeeStateLegacy(gasFeeState); + const result = swapsUtil.isGasFeeStateLegacy(gasFeeState); expect(result).toBe(true); }); @@ -1206,7 +1202,7 @@ describe('SwapsController', () => { }; // @ts-expect-error - incomplete type - const result = isGasFeeStateLegacy(gasFeeState); + const result = swapsUtil.isGasFeeStateLegacy(gasFeeState); expect(result).toBe(false); }); @@ -1217,7 +1213,7 @@ describe('SwapsController', () => { }; // @ts-expect-error - incomplete type - const result = isGasFeeStateLegacy(gasFeeState); + const result = swapsUtil.isGasFeeStateLegacy(gasFeeState); expect(result).toBe(false); }); diff --git a/src/SwapsController.ts b/src/SwapsController.ts index a0757906..559384f0 100644 --- a/src/SwapsController.ts +++ b/src/SwapsController.ts @@ -1,5 +1,5 @@ -import { BaseController } from '@metamask/base-controller'; import type { StateMetadata } from '@metamask/base-controller'; +import { BaseController } from '@metamask/base-controller'; import { gweiDecToWEIBN, query, @@ -11,9 +11,6 @@ import type { FetchGasFeeEstimateOptions, GasFeeEstimates, GasFeeState, - GasFeeStateEthGasPrice, - GasFeeStateFeeMarket, - GasFeeStateLegacy, } from '@metamask/gas-fee-controller'; import { GAS_ESTIMATE_TYPES } from '@metamask/gas-fee-controller'; import type { Hex } from '@metamask/utils'; @@ -26,8 +23,6 @@ import * as web3 from 'web3'; import type { APIFetchQuotesMetadata, APIFetchQuotesParams, - ChainCache, - ChainData, CustomEthGasPriceEstimate, CustomGasFee, Quote, @@ -51,7 +46,15 @@ import { fetchTopAssets, fetchTradesInfo, getDefaultSwapsControllerState, + getNewChainCache, getSwapsContractAddress, + INITIAL_CHAIN_DATA, + isCustomEthGasPriceEstimate, + isCustomGasFee, + isEthGasPriceEstimate, + isGasFeeStateEthGasPrice, + isGasFeeStateFeeMarket, + isGasFeeStateLegacy, NATIVE_SWAPS_TOKEN_ADDRESS, OPTIMISM_CHAIN_ID, shouldEnableDirectWrapping, @@ -86,126 +89,29 @@ const metadata: StateMetadata = { config: { persist: false, anonymous: false }, }; -// Functions to determine type of the return value from GasFeeController - -/** - * Checks if the given object is of type GasFeeStateEthGasPrice. - * @param object - The gas fee state to be checked. - * @returns Whether the given object is of type GasFeeStateEthGasPrice. - */ -export function isGasFeeStateEthGasPrice( - object: GasFeeState, -): object is GasFeeStateEthGasPrice { - return object.gasEstimateType === GAS_ESTIMATE_TYPES.ETH_GASPRICE; -} - -/** - * Determines if the given object is of type GasFeeStateFeeMarket based on its 'gasEstimateType'. - * @param object - The gas fee state to be evaluated. - * @returns Whether the object is of type GasFeeStateFeeMarket. - */ -function isGasFeeStateFeeMarket( - object: GasFeeState, -): object is GasFeeStateFeeMarket { - return object.gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET; -} - -/** - * Determines if the given object is of type GasFeeStateLegacy based on its 'gasEstimateType'. - * @param object - The gas fee state to be evaluated. - * @returns Whether the object is of type GasFeeStateLegacy. - */ -export function isGasFeeStateLegacy( - object: GasFeeState, -): object is GasFeeStateLegacy { - return object.gasEstimateType === GAS_ESTIMATE_TYPES.LEGACY; -} - -/** - * Determines if the given object is of type EthGasPriceEstimate. - * @param object - The object to be evaluated. - * @returns Whether the object is of type EthGasPriceEstimate. - */ -function isEthGasPriceEstimate(object: any): object is EthGasPriceEstimate { - return Boolean(object) && object?.gasPrice !== undefined; -} - -/** - * Determines if the given object is of type CustomEthGasPriceEstimate. - * @param object - The object to be evaluated. - * @returns Whether the object is of type CustomEthGasPriceEstimate. - */ -function isCustomEthGasPriceEstimate( - object: any, -): object is CustomEthGasPriceEstimate { - return Boolean(object) && object?.gasPrice !== undefined; -} - -/** - * Determines if the given object is of type CustomGasFee. - * @param object - The object to be evaluated. - * @returns Whether the object is of type CustomGasFee. - */ -function isCustomGasFee(object: any): object is CustomGasFee { - return ( - Boolean(object) && - 'maxFeePerGas' in object && - 'maxPriorityFeePerGas' in object - ); -} - -export const INITIAL_CHAIN_DATA: ChainData = { - aggregatorMetadata: null, - tokens: null, - topAssets: null, - aggregatorMetadataLastFetched: 0, - topAssetsLastFetched: 0, - tokensLastFetched: 0, -}; - -/** - * Gets a new chainCache for a chainId with updated data. - * @param chainCache - Current chainCache from state. - * @param chainId - Current chainId from the config. - * @param data - Data to be updated. - * @returns The new chainCache. - */ -function getNewChainCache( - chainCache: ChainCache, - chainId: Hex, - data: Partial, -): ChainCache { - return { - ...chainCache, - [chainId]: { - ...chainCache?.[chainId], - ...data, - }, - }; -} - export default class SwapsController extends BaseController< typeof controllerName, SwapsControllerState, SwapsControllerMessenger > { - private handle?: NodeJS.Timeout; + #web3: Web3Type; - private web3: Web3Type; + #ethQuery: any; - private ethQuery: any; + #pollCount = 0; - private pollCount = 0; + #abortController?: AbortController; - private readonly mutex = new Mutex(); + #mutex = new Mutex(); - private abortController?: AbortController; + private handle?: NodeJS.Timeout; - private readonly fetchGasFeeEstimates?: ( + // TODO: Remove once GasFeeController exports this action type + readonly #fetchGasFeeEstimates?: ( options?: FetchGasFeeEstimateOptions, ) => Promise; - private readonly fetchEstimatedMultiLayerL1Fee?: ( + readonly #fetchEstimatedMultiLayerL1Fee?: ( eth: any, options: { txParams: TxParams; @@ -219,9 +125,9 @@ export default class SwapsController extends BaseController< */ /* istanbul ignore next */ private async getGasPrice(): Promise { - if (this.fetchGasFeeEstimates) { - const gasFeeState = await this.fetchGasFeeEstimates({ - shouldUpdateState: this.pollCount === 1, + if (this.#fetchGasFeeEstimates) { + const gasFeeState = await this.#fetchGasFeeEstimates({ + shouldUpdateState: this.#pollCount === 1, }); if ( !gasFeeState || @@ -250,7 +156,7 @@ export default class SwapsController extends BaseController< } try { - const gasPrice = await query(this.ethQuery, 'gasPrice'); + const gasPrice = await query(this.#ethQuery, 'gasPrice'); return { gasPrice: weiHexToGweiDec(gasPrice).toString(), }; @@ -515,7 +421,7 @@ export default class SwapsController extends BaseController< contractAddress: string, walletAddress: string, ): Promise { - const contract = new this.web3.eth.Contract(abiERC20, contractAddress); + const contract = new this.#web3.eth.Contract(abiERC20, contractAddress); const allowanceTimeout = new Promise((_, reject) => { setTimeout(() => { reject(new Error(SwapsError.SWAPS_ALLOWANCE_TIMEOUT)); @@ -556,7 +462,7 @@ export default class SwapsController extends BaseController< to: tradeTxParams.to, value: tradeTxParams.value, } as TxParams, - this.ethQuery, + this.#ethQuery, ), gasTimeout, ]); @@ -569,13 +475,13 @@ export default class SwapsController extends BaseController< private async pollForNewQuotesWithThreshold( fetchThreshold = 0, ): Promise { - this.pollCount += 1; + this.#pollCount += 1; if (this.handle) { clearTimeout(this.handle); this.handle = undefined; } - if (this.pollCount < Number(this.state.config.pollCountLimit) + 1) { + if (this.#pollCount < Number(this.state.config.pollCountLimit) + 1) { if (!this.state.isInPolling) { this.update((_state) => { _state.isInPolling = true; @@ -586,7 +492,7 @@ export default class SwapsController extends BaseController< this.update((_state) => { _state.pollingCyclesLeft = - _state.config.pollCountLimit - this.pollCount; + _state.config.pollCountLimit - this.#pollCount; }); if (threshold && nextQuotesState?.quoteRefreshSeconds) { @@ -664,8 +570,8 @@ export default class SwapsController extends BaseController< const { clientId, chainId } = this.state.config; try { /** We need to abort quotes fetch if stopPollingAndResetState is called while getting quotes */ - this.abortController = new AbortController(); - const { signal } = this.abortController; + this.#abortController = new AbortController(); + const { signal } = this.#abortController; let quotes: { [key: string]: Quote } = await fetchTradesInfo( fetchParams, signal, @@ -681,9 +587,9 @@ export default class SwapsController extends BaseController< // Fetch an L1 fee for each quote on Optimism. await Promise.all( Object.values(quotes).map(async (quote) => { - if (quote.trade && this.fetchEstimatedMultiLayerL1Fee) { + if (quote.trade && this.#fetchEstimatedMultiLayerL1Fee) { const multiLayerL1TradeFeeTotal = - await this.fetchEstimatedMultiLayerL1Fee(this.ethQuery, { + await this.#fetchEstimatedMultiLayerL1Fee(this.#ethQuery, { txParams: quote.trade, chainId, }); @@ -805,8 +711,48 @@ export default class SwapsController extends BaseController< }, }); - this.fetchGasFeeEstimates = opts.fetchGasFeeEstimates; - this.fetchEstimatedMultiLayerL1Fee = opts.fetchEstimatedMultiLayerL1Fee; + this.messagingSystem.registerActionHandler( + `SwapsController:updateQuotesWithGasPrice`, + this.updateQuotesWithGasPrice.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:updateSelectedQuoteWithGasLimit`, + this.updateSelectedQuoteWithGasLimit.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:startFetchAndSetQuotes`, + this.startFetchAndSetQuotes.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:fetchTokenWithCache`, + this.fetchTokenWithCache.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:fetchTopAssetsWithCache`, + this.fetchTopAssetsWithCache.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:fetchAggregatorMetadataWithCache`, + this.fetchAggregatorMetadataWithCache.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:stopPollingAndResetState`, + this.stopPollingAndResetState.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:configure`, + this.configure.bind(this), + ); + + this.#fetchGasFeeEstimates = opts.fetchGasFeeEstimates; + this.#fetchEstimatedMultiLayerL1Fee = opts.fetchEstimatedMultiLayerL1Fee; } /** @@ -881,7 +827,7 @@ export default class SwapsController extends BaseController< // Every time we get a new request that is not from the polling, // we reset the poll count so we can poll for up to three more sets // of quotes with these new params. - this.pollCount = 0; + this.#pollCount = 0; this.update((_state) => { _state.fetchParams = fetchParams; @@ -897,7 +843,7 @@ export default class SwapsController extends BaseController< /** * Fetches the tokens and updates the state with them. */ - async fetchTokenWithCache() { + public async fetchTokenWithCache() { const { chainId, clientId, fetchTokensThreshold, supportedChainIds } = this.state.config; const { tokens, tokensLastFetched } = this.state; @@ -907,7 +853,7 @@ export default class SwapsController extends BaseController< } if (!tokens || fetchTokensThreshold < Date.now() - tokensLastFetched) { - const releaseLock = await this.mutex.acquire(); + const releaseLock = await this.#mutex.acquire(); try { const newTokens = await fetchTokens(chainId, clientId); this.update((_state) => { @@ -947,7 +893,7 @@ export default class SwapsController extends BaseController< !topAssets || fetchTopAssetsThreshold < Date.now() - topAssetsLastFetched ) { - const releaseLock = await this.mutex.acquire(); + const releaseLock = await this.#mutex.acquire(); try { const newTopAssets = await fetchTopAssets(chainId, clientId); const data = { @@ -1000,7 +946,7 @@ export default class SwapsController extends BaseController< fetchAggregatorMetadataThreshold < Date.now() - aggregatorMetadataLastFetched ) { - const releaseLock = await this.mutex.acquire(); + const releaseLock = await this.#mutex.acquire(); try { const newAggregatorMetada = await fetchAggregatorMetadata( chainId, @@ -1052,9 +998,9 @@ export default class SwapsController extends BaseController< description: null, }, ) { - this.abortController && this.abortController.abort(); + this.#abortController && this.#abortController.abort(); this.handle && clearTimeout(this.handle); - this.pollCount = Number(this.state.config.pollCountLimit) + 1; + this.#pollCount = Number(this.state.config.pollCountLimit) + 1; this.update((_state) => { const defaultState = getDefaultSwapsControllerState(); Object.keys(defaultState).forEach((key) => { @@ -1112,8 +1058,8 @@ export default class SwapsController extends BaseController< this.#setChainId(chainId); } if (provider) { - this.web3 = new Web3(provider); - this.ethQuery = new EthQuery(provider); + this.#web3 = new Web3(provider); + this.#ethQuery = new EthQuery(provider); } this.update((_state) => { _state.config = { diff --git a/src/SwapsController.types.ts b/src/SwapsController.types.ts index 4fa108a8..6686e9c4 100644 --- a/src/SwapsController.types.ts +++ b/src/SwapsController.types.ts @@ -1,4 +1,8 @@ -import type { RestrictedControllerMessenger } from '@metamask/base-controller'; +import type { + RestrictedControllerMessenger, + ControllerStateChangeEvent, + ControllerGetStateAction, +} from '@metamask/base-controller'; import type EthQuery from '@metamask/eth-query'; import type { EthGasPriceEstimate, @@ -7,6 +11,7 @@ import type { } from '@metamask/gas-fee-controller'; import type { Hex } from '@metamask/utils'; +import type SwapsController from './SwapsController'; import type { controllerName, SwapsError } from './swapsUtil'; export type SwapsAsset = { @@ -317,20 +322,46 @@ export type SwapsControllerState = { config: SwapsConfig; }; +/** + * The action that fetches the state of the {@link SwapsController}. + */ +export type SwapsControllerGetStateAction = ControllerGetStateAction< + typeof controllerName, + SwapsControllerState +>; + +/** + * The event that {@link SwapsController} can emit. + */ +export type SwapsControllerStateChangeEvent = ControllerStateChangeEvent< + typeof controllerName, + SwapsControllerState +>; + /** * The external actions available to the {@link SwapsController}. + * TODO: Add GasFeeControllerFetchGasFeeEstimates once GasFeeController exports this action type */ export type AllowedActions = never; /** * The internal actions available to the SwapsController. */ -export type SwapsControllerActions = never; +export type SwapsControllerActions = + | SwapsControllerGetStateAction + | SwapsControllerUpdateQuotesWithGasPrice + | SwapsControllerUpdateSelectedQuoteWithGasLimit + | SwapsControllerStartFetchAndSetQuotes + | SwapsControllerFetchTokenWithCache + | SwapsControllerFetchTopAssetsWithCache + | SwapsControllerFetchAggregatorMetadataWithCache + | SwapsControllerStopPollingAndResetState + | SwapsControllerConfigure; /** * The events that the SwapsController can emit. */ -export type SwapsControllerEvents = never; +export type SwapsControllerEvents = SwapsControllerStateChangeEvent; /** * The messenger for the SwapsController. @@ -355,3 +386,67 @@ export type SwapsControllerOptions = { ) => Promise; messenger: SwapsControllerMessenger; }; + +/** + * The action that updates quotes with gas price {@link SwapsController}. + */ +export type SwapsControllerUpdateQuotesWithGasPrice = { + type: `SwapsController:updateQuotesWithGasPrice`; + handler: SwapsController['updateQuotesWithGasPrice']; +}; + +/** + * The action that updates the selected quote with gas limit {@link SwapsController}. + */ +export type SwapsControllerUpdateSelectedQuoteWithGasLimit = { + type: `SwapsController:updateSelectedQuoteWithGasLimit`; + handler: SwapsController['updateSelectedQuoteWithGasLimit']; +}; + +/** + * The action that starts fetching and setting quotes {@link SwapsController}. + */ +export type SwapsControllerStartFetchAndSetQuotes = { + type: `SwapsController:startFetchAndSetQuotes`; + handler: SwapsController['startFetchAndSetQuotes']; +}; + +/** + * The action that fetches a token with cache {@link SwapsController}. + */ +export type SwapsControllerFetchTokenWithCache = { + type: `SwapsController:fetchTokenWithCache`; + handler: SwapsController['fetchTokenWithCache']; +}; + +/** + * The action that fetches top assets with cache {@link SwapsController}. + */ +export type SwapsControllerFetchTopAssetsWithCache = { + type: `SwapsController:fetchTopAssetsWithCache`; + handler: SwapsController['fetchTopAssetsWithCache']; +}; + +/** + * The action that fetches aggregator metadata with cache {@link SwapsController}. + */ +export type SwapsControllerFetchAggregatorMetadataWithCache = { + type: `SwapsController:fetchAggregatorMetadataWithCache`; + handler: SwapsController['fetchAggregatorMetadataWithCache']; +}; + +/** + * The action that stops polling and resets state {@link SwapsController}. + */ +export type SwapsControllerStopPollingAndResetState = { + type: `SwapsController:stopPollingAndResetState`; + handler: SwapsController['stopPollingAndResetState']; +}; + +/** + * The action that configures the SwapsController {@link SwapsController}. + */ +export type SwapsControllerConfigure = { + type: `SwapsController:configure`; + handler: SwapsController['configure']; +}; diff --git a/src/constants.ts b/src/constants.ts index 916fffd0..ec0b9ae6 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,7 +1,19 @@ import { toHex } from '@metamask/controller-utils'; -import { INITIAL_CHAIN_DATA } from './SwapsController'; -import type { SwapsToken, SwapsControllerState } from './SwapsController.types'; +import type { + SwapsToken, + SwapsControllerState, + ChainData, +} from './SwapsController.types'; + +export const INITIAL_CHAIN_DATA: ChainData = { + aggregatorMetadata: null, + tokens: null, + topAssets: null, + aggregatorMetadataLastFetched: 0, + topAssetsLastFetched: 0, + tokensLastFetched: 0, +}; //* Chain IDs and names diff --git a/src/swapsUtil.ts b/src/swapsUtil.ts index 648ea04f..de905ac2 100644 --- a/src/swapsUtil.ts +++ b/src/swapsUtil.ts @@ -1,16 +1,25 @@ import { + BNToHex, convertHexToDecimal, handleFetch, - timeoutFetch, - BNToHex, query, + timeoutFetch, } from '@metamask/controller-utils'; +import type { + EthGasPriceEstimate, + GasFeeState, + GasFeeStateEthGasPrice, + GasFeeStateFeeMarket, + GasFeeStateLegacy, +} from '@metamask/gas-fee-controller'; +import { GAS_ESTIMATE_TYPES } from '@metamask/gas-fee-controller'; import type { TransactionParams } from '@metamask/transaction-controller'; import type { Hex } from '@metamask/utils'; import { add0x } from '@metamask/utils'; import { BigNumber } from 'bignumber.js'; import { BN } from 'bn.js'; +// eslint-disable-next-line import/order import { ALLOWED_CONTRACT_ADDRESSES, API_BASE_URL, @@ -26,9 +35,14 @@ import { SWAPS_WRAPPED_TOKENS_ADDRESSES, TOKEN_TRANSFER_LOG_TOPIC_HASH, } from './constants'; + import type { APIAggregatorMetadata, APIFetchQuotesParams, + ChainCache, + ChainData, + CustomEthGasPriceEstimate, + CustomGasFee, FeatureFlags, NetworkFeatureFlags, NetworksFeatureStatus, @@ -783,3 +797,94 @@ export function constructTxParams({ } return normalizeTransaction(txParams); } + +// Functions to determine type of the return value from GasFeeController + +/** + * Checks if the given object is of type GasFeeStateEthGasPrice. + * @param object - The gas fee state to be checked. + * @returns Whether the given object is of type GasFeeStateEthGasPrice. + */ +export function isGasFeeStateEthGasPrice( + object: GasFeeState, +): object is GasFeeStateEthGasPrice { + return object.gasEstimateType === GAS_ESTIMATE_TYPES.ETH_GASPRICE; +} + +/** + * Determines if the given object is of type GasFeeStateFeeMarket based on its 'gasEstimateType'. + * @param object - The gas fee state to be evaluated. + * @returns Whether the object is of type GasFeeStateFeeMarket. + */ +export function isGasFeeStateFeeMarket( + object: GasFeeState, +): object is GasFeeStateFeeMarket { + return object.gasEstimateType === GAS_ESTIMATE_TYPES.FEE_MARKET; +} + +/** + * Determines if the given object is of type GasFeeStateLegacy based on its 'gasEstimateType'. + * @param object - The gas fee state to be evaluated. + * @returns Whether the object is of type GasFeeStateLegacy. + */ +export function isGasFeeStateLegacy( + object: GasFeeState, +): object is GasFeeStateLegacy { + return object.gasEstimateType === GAS_ESTIMATE_TYPES.LEGACY; +} + +/** + * Determines if the given object is of type EthGasPriceEstimate. + * @param object - The object to be evaluated. + * @returns Whether the object is of type EthGasPriceEstimate. + */ +export function isEthGasPriceEstimate( + object: any, +): object is EthGasPriceEstimate { + return Boolean(object) && object?.gasPrice !== undefined; +} + +/** + * Determines if the given object is of type CustomEthGasPriceEstimate. + * @param object - The object to be evaluated. + * @returns Whether the object is of type CustomEthGasPriceEstimate. + */ +export function isCustomEthGasPriceEstimate( + object: any, +): object is CustomEthGasPriceEstimate { + return Boolean(object) && object?.gasPrice !== undefined; +} + +/** + * Determines if the given object is of type CustomGasFee. + * @param object - The object to be evaluated. + * @returns Whether the object is of type CustomGasFee. + */ +export function isCustomGasFee(object: any): object is CustomGasFee { + return ( + Boolean(object) && + 'maxFeePerGas' in object && + 'maxPriorityFeePerGas' in object + ); +} + +/** + * Gets a new chainCache for a chainId with updated data. + * @param chainCache - Current chainCache from state. + * @param chainId - Current chainId from the config. + * @param data - Data to be updated. + * @returns The new chainCache. + */ +export function getNewChainCache( + chainCache: ChainCache, + chainId: Hex, + data: Partial, +): ChainCache { + return { + ...chainCache, + [chainId]: { + ...chainCache?.[chainId], + ...data, + }, + }; +} From 7a89c1dde1e716d7ae63017c3a9033f7ba1e7d60 Mon Sep 17 00:00:00 2001 From: nikoferro Date: Thu, 22 Aug 2024 11:59:24 +0200 Subject: [PATCH 06/32] fix: removing provider from state since its not serializable --- src/SwapsController.test.ts | 9 +++++---- src/SwapsController.ts | 31 ++++++++++++++++++------------- src/SwapsController.types.ts | 1 - src/constants.ts | 1 - 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/SwapsController.test.ts b/src/SwapsController.test.ts index b9347761..79e73fc7 100644 --- a/src/SwapsController.test.ts +++ b/src/SwapsController.test.ts @@ -163,6 +163,7 @@ describe('SwapsController', () => { estimatedGasFeeTimeBounds: {}, gasEstimateType: 'none', })); + fetchEstimatedMultiLayerL1Fee = jest.fn().mockImplementation(() => '0x0'); swapsController = new SwapsController( { @@ -216,7 +217,6 @@ describe('SwapsController', () => { fetchAggregatorMetadataThreshold: 1000 * 60 * 60 * 24 * 15, fetchTokensThreshold: 1000 * 60 * 60 * 24, fetchTopAssetsThreshold: 1000 * 60 * 30, - provider: undefined, clientId: undefined, }); }); @@ -281,7 +281,6 @@ describe('SwapsController', () => { fetchAggregatorMetadataThreshold: 1000 * 60 * 60 * 24 * 15, fetchTokensThreshold: 1000 * 60 * 60 * 24, fetchTopAssetsThreshold: 1000 * 60 * 30, - provider: undefined, clientId: undefined, }, }); @@ -325,11 +324,13 @@ describe('SwapsController', () => { chainId: '0x1', rpcUrl: 'test', }; - expect(swapsController.state.config.provider).toBeUndefined(); + expect(swapsController.web3).toBeUndefined(); + expect(swapsController.ethQuery).toBeUndefined(); swapsController.configure({ provider }); - expect(swapsController.state.config.provider.name).toBe(provider.name); + expect(swapsController.web3).toBeDefined(); + expect(swapsController.ethQuery).toBeDefined(); }); }); diff --git a/src/SwapsController.ts b/src/SwapsController.ts index 559384f0..c683b7cc 100644 --- a/src/SwapsController.ts +++ b/src/SwapsController.ts @@ -94,9 +94,9 @@ export default class SwapsController extends BaseController< SwapsControllerState, SwapsControllerMessenger > { - #web3: Web3Type; + web3: Web3Type; - #ethQuery: any; + ethQuery: any; #pollCount = 0; @@ -156,7 +156,7 @@ export default class SwapsController extends BaseController< } try { - const gasPrice = await query(this.#ethQuery, 'gasPrice'); + const gasPrice = await query(this.ethQuery, 'gasPrice'); return { gasPrice: weiHexToGweiDec(gasPrice).toString(), }; @@ -421,7 +421,7 @@ export default class SwapsController extends BaseController< contractAddress: string, walletAddress: string, ): Promise { - const contract = new this.#web3.eth.Contract(abiERC20, contractAddress); + const contract = new this.web3.eth.Contract(abiERC20, contractAddress); const allowanceTimeout = new Promise((_, reject) => { setTimeout(() => { reject(new Error(SwapsError.SWAPS_ALLOWANCE_TIMEOUT)); @@ -462,7 +462,7 @@ export default class SwapsController extends BaseController< to: tradeTxParams.to, value: tradeTxParams.value, } as TxParams, - this.#ethQuery, + this.ethQuery, ), gasTimeout, ]); @@ -589,7 +589,7 @@ export default class SwapsController extends BaseController< Object.values(quotes).map(async (quote) => { if (quote.trade && this.#fetchEstimatedMultiLayerL1Fee) { const multiLayerL1TradeFeeTotal = - await this.#fetchEstimatedMultiLayerL1Fee(this.#ethQuery, { + await this.#fetchEstimatedMultiLayerL1Fee(this.ethQuery, { txParams: quote.trade, chainId, }); @@ -1052,19 +1052,24 @@ export default class SwapsController extends BaseController< }); } - public configure(config: Partial) { - const { chainId, provider } = config; - if (chainId) { - this.#setChainId(chainId); + #setProvider(provider: any) { + this.web3 = new Web3(provider); + this.ethQuery = new EthQuery(provider); + } + + public configure(config: Partial & { provider?: any }) { + const { provider, ...serializableConfig } = config; + if (serializableConfig.chainId) { + this.#setChainId(serializableConfig.chainId); } if (provider) { - this.#web3 = new Web3(provider); - this.#ethQuery = new EthQuery(provider); + this.#setProvider(provider); } + this.update((_state) => { _state.config = { ..._state.config, - ...config, + ...serializableConfig, }; }); } diff --git a/src/SwapsController.types.ts b/src/SwapsController.types.ts index 6686e9c4..bddda2c3 100644 --- a/src/SwapsController.types.ts +++ b/src/SwapsController.types.ts @@ -292,7 +292,6 @@ export type SwapsConfig = { fetchAggregatorMetadataThreshold: number; fetchTokensThreshold: number; fetchTopAssetsThreshold: number; - provider: any; chainId: Hex; supportedChainIds: Hex[]; }; diff --git a/src/constants.ts b/src/constants.ts index ec0b9ae6..25a790a0 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -281,7 +281,6 @@ export const getDefaultSwapsControllerState = (): SwapsControllerState => ({ fetchAggregatorMetadataThreshold: 1000 * 60 * 60 * 24 * 15, fetchTokensThreshold: 1000 * 60 * 60 * 24, fetchTopAssetsThreshold: 1000 * 60 * 30, - provider: undefined, chainId: '0x1', supportedChainIds: [ ETH_CHAIN_ID, From 9087137879c446e36f2e78b2871b079406cf8777 Mon Sep 17 00:00:00 2001 From: nikoferro Date: Thu, 22 Aug 2024 13:28:45 +0200 Subject: [PATCH 07/32] fix: fix for resetting state while keeping the current chain and last fetched config --- src/SwapsController.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/SwapsController.ts b/src/SwapsController.ts index c683b7cc..587a9504 100644 --- a/src/SwapsController.ts +++ b/src/SwapsController.ts @@ -496,7 +496,6 @@ export default class SwapsController extends BaseController< }); if (threshold && nextQuotesState?.quoteRefreshSeconds) { - // this.update({ ...this.state, ...nextQuotesState, usedGasEstimate }); this.update((_state) => { _state.quotes = nextQuotesState.quotes ?? _state.quotes; _state.quotesLastFetched = nextQuotesState.quotesLastFetched ?? 0; @@ -1002,11 +1001,22 @@ export default class SwapsController extends BaseController< this.handle && clearTimeout(this.handle); this.#pollCount = Number(this.state.config.pollCountLimit) + 1; this.update((_state) => { + const currentState = { ..._state }; const defaultState = getDefaultSwapsControllerState(); Object.keys(defaultState).forEach((key) => { const typedKey = key as keyof typeof defaultState; (_state as any)[typedKey] = defaultState[typedKey]; }); + _state.isInPolling = false; + _state.config = currentState.config; + _state.tokensLastFetched = currentState.tokensLastFetched; + _state.topAssetsLastFetched = currentState.topAssetsLastFetched; + _state.aggregatorMetadataLastFetched = + currentState.aggregatorMetadataLastFetched; + _state.tokens = currentState.tokens; + _state.topAssets = currentState.topAssets; + _state.aggregatorMetadata = currentState.aggregatorMetadata; + _state.chainCache = currentState.chainCache; _state.error.key = error.key; _state.error.description = error.description; }); From edf5bb57a236ce20fbec3b482bde4c200288c319 Mon Sep 17 00:00:00 2001 From: nikoferro Date: Thu, 22 Aug 2024 13:44:51 +0200 Subject: [PATCH 08/32] chore: export controller types --- src/SwapsController.test.ts | 2 +- src/SwapsController.ts | 26 +++++++++++----------- src/constants.ts | 6 +---- src/index.ts | 19 ++++++++++++++++ src/swapsUtil.test.ts | 6 +---- src/swapsUtil.ts | 2 +- src/{SwapsController.types.ts => types.ts} | 0 7 files changed, 36 insertions(+), 25 deletions(-) rename src/{SwapsController.types.ts => types.ts} (100%) diff --git a/src/SwapsController.test.ts b/src/SwapsController.test.ts index 79e73fc7..19ab8a44 100644 --- a/src/SwapsController.test.ts +++ b/src/SwapsController.test.ts @@ -1,7 +1,7 @@ import { ChainId } from '@metamask/controller-utils'; import { GasFeeEstimates } from '@metamask/gas-fee-controller'; import SwapsController from './SwapsController'; -import { Quote, SwapsControllerMessenger } from './SwapsController.types'; +import { Quote, SwapsControllerMessenger } from './types'; import * as swapsUtil from './swapsUtil'; const API_TRADES: { diff --git a/src/SwapsController.ts b/src/SwapsController.ts index 587a9504..655028ef 100644 --- a/src/SwapsController.ts +++ b/src/SwapsController.ts @@ -20,19 +20,6 @@ import abiERC20 from 'human-standard-token-abi'; import type { Web3 as Web3Type } from 'web3'; import * as web3 from 'web3'; -import type { - APIFetchQuotesMetadata, - APIFetchQuotesParams, - CustomEthGasPriceEstimate, - CustomGasFee, - Quote, - QuoteValues, - SwapsConfig, - SwapsControllerMessenger, - SwapsControllerOptions, - SwapsControllerState, - TxParams, -} from './SwapsController.types'; import { calcTokenAmount, calculateGasEstimateWithRefund, @@ -60,6 +47,19 @@ import { shouldEnableDirectWrapping, SwapsError, } from './swapsUtil'; +import type { + APIFetchQuotesMetadata, + APIFetchQuotesParams, + CustomEthGasPriceEstimate, + CustomGasFee, + Quote, + QuoteValues, + SwapsConfig, + SwapsControllerMessenger, + SwapsControllerOptions, + SwapsControllerState, + TxParams, +} from './types'; // Hack to fix the issue with the web3 import that works different in app vs tests const Web3 = web3.Web3 === undefined ? web3.default : web3.Web3; diff --git a/src/constants.ts b/src/constants.ts index 25a790a0..8f5b4384 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,10 +1,6 @@ import { toHex } from '@metamask/controller-utils'; -import type { - SwapsToken, - SwapsControllerState, - ChainData, -} from './SwapsController.types'; +import type { SwapsToken, SwapsControllerState, ChainData } from './types'; export const INITIAL_CHAIN_DATA: ChainData = { aggregatorMetadata: null, diff --git a/src/index.ts b/src/index.ts index 94f16e6a..6575d434 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,23 @@ import SwapsController from './SwapsController'; export * as swapsUtils from './swapsUtil'; +export type { + SwapsConfig, + SwapsControllerState, + SwapsControllerGetStateAction, + SwapsControllerStateChangeEvent, + SwapsControllerActions, + SwapsControllerEvents, + SwapsControllerMessenger, + SwapsControllerOptions, + SwapsControllerUpdateQuotesWithGasPrice, + SwapsControllerUpdateSelectedQuoteWithGasLimit, + SwapsControllerStartFetchAndSetQuotes, + SwapsControllerFetchTokenWithCache, + SwapsControllerFetchTopAssetsWithCache, + SwapsControllerFetchAggregatorMetadataWithCache, + SwapsControllerStopPollingAndResetState, + SwapsControllerConfigure, +} from './types'; + export default SwapsController; diff --git a/src/swapsUtil.test.ts b/src/swapsUtil.test.ts index f30cd52a..4149c9c2 100644 --- a/src/swapsUtil.test.ts +++ b/src/swapsUtil.test.ts @@ -3,11 +3,7 @@ import { BigNumber } from 'bignumber.js'; import { BNToHex, query, toHex } from '@metamask/controller-utils'; import { add0x } from '@metamask/utils'; import { BN } from 'bn.js'; -import type { - QuoteValues, - SwapsToken, - TxParams, -} from './SwapsController.types'; +import type { QuoteValues, SwapsToken, TxParams } from './types'; import * as swapsUtil from './swapsUtil'; /** diff --git a/src/swapsUtil.ts b/src/swapsUtil.ts index de905ac2..e7528000 100644 --- a/src/swapsUtil.ts +++ b/src/swapsUtil.ts @@ -52,7 +52,7 @@ import type { SwapsToken, TransactionReceipt, TxParams, -} from './SwapsController.types'; +} from './types'; // / // / BEGIN: Lifted from now unexported normalizeTransaction in @metamask/transaction-controller@3.0.0 diff --git a/src/SwapsController.types.ts b/src/types.ts similarity index 100% rename from src/SwapsController.types.ts rename to src/types.ts From bcb4f89fd36106d3c4d6d62b69c14f6464633ced Mon Sep 17 00:00:00 2001 From: Nicolas Ferro Date: Fri, 23 Aug 2024 11:13:52 +0200 Subject: [PATCH 09/32] Update src/types.ts Co-authored-by: Jongsun Suh --- src/types.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/types.ts b/src/types.ts index bddda2c3..d46a73b1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -285,16 +285,6 @@ export type CustomGasFee = { selected?: 'low' | 'medium' | 'high'; }; -export type SwapsConfig = { - clientId?: string; - maxGasLimit: number; - pollCountLimit: number; - fetchAggregatorMetadataThreshold: number; - fetchTokensThreshold: number; - fetchTopAssetsThreshold: number; - chainId: Hex; - supportedChainIds: Hex[]; -}; export type SwapsControllerState = { quotes: { [key: string]: Quote }; From 7405e7e9450e39a0db4fc6165fdaefb83061425e Mon Sep 17 00:00:00 2001 From: Nicolas Ferro Date: Fri, 23 Aug 2024 11:14:06 +0200 Subject: [PATCH 10/32] Update src/types.ts Co-authored-by: Jongsun Suh --- src/types.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index d46a73b1..d32d7381 100644 --- a/src/types.ts +++ b/src/types.ts @@ -308,7 +308,6 @@ export type SwapsControllerState = { topAssets: null | SwapsAsset[]; topAssetsLastFetched: number; chainCache: ChainCache; - config: SwapsConfig; }; /** From 19c845559aaefb7c35285fe019867d92bcca6f39 Mon Sep 17 00:00:00 2001 From: Nicolas Ferro Date: Fri, 23 Aug 2024 11:14:16 +0200 Subject: [PATCH 11/32] Update src/types.ts Co-authored-by: Jongsun Suh --- src/types.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/types.ts b/src/types.ts index d32d7381..577ff52a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -363,6 +363,14 @@ export type SwapsControllerMessenger = RestrictedControllerMessenger< >; export type SwapsControllerOptions = { + clientId?: string; + maxGasLimit: number; + pollCountLimit: number; + fetchAggregatorMetadataThreshold: number; + fetchTokensThreshold: number; + fetchTopAssetsThreshold: number; + chainId: Hex; + supportedChainIds: Hex[]; // TODO: Remove once GasFeeController exports this action type fetchGasFeeEstimates?: () => Promise; fetchEstimatedMultiLayerL1Fee?: ( @@ -373,6 +381,7 @@ export type SwapsControllerOptions = { }, ) => Promise; messenger: SwapsControllerMessenger; + state: Partial }; /** From e8e32af890f3408d0f928ffd25cbc997af1c2dd3 Mon Sep 17 00:00:00 2001 From: Nicolas Ferro Date: Fri, 23 Aug 2024 11:15:54 +0200 Subject: [PATCH 12/32] Update src/SwapsController.ts Co-authored-by: Jongsun Suh --- src/SwapsController.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/SwapsController.ts b/src/SwapsController.ts index 655028ef..cacfe963 100644 --- a/src/SwapsController.ts +++ b/src/SwapsController.ts @@ -745,10 +745,6 @@ export default class SwapsController extends BaseController< this.stopPollingAndResetState.bind(this), ); - this.messagingSystem.registerActionHandler( - `SwapsController:configure`, - this.configure.bind(this), - ); this.#fetchGasFeeEstimates = opts.fetchGasFeeEstimates; this.#fetchEstimatedMultiLayerL1Fee = opts.fetchEstimatedMultiLayerL1Fee; From ae687e86565814aa0e4013cf1f226fae3c88b8bb Mon Sep 17 00:00:00 2001 From: Nicolas Ferro Date: Fri, 23 Aug 2024 11:16:06 +0200 Subject: [PATCH 13/32] Update src/constants.ts Co-authored-by: Jongsun Suh --- src/constants.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 8f5b4384..590e38e8 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -271,20 +271,4 @@ export const getDefaultSwapsControllerState = (): SwapsControllerState => ({ chainCache: { '0x1': INITIAL_CHAIN_DATA, }, - config: { - maxGasLimit: 2500000, - pollCountLimit: 3, - fetchAggregatorMetadataThreshold: 1000 * 60 * 60 * 24 * 15, - fetchTokensThreshold: 1000 * 60 * 60 * 24, - fetchTopAssetsThreshold: 1000 * 60 * 30, - chainId: '0x1', - supportedChainIds: [ - ETH_CHAIN_ID, - BSC_CHAIN_ID, - SWAPS_TESTNET_CHAIN_ID, - POLYGON_CHAIN_ID, - AVALANCHE_CHAIN_ID, - ], - clientId: undefined, - }, }); From cd88c0cf955eacd7276c1b38467105846b793788 Mon Sep 17 00:00:00 2001 From: Nicolas Ferro Date: Fri, 23 Aug 2024 11:16:13 +0200 Subject: [PATCH 14/32] Update src/types.ts Co-authored-by: Jongsun Suh --- src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index 577ff52a..a1446fbf 100644 --- a/src/types.ts +++ b/src/types.ts @@ -292,7 +292,7 @@ export type SwapsControllerState = { fetchParamsMetaData: APIFetchQuotesMetadata; topAggSavings: QuoteSavings | null; quotesLastFetched: null | number; - error: { key: null | SwapsError; description: null | string }; + error: { key: null | keyof typeof SwapsError; description: null | string }; topAggId: null | string; isInPolling: boolean; pollingCyclesLeft: number; From 762db095d2e2cc4bb97edb54e6143e6916d36b5c Mon Sep 17 00:00:00 2001 From: Nicolas Ferro Date: Fri, 23 Aug 2024 11:16:21 +0200 Subject: [PATCH 15/32] Update src/swapsUtil.ts Co-authored-by: Jongsun Suh --- src/swapsUtil.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/swapsUtil.ts b/src/swapsUtil.ts index e7528000..6f56c7a4 100644 --- a/src/swapsUtil.ts +++ b/src/swapsUtil.ts @@ -735,7 +735,7 @@ export function calcTokenAmount(value: number | BigNumber, decimals: number) { * @param ethQuery - The ethQuery object. * @returns Promise resolving to an object containing gas and gasPrice. */ -export async function estimateGas(transaction: TxParams, ethQuery: any) { +export async function estimateGas(transaction: Omit & Partial>, ethQuery: any) { const estimatedTransaction = { ...transaction }; const { value, data } = estimatedTransaction; const { gasLimit } = await query(ethQuery, 'getBlockByNumber', [ From 9cbf039d1bc31b382285ca23636100f0763ad28f Mon Sep 17 00:00:00 2001 From: Nicolas Ferro Date: Fri, 23 Aug 2024 11:16:31 +0200 Subject: [PATCH 16/32] Update src/SwapsController.ts Co-authored-by: Jongsun Suh --- src/SwapsController.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SwapsController.ts b/src/SwapsController.ts index cacfe963..b6a59103 100644 --- a/src/SwapsController.ts +++ b/src/SwapsController.ts @@ -999,8 +999,8 @@ export default class SwapsController extends BaseController< this.update((_state) => { const currentState = { ..._state }; const defaultState = getDefaultSwapsControllerState(); - Object.keys(defaultState).forEach((key) => { - const typedKey = key as keyof typeof defaultState; + getKnownPropertyTypes(defaultState).forEach((key) => { + const typedKey = key; (_state as any)[typedKey] = defaultState[typedKey]; }); _state.isInPolling = false; From 70ccc6956cb52942b590467d8cb7f66cbfad55aa Mon Sep 17 00:00:00 2001 From: Nicolas Ferro Date: Fri, 23 Aug 2024 11:16:46 +0200 Subject: [PATCH 17/32] Update src/SwapsController.ts Co-authored-by: Jongsun Suh --- src/SwapsController.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/SwapsController.ts b/src/SwapsController.ts index b6a59103..1fbfda82 100644 --- a/src/SwapsController.ts +++ b/src/SwapsController.ts @@ -1052,10 +1052,11 @@ export default class SwapsController extends BaseController< _state.tokens = cachedData.tokens; _state.topAssets = cachedData.topAssets; _state.aggregatorMetadataLastFetched = - cachedData.aggregatorMetadataLastFetched; - _state.topAssetsLastFetched = cachedData.topAssetsLastFetched; - _state.tokensLastFetched = cachedData.tokensLastFetched; - }); + #setProvider(provider: Provider) { + // @ts-expect-error TODO: align `Web3` with EIP-1193 provider + this.web3 = new Web3(provider); + this.ethQuery = new EthQuery(provider); + } } #setProvider(provider: any) { From 6f1f627de46eb10a564ae3d70be70af88e9cb1fb Mon Sep 17 00:00:00 2001 From: Nicolas Ferro Date: Fri, 23 Aug 2024 11:16:57 +0200 Subject: [PATCH 18/32] Update src/SwapsController.ts Co-authored-by: Jongsun Suh --- src/SwapsController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SwapsController.ts b/src/SwapsController.ts index 1fbfda82..ec90576a 100644 --- a/src/SwapsController.ts +++ b/src/SwapsController.ts @@ -96,7 +96,7 @@ export default class SwapsController extends BaseController< > { web3: Web3Type; - ethQuery: any; + ethQuery: EthQuery; #pollCount = 0; From be4f51fda98a6533cf3879d9ebeef188d55ca0c4 Mon Sep 17 00:00:00 2001 From: Nicolas Ferro Date: Fri, 23 Aug 2024 11:17:33 +0200 Subject: [PATCH 19/32] Update src/SwapsController.ts Co-authored-by: Jongsun Suh --- src/SwapsController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SwapsController.ts b/src/SwapsController.ts index ec90576a..37f3ae1f 100644 --- a/src/SwapsController.ts +++ b/src/SwapsController.ts @@ -461,7 +461,7 @@ export default class SwapsController extends BaseController< from: tradeTxParams.from, to: tradeTxParams.to, value: tradeTxParams.value, - } as TxParams, + }, this.ethQuery, ), gasTimeout, From dc8049976bbda194a261f67bccd5e3c402f9efcb Mon Sep 17 00:00:00 2001 From: Nicolas Ferro Date: Fri, 23 Aug 2024 11:17:40 +0200 Subject: [PATCH 20/32] Update src/SwapsController.ts Co-authored-by: Jongsun Suh --- src/SwapsController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SwapsController.ts b/src/SwapsController.ts index 37f3ae1f..4c47a4c0 100644 --- a/src/SwapsController.ts +++ b/src/SwapsController.ts @@ -443,7 +443,7 @@ export default class SwapsController extends BaseController< /* istanbul ignore next */ private async timedoutGasReturn( - tradeTxParams: TxParams | null, + tradeTxParams: Omit & Partial> | null, ): Promise<{ gas: string | null }> { if (!tradeTxParams) { return { gas: null }; From 7d3cc24be9e1df45769cf71a313cf232f3dfc9b6 Mon Sep 17 00:00:00 2001 From: Nicolas Ferro Date: Fri, 23 Aug 2024 11:17:56 +0200 Subject: [PATCH 21/32] Update src/types.ts Co-authored-by: Jongsun Suh --- src/types.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/types.ts b/src/types.ts index a1446fbf..4a0f70d9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -173,11 +173,7 @@ export type Quote = { approvalNeeded: TxParams | null; sourceAmount: string; destinationAmount: number; - error: { - name: string; - message: string; - stack: string; - } | null; + error: JsonRpcError | null; sourceToken: string; destinationToken: string; maxGas: number; From e240fa341e60d0aae97bec17385e12ea76b8c463 Mon Sep 17 00:00:00 2001 From: Nicolas Ferro Date: Fri, 23 Aug 2024 11:18:11 +0200 Subject: [PATCH 22/32] Update src/SwapsController.ts Co-authored-by: Jongsun Suh --- src/SwapsController.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/SwapsController.ts b/src/SwapsController.ts index 4c47a4c0..6f45da59 100644 --- a/src/SwapsController.ts +++ b/src/SwapsController.ts @@ -86,7 +86,6 @@ const metadata: StateMetadata = { usedGasEstimate: { persist: false, anonymous: false }, usedCustomGas: { persist: false, anonymous: false }, chainCache: { persist: false, anonymous: false }, - config: { persist: false, anonymous: false }, }; export default class SwapsController extends BaseController< From 3e7283a8f39e5ffc48ad3e0fa1272d125cb8d354 Mon Sep 17 00:00:00 2001 From: Nicolas Ferro Date: Fri, 23 Aug 2024 11:18:19 +0200 Subject: [PATCH 23/32] Update src/types.ts Co-authored-by: Jongsun Suh --- src/types.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/types.ts b/src/types.ts index 4a0f70d9..b427d22d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -436,10 +436,3 @@ export type SwapsControllerStopPollingAndResetState = { handler: SwapsController['stopPollingAndResetState']; }; -/** - * The action that configures the SwapsController {@link SwapsController}. - */ -export type SwapsControllerConfigure = { - type: `SwapsController:configure`; - handler: SwapsController['configure']; -}; From 81dd4468b84c1f66b1f85697a09604269e56e29a Mon Sep 17 00:00:00 2001 From: Nicolas Ferro Date: Fri, 23 Aug 2024 11:18:41 +0200 Subject: [PATCH 24/32] Update src/SwapsController.ts Co-authored-by: Jongsun Suh --- src/SwapsController.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/SwapsController.ts b/src/SwapsController.ts index 6f45da59..ea1cf827 100644 --- a/src/SwapsController.ts +++ b/src/SwapsController.ts @@ -1063,23 +1063,6 @@ export default class SwapsController extends BaseController< this.ethQuery = new EthQuery(provider); } - public configure(config: Partial & { provider?: any }) { - const { provider, ...serializableConfig } = config; - if (serializableConfig.chainId) { - this.#setChainId(serializableConfig.chainId); - } - if (provider) { - this.#setProvider(provider); - } - - this.update((_state) => { - _state.config = { - ..._state.config, - ...serializableConfig, - }; - }); - } - /** * Updates the state of the controller for testing purposes. * This method should not be used outside of testing. From f184eae277e546a91db35a51cef6a0ee55e36e54 Mon Sep 17 00:00:00 2001 From: nikoferro Date: Fri, 23 Aug 2024 15:23:21 +0200 Subject: [PATCH 25/32] chore: pr feedback changes --- src/SwapsController.test.ts | 281 ++++++---- src/SwapsController.ts | 1036 ++++++++++++++++++++--------------- src/constants.ts | 2 +- src/index.ts | 2 - src/swapsUtil.ts | 5 +- src/types.ts | 23 +- 6 files changed, 779 insertions(+), 570 deletions(-) diff --git a/src/SwapsController.test.ts b/src/SwapsController.test.ts index 19ab8a44..c094c4d1 100644 --- a/src/SwapsController.test.ts +++ b/src/SwapsController.test.ts @@ -3,6 +3,23 @@ import { GasFeeEstimates } from '@metamask/gas-fee-controller'; import SwapsController from './SwapsController'; import { Quote, SwapsControllerMessenger } from './types'; import * as swapsUtil from './swapsUtil'; +import { Provider } from '@metamask/network-controller'; + +const INITIAL_CONTROLLER_OPTIONS = { + pollCountLimit: 3, + fetchAggregatorMetadataThreshold: 1000 * 60 * 60 * 24 * 15, + fetchTokensThreshold: 1000 * 60 * 60 * 24, + fetchTopAssetsThreshold: 1000 * 60 * 30, + chainId: swapsUtil.ETH_CHAIN_ID, + supportedChainIds: [ + swapsUtil.ETH_CHAIN_ID, + swapsUtil.BSC_CHAIN_ID, + swapsUtil.SWAPS_TESTNET_CHAIN_ID, + swapsUtil.POLYGON_CHAIN_ID, + swapsUtil.AVALANCHE_CHAIN_ID, + ], + clientId: undefined, +}; const API_TRADES: { [key: string]: Quote; @@ -167,6 +184,7 @@ describe('SwapsController', () => { swapsController = new SwapsController( { + ...INITIAL_CONTROLLER_OPTIONS, messenger: messengerMock, // TODO: Remove once GasFeeController exports this action type fetchGasFeeEstimates, @@ -205,20 +223,36 @@ describe('SwapsController', () => { swapsUtilEstimateGas.mockRestore(); }); - it('should set default config', () => { - expect(swapsController.state.config).toStrictEqual( - swapsUtil.getDefaultSwapsControllerState().config, + it('should set default options', () => { + expect(swapsController.__test__getInternal('#chainId')).toStrictEqual( + INITIAL_CONTROLLER_OPTIONS.chainId, ); - expect(swapsController.state.config).toStrictEqual({ - chainId: '0x1', - supportedChainIds: ['0x1', '0x38', '0x539', '0x89', '0xa86a'], - maxGasLimit: 2500000, - pollCountLimit: 3, - fetchAggregatorMetadataThreshold: 1000 * 60 * 60 * 24 * 15, - fetchTokensThreshold: 1000 * 60 * 60 * 24, - fetchTopAssetsThreshold: 1000 * 60 * 30, - clientId: undefined, - }); + expect( + swapsController.__test__getInternal('#supportedChainIds'), + ).toStrictEqual(INITIAL_CONTROLLER_OPTIONS.supportedChainIds); + expect( + swapsController.__test__getInternal('#pollCountLimit'), + ).toStrictEqual(INITIAL_CONTROLLER_OPTIONS.pollCountLimit); + expect( + swapsController.__test__getInternal('#fetchAggregatorMetadataThreshold'), + ).toStrictEqual( + INITIAL_CONTROLLER_OPTIONS.fetchAggregatorMetadataThreshold, + ); + expect( + swapsController.__test__getInternal('#fetchTokensThreshold'), + ).toStrictEqual(INITIAL_CONTROLLER_OPTIONS.fetchTokensThreshold); + expect( + swapsController.__test__getInternal('#fetchTopAssetsThreshold'), + ).toStrictEqual(INITIAL_CONTROLLER_OPTIONS.fetchTopAssetsThreshold); + expect(swapsController.__test__getInternal('#clientId')).toStrictEqual( + INITIAL_CONTROLLER_OPTIONS.clientId, + ); + expect(swapsController.__test__getInternal('#fetchGasFeeEstimates')).toBe( + fetchGasFeeEstimates, + ); + expect( + swapsController.__test__getInternal('#fetchEstimatedMultiLayerL1Fee'), + ).toBe(fetchEstimatedMultiLayerL1Fee); }); it('should set default state', () => { @@ -273,22 +307,48 @@ describe('SwapsController', () => { topAssets: null, }, }, - config: { - chainId: '0x1', - supportedChainIds: ['0x1', '0x38', '0x539', '0x89', '0xa86a'], - maxGasLimit: 2500000, - pollCountLimit: 3, - fetchAggregatorMetadataThreshold: 1000 * 60 * 60 * 24 * 15, - fetchTokensThreshold: 1000 * 60 * 60 * 24, - fetchTopAssetsThreshold: 1000 * 60 * 30, - clientId: undefined, - }, }); }); + it('should set default options if not present', () => { + swapsController = new SwapsController( + { + messenger: messengerMock, + fetchGasFeeEstimates, + fetchEstimatedMultiLayerL1Fee, + }, + {}, + ); + + expect(swapsController.__test__getInternal('#chainId')).toStrictEqual( + INITIAL_CONTROLLER_OPTIONS.chainId, + ); + expect( + swapsController.__test__getInternal('#supportedChainIds'), + ).toStrictEqual(INITIAL_CONTROLLER_OPTIONS.supportedChainIds); + expect( + swapsController.__test__getInternal('#pollCountLimit'), + ).toStrictEqual(INITIAL_CONTROLLER_OPTIONS.pollCountLimit); + expect( + swapsController.__test__getInternal('#fetchAggregatorMetadataThreshold'), + ).toStrictEqual( + INITIAL_CONTROLLER_OPTIONS.fetchAggregatorMetadataThreshold, + ); + expect( + swapsController.__test__getInternal('#fetchTokensThreshold'), + ).toStrictEqual(INITIAL_CONTROLLER_OPTIONS.fetchTokensThreshold); + expect( + swapsController.__test__getInternal('#fetchTopAssetsThreshold'), + ).toStrictEqual(INITIAL_CONTROLLER_OPTIONS.fetchTopAssetsThreshold); + expect(swapsController.__test__getInternal('#clientId')).toStrictEqual( + INITIAL_CONTROLLER_OPTIONS.clientId, + ); + }); + it('should set a default value for pollingCyclesLeft', () => { swapsController = new SwapsController( { + ...INITIAL_CONTROLLER_OPTIONS, messenger: messengerMock, fetchGasFeeEstimates, fetchEstimatedMultiLayerL1Fee, @@ -301,16 +361,14 @@ describe('SwapsController', () => { it('should use swapsUtil.INITIAL_CHAIN_DATA when chainCache does not have data for the chainId', () => { const chainId = ChainId.aurora; - swapsController.configure({ - supportedChainIds: [chainId], - }); + swapsController.__test__updatePrivate('#supportedChainIds', [chainId]); // add to supportedChainIds, clear chainCache and set chainId swapsController.__test__updateState({ chainCache: {}, }); - swapsController.configure({ chainId }); + swapsController.setChainId(chainId); const cachedData = swapsController.state.chainCache[chainId]; expect(cachedData).toEqual(swapsUtil.INITIAL_CHAIN_DATA); @@ -323,88 +381,98 @@ describe('SwapsController', () => { type: 'test', chainId: '0x1', rpcUrl: 'test', - }; - expect(swapsController.web3).toBeUndefined(); - expect(swapsController.ethQuery).toBeUndefined(); + } as unknown as Provider; - swapsController.configure({ provider }); + expect(swapsController.__test__getInternal('#web3')).toBeUndefined(); + expect(swapsController.__test__getInternal('#ethQuery')).toBeUndefined(); - expect(swapsController.web3).toBeDefined(); - expect(swapsController.ethQuery).toBeDefined(); + swapsController.setProvider(provider); + + expect(swapsController.__test__getInternal('#web3')).toBeDefined(); + expect(swapsController.__test__getInternal('#ethQuery')).toBeDefined(); }); }); - describe('chain cache', () => { - it('should update cache configuration', () => { - expect(swapsController.state.config).toMatchObject({ - fetchAggregatorMetadataThreshold: 1000 * 60 * 60 * 24 * 15, - fetchTokensThreshold: 1000 * 60 * 60 * 24, - fetchTopAssetsThreshold: 1000 * 60 * 30, - }); + describe('provider', () => { + it('should set provider with options', () => { + const provider = { + name: 'test', + type: 'test', + chainId: '0x1', + rpcUrl: 'test', + } as unknown as Provider; - swapsController.configure({ - fetchAggregatorMetadataThreshold: 0, - fetchTokensThreshold: 0, - fetchTopAssetsThreshold: 0, - }); + expect(swapsController.__test__getInternal('#web3')).toBeUndefined(); + expect(swapsController.__test__getInternal('#ethQuery')).toBeUndefined(); - expect(swapsController.state.config).toMatchObject({ - ...swapsController.state.config, - fetchAggregatorMetadataThreshold: 0, - fetchTokensThreshold: 0, - fetchTopAssetsThreshold: 0, + swapsController.setProvider(provider, { + chainId: '0x23', + pollCountLimit: 10, }); + + expect(swapsController.__test__getInternal('#web3')).toBeDefined(); + expect(swapsController.__test__getInternal('#ethQuery')).toBeDefined(); + expect(swapsController.__test__getInternal('#chainId')).toBe('0x23'); + expect(swapsController.__test__getInternal('#pollCountLimit')).toBe(10); }); + }); + describe('chain cache', () => { it('should update chainId configuration', () => { - swapsController.configure({ - supportedChainIds: ['0x23', '0x24', '0x291'], - }); - swapsController.configure({ chainId: '0x23' }); - expect(swapsController.state.config.chainId).toBe('0x23'); - - swapsController.configure({ chainId: '0x24' }); - expect(swapsController.state.config.chainId).toBe('0x24'); - - swapsController.configure({ chainId: '0x291' }); - expect(swapsController.state.config.chainId).toBe('0x291'); + swapsController.__test__updatePrivate('#supportedChainIds', [ + '0x23', + '0x24', + '0x291', + ]); + swapsController.setChainId('0x23'); + expect(swapsController.__test__getInternal('#chainId')).toBe('0x23'); + + swapsController.setChainId('0x24'); + expect(swapsController.__test__getInternal('#chainId')).toBe('0x24'); + + swapsController.setChainId('0x291'); + expect(swapsController.__test__getInternal('#chainId')).toBe('0x291'); }); it('should create default cache for supported chainIds', () => { - swapsController.configure({ - supportedChainIds: ['0x23', '0x24', '0x291'], - }); - swapsController.configure({ chainId: '0x23' }); + swapsController.__test__updatePrivate('#supportedChainIds', [ + '0x23', + '0x24', + '0x291', + ]); + swapsController.setChainId('0x23'); expect(swapsController.state.chainCache['0x23']).toStrictEqual( swapsUtil.INITIAL_CHAIN_DATA, ); - swapsController.configure({ chainId: '0x24' }); + swapsController.setChainId('0x24'); expect(swapsController.state.chainCache['0x24']).toStrictEqual( swapsUtil.INITIAL_CHAIN_DATA, ); - swapsController.configure({ chainId: '0x291' }); + swapsController.setChainId('0x291'); expect(swapsController.state.chainCache['0x291']).toStrictEqual( swapsUtil.INITIAL_CHAIN_DATA, ); }); it('should not create default cache for unsupported chainIds', () => { - swapsController.configure({ chainId: '0x23' }); + swapsController.setChainId('0x23'); expect(swapsController.state.chainCache['0x23']).toBeUndefined(); - swapsController.configure({ chainId: '0x24' }); + swapsController.setChainId('0x24'); expect(swapsController.state.chainCache['0x24']).toBeUndefined(); - swapsController.configure({ chainId: '0x291' }); + swapsController.setChainId('0x291'); expect(swapsController.state.chainCache['0x291']).toBeUndefined(); }); it('should load existing cache for chainId', () => { - swapsController.configure({ - supportedChainIds: ['0x23', '0x24', '0x291'], - }); + swapsController.__test__updatePrivate('#supportedChainIds', [ + '0x23', + '0x24', + '0x291', + ]); const chainData23 = { ...swapsUtil.INITIAL_CHAIN_DATA, @@ -433,17 +501,17 @@ describe('SwapsController', () => { }, }); - swapsController.configure({ chainId: '0x23' }); + swapsController.setChainId('0x23'); expect(swapsController.state.chainCache['0x23']).toStrictEqual( chainData23, ); - swapsController.configure({ chainId: '0x24' }); + swapsController.setChainId('0x24'); expect(swapsController.state.chainCache['0x24']).toStrictEqual( chainData24, ); - swapsController.configure({ chainId: '0x291' }); + swapsController.setChainId('0x291'); expect(swapsController.state.chainCache['0x291']).toStrictEqual( chainData0x123, ); @@ -470,7 +538,7 @@ describe('SwapsController', () => { it('should fetch tokens when last fetched is over threshold', async () => { const threshold = 5000; - swapsController.configure({ fetchTokensThreshold: threshold }); + swapsController.__test__updatePrivate('#fetchTokensThreshold', threshold); swapsController.__test__updateState({ tokens: [], tokensLastFetched: Date.now() - threshold - 1, @@ -502,7 +570,7 @@ describe('SwapsController', () => { throw new Error(); }); const threshold = 5000; - swapsController.configure({ fetchTokensThreshold: threshold }); + swapsController.__test__updatePrivate('#fetchTokensThreshold', threshold); swapsController.__test__updateState({ tokens: [], tokensLastFetched: Date.now() - threshold - 1, @@ -517,7 +585,8 @@ describe('SwapsController', () => { tokens: [], tokensLastFetched: 0, }); - swapsController.configure({ supportedChainIds: ['0x1'], chainId: '0x2' }); + swapsController.__test__updatePrivate('#supportedChainIds', ['0x1']); + swapsController.setChainId('0x2'); await swapsController.fetchTokenWithCache(); expect(swapsUtilFetchTokens).not.toHaveBeenCalled(); @@ -544,7 +613,10 @@ describe('SwapsController', () => { it('should fetch top assets when last fetched is over threshold', async () => { const threshold = 5000; - swapsController.configure({ fetchTopAssetsThreshold: threshold }); + swapsController.__test__updatePrivate( + '#fetchTopAssetsThreshold', + threshold, + ); swapsController.__test__updateState({ topAssets: [], topAssetsLastFetched: Date.now() - threshold - 1, @@ -576,7 +648,10 @@ describe('SwapsController', () => { throw new Error(); }); const threshold = 5000; - swapsController.configure({ fetchTopAssetsThreshold: threshold }); + swapsController.__test__updatePrivate( + '#fetchTopAssetsThreshold', + threshold, + ); swapsController.__test__updateState({ topAssets: [], topAssetsLastFetched: Date.now() - threshold - 1, @@ -587,15 +662,13 @@ describe('SwapsController', () => { }); it('should return undefined if chain id is not supported', async () => { - swapsController.configure({ - supportedChainIds: ['0x1'], - }); + swapsController.__test__updatePrivate('#supportedChainIds', ['0x1']); swapsController.__test__updateState({ topAssets: [], topAssetsLastFetched: 0, }); - swapsController.configure({ chainId: '0x2' }); + swapsController.setChainId('0x2'); await swapsController.fetchTopAssetsWithCache(); expect(swapsUtilFetchTopAssets).not.toHaveBeenCalled(); }); @@ -621,9 +694,10 @@ describe('SwapsController', () => { it('should fetch aggregator metadata when last fetched is over threshold', async () => { const threshold = 5000; - swapsController.configure({ - fetchAggregatorMetadataThreshold: threshold, - }); + swapsController.__test__updatePrivate( + '#fetchAggregatorMetadataThreshold', + threshold, + ); swapsController.__test__updateState({ aggregatorMetadata: {}, aggregatorMetadataLastFetched: Date.now() - threshold - 1, @@ -655,9 +729,10 @@ describe('SwapsController', () => { throw new Error(); }); const threshold = 5000; - swapsController.configure({ - fetchAggregatorMetadataThreshold: threshold, - }); + swapsController.__test__updatePrivate( + '#fetchAggregatorMetadataThreshold', + threshold, + ); swapsController.__test__updateState({ aggregatorMetadata: {}, aggregatorMetadataLastFetched: Date.now() - threshold - 1, @@ -667,14 +742,12 @@ describe('SwapsController', () => { expect(swapsController.state.aggregatorMetadataLastFetched).toBe(0); }); it('should return undefined if chain id is not supported', async () => { - swapsController.configure({ - supportedChainIds: ['0x1'], - }); + swapsController.__test__updatePrivate('#supportedChainIds', ['0x1']); swapsController.__test__updateState({ aggregatorMetadata: {}, aggregatorMetadataLastFetched: 0, }); - swapsController.configure({ chainId: '0x2' }); + swapsController.setChainId('0x2'); await swapsController.fetchAggregatorMetadataWithCache(); expect(swapsUtilFetchAggregatorMetadata).not.toHaveBeenCalled(); }); @@ -816,13 +889,18 @@ describe('SwapsController', () => { expect(swapsController.state.quoteValues).toEqual({}); }); - it('should clear timeout if this.handle is set', () => { + it('should clear timeout if this.#handle is set', () => { const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout'); - swapsController['handle'] = setTimeout(() => {}, 1000); // Set a timeout + swapsController.__test__updatePrivate( + '#handle', + setTimeout(() => {}, 1000), + ); swapsController.stopPollingAndResetState(); - expect(clearTimeoutSpy).toHaveBeenCalledWith(swapsController['handle']); + expect(clearTimeoutSpy).toHaveBeenCalledWith( + swapsController.__test__getInternal('#handle'), + ); clearTimeoutSpy.mockRestore(); }); }); @@ -847,6 +925,7 @@ describe('SwapsController', () => { swapsController = new SwapsController( { + ...INITIAL_CONTROLLER_OPTIONS, messenger: messengerMock, fetchGasFeeEstimates, }, @@ -877,6 +956,7 @@ describe('SwapsController', () => { swapsController = new SwapsController( { + ...INITIAL_CONTROLLER_OPTIONS, messenger: messengerMock, fetchGasFeeEstimates, }, @@ -892,6 +972,7 @@ describe('SwapsController', () => { it('should fetch gas price from fetchGasPrices if fetchGasFeeEstimates is not defined', async () => { swapsController = new SwapsController( { + ...INITIAL_CONTROLLER_OPTIONS, messenger: messengerMock, fetchGasFeeEstimates: undefined, }, @@ -933,6 +1014,7 @@ describe('SwapsController', () => { swapsController = new SwapsController( { + ...INITIAL_CONTROLLER_OPTIONS, messenger: messengerMock, fetchGasFeeEstimates, }, @@ -1045,6 +1127,7 @@ describe('SwapsController', () => { swapsController = new SwapsController( { + ...INITIAL_CONTROLLER_OPTIONS, messenger: messengerMock, fetchGasFeeEstimates, }, @@ -1125,6 +1208,7 @@ describe('SwapsController', () => { swapsController = new SwapsController( { + ...INITIAL_CONTROLLER_OPTIONS, messenger: messengerMock, fetchGasFeeEstimates, }, @@ -1170,6 +1254,7 @@ describe('SwapsController', () => { swapsController = new SwapsController( { + ...INITIAL_CONTROLLER_OPTIONS, messenger: messengerMock, fetchGasFeeEstimates, }, diff --git a/src/SwapsController.ts b/src/SwapsController.ts index ea1cf827..e67d5f82 100644 --- a/src/SwapsController.ts +++ b/src/SwapsController.ts @@ -13,7 +13,8 @@ import type { GasFeeState, } from '@metamask/gas-fee-controller'; import { GAS_ESTIMATE_TYPES } from '@metamask/gas-fee-controller'; -import type { Hex } from '@metamask/utils'; +import type { Provider } from '@metamask/network-controller'; +import { getKnownPropertyNames, type Hex } from '@metamask/utils'; import { Mutex } from 'async-mutex'; import { BigNumber } from 'bignumber.js'; import abiERC20 from 'human-standard-token-abi'; @@ -21,12 +22,15 @@ import type { Web3 as Web3Type } from 'web3'; import * as web3 from 'web3'; import { + AVALANCHE_CHAIN_ID, + BSC_CHAIN_ID, calcTokenAmount, calculateGasEstimateWithRefund, calculateGasLimits, controllerName, DEFAULT_ERC20_APPROVE_GAS, estimateGas, + ETH_CHAIN_ID, fetchAggregatorMetadata, fetchGasPrices, fetchTokens, @@ -44,7 +48,9 @@ import { isGasFeeStateLegacy, NATIVE_SWAPS_TOKEN_ADDRESS, OPTIMISM_CHAIN_ID, + POLYGON_CHAIN_ID, shouldEnableDirectWrapping, + SWAPS_TESTNET_CHAIN_ID, SwapsError, } from './swapsUtil'; import type { @@ -54,7 +60,6 @@ import type { CustomGasFee, Quote, QuoteValues, - SwapsConfig, SwapsControllerMessenger, SwapsControllerOptions, SwapsControllerState, @@ -93,17 +98,31 @@ export default class SwapsController extends BaseController< SwapsControllerState, SwapsControllerMessenger > { - web3: Web3Type; + #abortController?: AbortController; - ethQuery: EthQuery; + #clientId?: string; - #pollCount = 0; + #ethQuery: EthQuery; - #abortController?: AbortController; + #fetchAggregatorMetadataThreshold: number; + + #fetchTokensThreshold: number; + + #fetchTopAssetsThreshold: number; + + #handle?: NodeJS.Timeout; #mutex = new Mutex(); - private handle?: NodeJS.Timeout; + #pollCount = 0; + + #pollCountLimit: number; + + #supportedChainIds: Hex[]; + + #web3: Web3Type; + + #chainId: Hex; // TODO: Remove once GasFeeController exports this action type readonly #fetchGasFeeEstimates?: ( @@ -118,255 +137,40 @@ export default class SwapsController extends BaseController< }, ) => Promise; - /** - * Fetch current gas price - * @returns Promise resolving to the current gas price or throw an error - */ - /* istanbul ignore next */ - private async getGasPrice(): Promise { - if (this.#fetchGasFeeEstimates) { - const gasFeeState = await this.#fetchGasFeeEstimates({ - shouldUpdateState: this.#pollCount === 1, - }); - if ( - !gasFeeState || - gasFeeState.gasEstimateType === GAS_ESTIMATE_TYPES.NONE - ) { - throw new Error(SwapsError.SWAPS_GAS_PRICE_ESTIMATION); - } - - if (isGasFeeStateFeeMarket(gasFeeState)) { - return gasFeeState.gasFeeEstimates; - } else if (isGasFeeStateLegacy(gasFeeState)) { - return { gasPrice: gasFeeState.gasFeeEstimates.medium }; - } else if (isGasFeeStateEthGasPrice(gasFeeState)) { - return { gasPrice: gasFeeState.gasFeeEstimates.gasPrice }; - } - } - - try { - const { proposedGasPrice } = await fetchGasPrices( - this.state.config.chainId, - this.state.config.clientId, - ); - return { gasPrice: proposedGasPrice }; - } catch (error) { - // - } - - try { - const gasPrice = await query(this.ethQuery, 'gasPrice'); - return { - gasPrice: weiHexToGweiDec(gasPrice).toString(), - }; - } catch (error) { - // + #buildChainCache = (chainId: Hex) => { + if (!this.#supportedChainIds.includes(chainId)) { + return; } - throw new Error(SwapsError.SWAPS_GAS_PRICE_ESTIMATION); - } - /** - * Calculates a quote `QuotesValue` - * @param quote - Quote object - * @param gasLimit - A hex string representing max units of gas to spend - * @param gasFeeEstimates - current gas fee estimates - * @param customGasFee - custom gas fee values - */ - /* istanbul ignore next */ - private calculateQuoteValues( - quote: Quote, - gasLimit: string | null, - gasFeeEstimates: GasFeeEstimates | EthGasPriceEstimate, - customGasFee?: CustomEthGasPriceEstimate | CustomGasFee, - ): QuoteValues { - const { destinationTokenInfo } = this.state.fetchParamsMetaData; - const { - aggregator, - averageGas, - maxGas, - destinationAmount = 0, - fee: metaMaskFee, - sourceAmount, - sourceToken, - trade, - gasEstimateWithRefund, - gasEstimate, - gasMultiplier, - approvalNeeded, - destinationTokenRate, - multiLayerL1TradeFeeTotal, - } = quote; - - // trade gas - const { tradeGasLimit, tradeMaxGasLimit } = calculateGasLimits( - Boolean(approvalNeeded), - gasEstimateWithRefund, - gasEstimate, - averageGas, - maxGas, - gasMultiplier, - gasLimit, - ); - - let totalGasInWei: BigNumber; - let maxTotalGasInWei: BigNumber; - - if (isEthGasPriceEstimate(gasFeeEstimates)) { - const gasPrice = isCustomEthGasPriceEstimate(customGasFee) - ? customGasFee.gasPrice - : gasFeeEstimates.gasPrice; - - totalGasInWei = tradeGasLimit.times( - gweiDecToWEIBN(gasPrice).toString(16), - 16, - ); - - maxTotalGasInWei = new BigNumber(tradeMaxGasLimit).times( - gweiDecToWEIBN(gasPrice).toString(16), - 16, - ); - - if (multiLayerL1TradeFeeTotal) { - totalGasInWei = totalGasInWei.plus(multiLayerL1TradeFeeTotal, 16); - maxTotalGasInWei = maxTotalGasInWei.plus(multiLayerL1TradeFeeTotal, 16); - } - } else { - const estimatedBaseFee = - (isCustomGasFee(customGasFee) && customGasFee?.estimatedBaseFee) || - gasFeeEstimates.estimatedBaseFee; - - const [maxFeePerGas, maxPriorityFeePerGas] = isCustomGasFee(customGasFee) - ? [customGasFee.maxFeePerGas, customGasFee.maxPriorityFeePerGas] - : [ - gasFeeEstimates.high.suggestedMaxFeePerGas, - gasFeeEstimates.high.suggestedMaxPriorityFeePerGas, - ]; - - totalGasInWei = tradeGasLimit.times( - gweiDecToWEIBN(estimatedBaseFee) - .add(gweiDecToWEIBN(maxPriorityFeePerGas)) - .toString(16), - 16, - ); + const { chainCache } = this.state; - maxTotalGasInWei = new BigNumber(tradeMaxGasLimit).times( - gweiDecToWEIBN(maxFeePerGas).toString(16), - 16, - ); + if (!chainCache?.[chainId]) { + this.update((_state) => { + _state.aggregatorMetadata = null; + _state.tokens = null; + _state.topAssets = null; + _state.aggregatorMetadataLastFetched = 0; + _state.topAssetsLastFetched = 0; + _state.tokensLastFetched = 0; + _state.chainCache = getNewChainCache(chainCache, chainId, { + ...INITIAL_CHAIN_DATA, + }); + }); + return; } - // totalGas + trade value - // trade.value is a sum of different values depending on the transaction. - // It always includes any external fees charged by the quote source. In - // addition, if the source asset is NATIVE, trade.value includes the amount - // of swapped NATIVE. - const totalInWei = totalGasInWei.plus(trade.value, 16); - const maxTotalInWei = maxTotalGasInWei.plus(trade.value, 16); - - // if value in trade, NATIVE fee will be the gas, if not it will be the total wei - const weiFee = - sourceToken === NATIVE_SWAPS_TOKEN_ADDRESS - ? totalInWei.minus(sourceAmount, 10) - : totalInWei; // sourceAmount is in wei : totalInWei; - const maxWeiFee = - sourceToken === NATIVE_SWAPS_TOKEN_ADDRESS - ? maxTotalInWei.minus(sourceAmount, 10) - : maxTotalInWei; // sourceAmount is in wei : totalInWei; - const ethFee = calcTokenAmount(weiFee, 18); - const maxEthFee = calcTokenAmount(maxWeiFee, 18); - - const decimalAdjustedDestinationAmount = calcTokenAmount( - destinationAmount, - destinationTokenInfo.decimals, - ); - - // fees - const tokenPercentageOfPreFeeDestAmount = new BigNumber(100, 10) - .minus(metaMaskFee, 10) - .div(100); - const destinationAmountBeforeMetaMaskFee = - decimalAdjustedDestinationAmount.div(tokenPercentageOfPreFeeDestAmount); - const metaMaskFeeInTokens = destinationAmountBeforeMetaMaskFee.minus( - decimalAdjustedDestinationAmount, - ); - - const conversionRate = destinationTokenRate ?? 1; - - const ethValueOfTokens = decimalAdjustedDestinationAmount.times( - conversionRate, - 10, - ); - - // the more tokens the better - const overallValueOfQuote = ethValueOfTokens.minus(ethFee, 10); - - const quoteValues: QuoteValues = { - aggregator, - tradeGasLimit: tradeGasLimit.toString(10), - tradeMaxGasLimit: tradeMaxGasLimit.toString(10), - ethFee: ethFee.toFixed(18), - maxEthFee: maxEthFee.toFixed(18), - ethValueOfTokens: ethValueOfTokens.toFixed(18), - overallValueOfQuote: overallValueOfQuote.toFixed(18), - metaMaskFeeInEth: metaMaskFeeInTokens.times(conversionRate).toFixed(18), - }; - - return quoteValues; - } - - /* istanbul ignore next */ - private calculatesCustomLimitMaxEthFee( - quote: Quote, - gasFee: - | EthGasPriceEstimate - | GasFeeEstimates - | CustomGasFee - | CustomEthGasPriceEstimate, - gasLimit: string, - ): string { - const { - averageGas, - maxGas, - sourceAmount, - sourceToken, - trade, - gasEstimateWithRefund, - gasEstimate, - gasMultiplier, - approvalNeeded, - } = quote; - - const { tradeMaxGasLimit } = calculateGasLimits( - Boolean(approvalNeeded), - gasEstimateWithRefund, - gasEstimate, - averageGas, - maxGas, - gasMultiplier, - gasLimit, - ); - - let gasPrice; - if (isCustomEthGasPriceEstimate(gasFee) || isEthGasPriceEstimate(gasFee)) { - gasPrice = gasFee.gasPrice; - } else if (isCustomGasFee(gasFee)) { - gasPrice = gasFee.maxFeePerGas; - } else { - gasPrice = gasFee.high.suggestedMaxFeePerGas; - } + const cachedData = chainCache[chainId]; - const maxTotalGasInWei = new BigNumber(tradeMaxGasLimit).times( - gweiDecToWEIBN(gasPrice).toString(16), - 16, - ); - const maxTotalInWei = maxTotalGasInWei.plus(trade.value ?? '0x0', 16); - const maxWeiFee = - sourceToken === NATIVE_SWAPS_TOKEN_ADDRESS - ? maxTotalInWei.minus(sourceAmount, 10) - : maxTotalInWei; - const maxEthFee = calcTokenAmount(maxWeiFee, 18).toFixed(18); - return maxEthFee; - } + this.update((_state) => { + _state.aggregatorMetadata = cachedData.aggregatorMetadata; + _state.tokens = cachedData.tokens; + _state.topAssets = cachedData.topAssets; + _state.aggregatorMetadataLastFetched = + cachedData.aggregatorMetadataLastFetched; + _state.topAssetsLastFetched = cachedData.topAssetsLastFetched; + _state.tokensLastFetched = cachedData.tokensLastFetched; + }); + }; /** * Find best quote and quotes calculated values @@ -374,7 +178,7 @@ export default class SwapsController extends BaseController< * @returns Promise resolving to the best quote object and values from quotes */ /* istanbul ignore next */ - private getBestQuoteAndQuotesValues( + #getBestQuoteAndQuotesValues( quotes: { [key: string]: Quote }, gasFeeEstimates: EthGasPriceEstimate | GasFeeEstimates, customGasFee?: CustomEthGasPriceEstimate | CustomGasFee, @@ -416,11 +220,11 @@ export default class SwapsController extends BaseController< * @returns Promise resolving to allowance number */ /* istanbul ignore next */ - private async getERC20Allowance( + async #getERC20Allowance( contractAddress: string, walletAddress: string, ): Promise { - const contract = new this.web3.eth.Contract(abiERC20, contractAddress); + const contract = new this.#web3.eth.Contract(abiERC20, contractAddress); const allowanceTimeout = new Promise((_, reject) => { setTimeout(() => { reject(new Error(SwapsError.SWAPS_ALLOWANCE_TIMEOUT)); @@ -429,10 +233,7 @@ export default class SwapsController extends BaseController< const allowancePromise = async () => { const result: bigint = await contract.methods - .allowance( - walletAddress, - getSwapsContractAddress(this.state.config.chainId), - ) + .allowance(walletAddress, getSwapsContractAddress(this.#chainId)) .call(); return new BigNumber(result.toString()); }; @@ -441,8 +242,10 @@ export default class SwapsController extends BaseController< } /* istanbul ignore next */ - private async timedoutGasReturn( - tradeTxParams: Omit & Partial> | null, + async #timedoutGasReturn( + tradeTxParams: + | (Omit & Partial>) + | null, ): Promise<{ gas: string | null }> { if (!tradeTxParams) { return { gas: null }; @@ -461,7 +264,7 @@ export default class SwapsController extends BaseController< to: tradeTxParams.to, value: tradeTxParams.value, }, - this.ethQuery, + this.#ethQuery, ), gasTimeout, ]); @@ -471,31 +274,29 @@ export default class SwapsController extends BaseController< } /* istanbul ignore next */ - private async pollForNewQuotesWithThreshold( - fetchThreshold = 0, - ): Promise { + async #pollForNewQuotesWithThreshold(fetchThreshold = 0): Promise { this.#pollCount += 1; - if (this.handle) { - clearTimeout(this.handle); - this.handle = undefined; + if (this.#handle) { + clearTimeout(this.#handle); + this.#handle = undefined; } - if (this.#pollCount < Number(this.state.config.pollCountLimit) + 1) { + if (this.#pollCount < Number(this.#pollCountLimit) + 1) { if (!this.state.isInPolling) { this.update((_state) => { _state.isInPolling = true; }); } const { nextQuotesState, threshold, usedGasEstimate } = - await this.fetchQuotes(); + await this.#fetchQuotes(); this.update((_state) => { - _state.pollingCyclesLeft = - _state.config.pollCountLimit - this.#pollCount; + _state.pollingCyclesLeft = this.#pollCountLimit - this.#pollCount; }); if (threshold && nextQuotesState?.quoteRefreshSeconds) { this.update((_state) => { + // @ts-expect-error - since the keys in the quote object are aggregator ids, we can safely ignore this error _state.quotes = nextQuotesState.quotes ?? _state.quotes; _state.quotesLastFetched = nextQuotesState.quotesLastFetched ?? 0; _state.approvalTransaction = @@ -506,8 +307,8 @@ export default class SwapsController extends BaseController< _state.quoteRefreshSeconds = nextQuotesState.quoteRefreshSeconds ?? 0; _state.usedGasEstimate = usedGasEstimate; }); - this.handle = setTimeout(() => { - this.pollForNewQuotesWithThreshold(threshold).catch(() => { + this.#handle = setTimeout(() => { + this.#pollForNewQuotesWithThreshold(threshold).catch(() => { this.update((_state) => { _state.isInPolling = false; }); @@ -515,7 +316,7 @@ export default class SwapsController extends BaseController< }, nextQuotesState.quoteRefreshSeconds * 1000 - threshold); } } else { - this.handle = setTimeout(() => { + this.#handle = setTimeout(() => { this.stopPollingAndResetState({ key: SwapsError.QUOTES_EXPIRED_ERROR, description: null, @@ -525,13 +326,13 @@ export default class SwapsController extends BaseController< } /* istanbul ignore next */ - private async getAllQuotesWithGasEstimates(trades: { + async #getAllQuotesWithGasEstimates(trades: { [key: string]: Quote; }): Promise<{ [key: string]: Quote }> { const quoteGasData = await Promise.all( Object.values(trades).map(async (trade) => { try { - const { gas } = await this.timedoutGasReturn(trade.trade); + const { gas } = await this.#timedoutGasReturn(trade.trade); return { gas, aggId: trade.aggregator, @@ -558,14 +359,13 @@ export default class SwapsController extends BaseController< } /* istanbul ignore next */ - private async fetchQuotes(): Promise<{ + async #fetchQuotes(): Promise<{ nextQuotesState: Partial | null; threshold: number | null; usedGasEstimate: EthGasPriceEstimate | GasFeeEstimates | null; }> { const timeStarted = Date.now(); const { fetchParams } = this.state; - const { clientId, chainId } = this.state.config; try { /** We need to abort quotes fetch if stopPollingAndResetState is called while getting quotes */ this.#abortController = new AbortController(); @@ -573,23 +373,26 @@ export default class SwapsController extends BaseController< let quotes: { [key: string]: Quote } = await fetchTradesInfo( fetchParams, signal, - chainId, - clientId, + this.#chainId, + this.#clientId, ); if (Object.values(quotes).length === 0) { throw new Error(SwapsError.QUOTES_NOT_AVAILABLE_ERROR); } - if (chainId === OPTIMISM_CHAIN_ID && Object.values(quotes).length > 0) { + if ( + this.#chainId === OPTIMISM_CHAIN_ID && + Object.values(quotes).length > 0 + ) { // Fetch an L1 fee for each quote on Optimism. await Promise.all( Object.values(quotes).map(async (quote) => { if (quote.trade && this.#fetchEstimatedMultiLayerL1Fee) { const multiLayerL1TradeFeeTotal = - await this.#fetchEstimatedMultiLayerL1Fee(this.ethQuery, { + await this.#fetchEstimatedMultiLayerL1Fee(this.#ethQuery, { txParams: quote.trade, - chainId, + chainId: this.#chainId, }); // eslint-disable-next-line require-atomic-updates quote.multiLayerL1TradeFeeTotal = @@ -603,7 +406,7 @@ export default class SwapsController extends BaseController< let approvalTransaction: TxParams | null = null; const enableDirectWrappingParam = shouldEnableDirectWrapping( - chainId, + this.#chainId, fetchParams.sourceToken, fetchParams.destinationToken, ); @@ -620,7 +423,7 @@ export default class SwapsController extends BaseController< fetchParams.sourceToken !== NATIVE_SWAPS_TOKEN_ADDRESS && !enableDirectWrapping ) { - const allowance = await this.getERC20Allowance( + const allowance = await this.#getERC20Allowance( fetchParams.sourceToken, fetchParams.walletAddress, ); @@ -636,7 +439,7 @@ export default class SwapsController extends BaseController< throw new Error(SwapsError.SWAPS_ALLOWANCE_ERROR); } - const { gas: approvalGas } = await this.timedoutGasReturn({ + const { gas: approvalGas } = await this.#timedoutGasReturn({ data: approvalTransaction.data, from: approvalTransaction.from, to: approvalTransaction.to, @@ -649,118 +452,399 @@ export default class SwapsController extends BaseController< } } - quotes = await this.getAllQuotesWithGasEstimates(quotes); + quotes = await this.#getAllQuotesWithGasEstimates(quotes); + + const gasFeeEstimates: EthGasPriceEstimate | GasFeeEstimates = + await this.getGasPrice(); + + const { topAggId, quoteValues } = this.#getBestQuoteAndQuotesValues( + quotes, + gasFeeEstimates, + ); + + const quotesLastFetched = Date.now(); + + const nextQuotesState: Partial = { + quotes, + quotesLastFetched, + approvalTransaction, + topAggId: quotes[topAggId]?.aggregator, + quoteValues, + quoteRefreshSeconds: quotes[topAggId]?.quoteRefreshSeconds, + }; + return { + nextQuotesState, + threshold: quotesLastFetched - timeStarted, + usedGasEstimate: gasFeeEstimates, + }; + } catch (error: any) { + const errorKey = Object.values(SwapsError).includes(error.message) + ? error.message + : SwapsError.ERROR_FETCHING_QUOTES; + this.stopPollingAndResetState({ key: errorKey, description: error }); + return { + nextQuotesState: null, + threshold: null, + usedGasEstimate: null, + }; + } + } + + /** + * Creates a SwapsController instance. + * @param opts - Constructor options. + * @param opts.clientId - The client id used by the controller. + * @param opts.pollCountLimit - The maximum number of times the controller will poll for quotes. + * @param opts.fetchAggregatorMetadataThreshold - The threshold for fetching aggregator metadata. + * @param opts.fetchTokensThreshold - The threshold for fetching tokens. + * @param opts.fetchTopAssetsThreshold - The threshold for fetching top assets. + * @param opts.chainId - The chain id used by the controller. + * @param opts.supportedChainIds - The supported chain ids used by the controller. + * @param opts.fetchGasFeeEstimates - Fetches gas fee estimates from GasFeeController. + * @param opts.fetchEstimatedMultiLayerL1Fee - Fetches an L1 fee for a given transaction. + * @param opts.messenger - The messaging system used by the controller. + * @param state - Initial state to set on this controller. + */ + constructor( + { + pollCountLimit = 3, + fetchAggregatorMetadataThreshold = 1000 * 60 * 60 * 24 * 15, + fetchTokensThreshold = 1000 * 60 * 60 * 24, + fetchTopAssetsThreshold = 1000 * 60 * 30, + chainId = ETH_CHAIN_ID, + supportedChainIds = [ + ETH_CHAIN_ID, + BSC_CHAIN_ID, + SWAPS_TESTNET_CHAIN_ID, + POLYGON_CHAIN_ID, + AVALANCHE_CHAIN_ID, + ], + clientId, + messenger, + fetchGasFeeEstimates, + fetchEstimatedMultiLayerL1Fee, + }: SwapsControllerOptions, + state: Partial, + ) { + super({ + name: controllerName, + metadata, + messenger, + state: { + ...getDefaultSwapsControllerState(), + ...state, + }, + }); + + this.#clientId = clientId; + this.#fetchAggregatorMetadataThreshold = fetchAggregatorMetadataThreshold; + this.#fetchEstimatedMultiLayerL1Fee = fetchEstimatedMultiLayerL1Fee; + this.#fetchGasFeeEstimates = fetchGasFeeEstimates; + this.#fetchTokensThreshold = fetchTokensThreshold; + this.#fetchTopAssetsThreshold = fetchTopAssetsThreshold; + this.#pollCountLimit = pollCountLimit; + this.#supportedChainIds = supportedChainIds; + + this.setChainId(chainId); + + this.messagingSystem.registerActionHandler( + `SwapsController:updateQuotesWithGasPrice`, + this.updateQuotesWithGasPrice.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:updateSelectedQuoteWithGasLimit`, + this.updateSelectedQuoteWithGasLimit.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:startFetchAndSetQuotes`, + this.startFetchAndSetQuotes.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:fetchTokenWithCache`, + this.fetchTokenWithCache.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:fetchTopAssetsWithCache`, + this.fetchTopAssetsWithCache.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:fetchAggregatorMetadataWithCache`, + this.fetchAggregatorMetadataWithCache.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `SwapsController:stopPollingAndResetState`, + this.stopPollingAndResetState.bind(this), + ); + } + + /** + * Fetch current gas price + * @returns Promise resolving to the current gas price or throw an error + */ + /* istanbul ignore next */ + private async getGasPrice(): Promise { + if (this.#fetchGasFeeEstimates) { + const gasFeeState = await this.#fetchGasFeeEstimates({ + shouldUpdateState: this.#pollCount === 1, + }); + if ( + !gasFeeState || + gasFeeState.gasEstimateType === GAS_ESTIMATE_TYPES.NONE + ) { + throw new Error(SwapsError.SWAPS_GAS_PRICE_ESTIMATION); + } + + if (isGasFeeStateFeeMarket(gasFeeState)) { + return gasFeeState.gasFeeEstimates; + } else if (isGasFeeStateLegacy(gasFeeState)) { + return { gasPrice: gasFeeState.gasFeeEstimates.medium }; + } else if (isGasFeeStateEthGasPrice(gasFeeState)) { + return { gasPrice: gasFeeState.gasFeeEstimates.gasPrice }; + } + } + + try { + const { proposedGasPrice } = await fetchGasPrices( + this.#chainId, + this.#clientId, + ); + return { gasPrice: proposedGasPrice }; + } catch (error) { + // + } + + try { + const gasPrice = await query(this.#ethQuery, 'gasPrice'); + return { + gasPrice: weiHexToGweiDec(gasPrice).toString(), + }; + } catch (error) { + // + } + throw new Error(SwapsError.SWAPS_GAS_PRICE_ESTIMATION); + } + + /** + * Calculates a quote `QuotesValue` + * @param quote - Quote object + * @param gasLimit - A hex string representing max units of gas to spend + * @param gasFeeEstimates - current gas fee estimates + * @param customGasFee - custom gas fee values + */ + /* istanbul ignore next */ + private calculateQuoteValues( + quote: Quote, + gasLimit: string | null, + gasFeeEstimates: GasFeeEstimates | EthGasPriceEstimate, + customGasFee?: CustomEthGasPriceEstimate | CustomGasFee, + ): QuoteValues { + const { destinationTokenInfo } = this.state.fetchParamsMetaData; + const { + aggregator, + averageGas, + maxGas, + destinationAmount = 0, + fee: metaMaskFee, + sourceAmount, + sourceToken, + trade, + gasEstimateWithRefund, + gasEstimate, + gasMultiplier, + approvalNeeded, + destinationTokenRate, + multiLayerL1TradeFeeTotal, + } = quote; + + // trade gas + const { tradeGasLimit, tradeMaxGasLimit } = calculateGasLimits( + Boolean(approvalNeeded), + gasEstimateWithRefund, + gasEstimate, + averageGas, + maxGas, + gasMultiplier, + gasLimit, + ); + + let totalGasInWei: BigNumber; + let maxTotalGasInWei: BigNumber; + + if (isEthGasPriceEstimate(gasFeeEstimates)) { + const gasPrice = isCustomEthGasPriceEstimate(customGasFee) + ? customGasFee.gasPrice + : gasFeeEstimates.gasPrice; + + totalGasInWei = tradeGasLimit.times( + gweiDecToWEIBN(gasPrice).toString(16), + 16, + ); + + maxTotalGasInWei = new BigNumber(tradeMaxGasLimit).times( + gweiDecToWEIBN(gasPrice).toString(16), + 16, + ); + + if (multiLayerL1TradeFeeTotal) { + totalGasInWei = totalGasInWei.plus(multiLayerL1TradeFeeTotal, 16); + maxTotalGasInWei = maxTotalGasInWei.plus(multiLayerL1TradeFeeTotal, 16); + } + } else { + const estimatedBaseFee = + (isCustomGasFee(customGasFee) && customGasFee?.estimatedBaseFee) || + gasFeeEstimates.estimatedBaseFee; - const gasFeeEstimates: EthGasPriceEstimate | GasFeeEstimates = - await this.getGasPrice(); + const [maxFeePerGas, maxPriorityFeePerGas] = isCustomGasFee(customGasFee) + ? [customGasFee.maxFeePerGas, customGasFee.maxPriorityFeePerGas] + : [ + gasFeeEstimates.high.suggestedMaxFeePerGas, + gasFeeEstimates.high.suggestedMaxPriorityFeePerGas, + ]; - const { topAggId, quoteValues } = this.getBestQuoteAndQuotesValues( - quotes, - gasFeeEstimates, + totalGasInWei = tradeGasLimit.times( + gweiDecToWEIBN(estimatedBaseFee) + .add(gweiDecToWEIBN(maxPriorityFeePerGas)) + .toString(16), + 16, ); - const quotesLastFetched = Date.now(); - - const nextQuotesState: Partial = { - quotes, - quotesLastFetched, - approvalTransaction, - topAggId: quotes[topAggId]?.aggregator, - quoteValues, - quoteRefreshSeconds: quotes[topAggId]?.quoteRefreshSeconds, - }; - return { - nextQuotesState, - threshold: quotesLastFetched - timeStarted, - usedGasEstimate: gasFeeEstimates, - }; - } catch (error: any) { - const errorKey = Object.values(SwapsError).includes(error.message) - ? error.message - : SwapsError.ERROR_FETCHING_QUOTES; - this.stopPollingAndResetState({ key: errorKey, description: error }); - return { - nextQuotesState: null, - threshold: null, - usedGasEstimate: null, - }; + maxTotalGasInWei = new BigNumber(tradeMaxGasLimit).times( + gweiDecToWEIBN(maxFeePerGas).toString(16), + 16, + ); } - } - /** - * Creates a SwapsController instance. - * @param opts - Constructor options. - * @param opts.fetchGasFeeEstimates - Fetches gas fee estimates from GasFeeController. - * @param opts.fetchEstimatedMultiLayerL1Fee - Fetches an L1 fee for a given transaction. - * @param opts.messenger - The messaging system used by the controller. - * @param state - Initial state to set on this controller. - */ - constructor( - opts: SwapsControllerOptions, - state: Partial, - ) { - super({ - name: controllerName, - metadata, - messenger: opts.messenger, - state: { - ...getDefaultSwapsControllerState(), - ...state, - }, - }); + // totalGas + trade value + // trade.value is a sum of different values depending on the transaction. + // It always includes any external fees charged by the quote source. In + // addition, if the source asset is NATIVE, trade.value includes the amount + // of swapped NATIVE. + const totalInWei = totalGasInWei.plus(trade.value, 16); + const maxTotalInWei = maxTotalGasInWei.plus(trade.value, 16); - this.messagingSystem.registerActionHandler( - `SwapsController:updateQuotesWithGasPrice`, - this.updateQuotesWithGasPrice.bind(this), - ); + // if value in trade, NATIVE fee will be the gas, if not it will be the total wei + const weiFee = + sourceToken === NATIVE_SWAPS_TOKEN_ADDRESS + ? totalInWei.minus(sourceAmount, 10) + : totalInWei; // sourceAmount is in wei : totalInWei; + const maxWeiFee = + sourceToken === NATIVE_SWAPS_TOKEN_ADDRESS + ? maxTotalInWei.minus(sourceAmount, 10) + : maxTotalInWei; // sourceAmount is in wei : totalInWei; + const ethFee = calcTokenAmount(weiFee, 18); + const maxEthFee = calcTokenAmount(maxWeiFee, 18); - this.messagingSystem.registerActionHandler( - `SwapsController:updateSelectedQuoteWithGasLimit`, - this.updateSelectedQuoteWithGasLimit.bind(this), + const decimalAdjustedDestinationAmount = calcTokenAmount( + destinationAmount, + destinationTokenInfo.decimals, ); - this.messagingSystem.registerActionHandler( - `SwapsController:startFetchAndSetQuotes`, - this.startFetchAndSetQuotes.bind(this), + // fees + const tokenPercentageOfPreFeeDestAmount = new BigNumber(100, 10) + .minus(metaMaskFee, 10) + .div(100); + const destinationAmountBeforeMetaMaskFee = + decimalAdjustedDestinationAmount.div(tokenPercentageOfPreFeeDestAmount); + const metaMaskFeeInTokens = destinationAmountBeforeMetaMaskFee.minus( + decimalAdjustedDestinationAmount, ); - this.messagingSystem.registerActionHandler( - `SwapsController:fetchTokenWithCache`, - this.fetchTokenWithCache.bind(this), - ); + const conversionRate = destinationTokenRate ?? 1; - this.messagingSystem.registerActionHandler( - `SwapsController:fetchTopAssetsWithCache`, - this.fetchTopAssetsWithCache.bind(this), + const ethValueOfTokens = decimalAdjustedDestinationAmount.times( + conversionRate, + 10, ); - this.messagingSystem.registerActionHandler( - `SwapsController:fetchAggregatorMetadataWithCache`, - this.fetchAggregatorMetadataWithCache.bind(this), - ); + // the more tokens the better + const overallValueOfQuote = ethValueOfTokens.minus(ethFee, 10); - this.messagingSystem.registerActionHandler( - `SwapsController:stopPollingAndResetState`, - this.stopPollingAndResetState.bind(this), + const quoteValues: QuoteValues = { + aggregator, + tradeGasLimit: tradeGasLimit.toString(10), + tradeMaxGasLimit: tradeMaxGasLimit.toString(10), + ethFee: ethFee.toFixed(18), + maxEthFee: maxEthFee.toFixed(18), + ethValueOfTokens: ethValueOfTokens.toFixed(18), + overallValueOfQuote: overallValueOfQuote.toFixed(18), + metaMaskFeeInEth: metaMaskFeeInTokens.times(conversionRate).toFixed(18), + }; + + return quoteValues; + } + + /* istanbul ignore next */ + private calculatesCustomLimitMaxEthFee( + quote: Quote, + gasFee: + | EthGasPriceEstimate + | GasFeeEstimates + | CustomGasFee + | CustomEthGasPriceEstimate, + gasLimit: string, + ): string { + const { + averageGas, + maxGas, + sourceAmount, + sourceToken, + trade, + gasEstimateWithRefund, + gasEstimate, + gasMultiplier, + approvalNeeded, + } = quote; + + const { tradeMaxGasLimit } = calculateGasLimits( + Boolean(approvalNeeded), + gasEstimateWithRefund, + gasEstimate, + averageGas, + maxGas, + gasMultiplier, + gasLimit, ); + let gasPrice; + if (isCustomEthGasPriceEstimate(gasFee) || isEthGasPriceEstimate(gasFee)) { + gasPrice = gasFee.gasPrice; + } else if (isCustomGasFee(gasFee)) { + gasPrice = gasFee.maxFeePerGas; + } else { + gasPrice = gasFee.high.suggestedMaxFeePerGas; + } - this.#fetchGasFeeEstimates = opts.fetchGasFeeEstimates; - this.#fetchEstimatedMultiLayerL1Fee = opts.fetchEstimatedMultiLayerL1Fee; + const maxTotalGasInWei = new BigNumber(tradeMaxGasLimit).times( + gweiDecToWEIBN(gasPrice).toString(16), + 16, + ); + const maxTotalInWei = maxTotalGasInWei.plus(trade.value ?? '0x0', 16); + const maxWeiFee = + sourceToken === NATIVE_SWAPS_TOKEN_ADDRESS + ? maxTotalInWei.minus(sourceAmount, 10) + : maxTotalInWei; + const maxEthFee = calcTokenAmount(maxWeiFee, 18).toFixed(18); + return maxEthFee; } /** * Updates all quotes with a new custom gas price. * @param customGasFee - Custom gas price in dec gwei format. */ - public updateQuotesWithGasPrice( + updateQuotesWithGasPrice( customGasFee: CustomEthGasPriceEstimate | CustomGasFee, ): void { const { quotes, usedGasEstimate } = this.state; if (!usedGasEstimate) { return; } - const { topAggId, quoteValues } = this.getBestQuoteAndQuotesValues( + const { topAggId, quoteValues } = this.#getBestQuoteAndQuotesValues( quotes, usedGasEstimate, customGasFee, @@ -776,7 +860,7 @@ export default class SwapsController extends BaseController< * Updates the selected quote maxEthFee param according to a custom gas limit. * @param customGasLimit - Custom gas limit in hex format. */ - public updateSelectedQuoteWithGasLimit(customGasLimit: string): void { + updateSelectedQuoteWithGasLimit(customGasLimit: string): void { const { topAggId, quotes, quoteValues, usedGasEstimate, usedCustomGas } = this.state; if (!topAggId || !quoteValues || !usedGasEstimate) { @@ -810,7 +894,7 @@ export default class SwapsController extends BaseController< * @param fetchParamsMetaData - Metadata for the fetchParams. * @returns Promise resolving when this operation completes. */ - public startFetchAndSetQuotes( + startFetchAndSetQuotes( fetchParams?: APIFetchQuotesParams, fetchParamsMetaData?: APIFetchQuotesMetadata, ) { @@ -831,39 +915,48 @@ export default class SwapsController extends BaseController< // ignoring rule since otherwise we need to change the behavior of the function // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.pollForNewQuotesWithThreshold(); + this.#pollForNewQuotesWithThreshold(); } /** * Fetches the tokens and updates the state with them. */ - public async fetchTokenWithCache() { - const { chainId, clientId, fetchTokensThreshold, supportedChainIds } = - this.state.config; + async fetchTokenWithCache() { const { tokens, tokensLastFetched } = this.state; - if (!supportedChainIds.includes(chainId)) { + if (!this.#supportedChainIds.includes(this.#chainId)) { return; } - if (!tokens || fetchTokensThreshold < Date.now() - tokensLastFetched) { + if ( + !tokens || + this.#fetchTokensThreshold < Date.now() - tokensLastFetched + ) { const releaseLock = await this.#mutex.acquire(); try { - const newTokens = await fetchTokens(chainId, clientId); + const newTokens = await fetchTokens(this.#chainId, this.#clientId); this.update((_state) => { _state.tokens = newTokens; _state.tokensLastFetched = Date.now(); - _state.chainCache = getNewChainCache(_state.chainCache, chainId, { - tokens: newTokens, - tokensLastFetched: Date.now(), - }); + _state.chainCache = getNewChainCache( + _state.chainCache, + this.#chainId, + { + tokens: newTokens, + tokensLastFetched: Date.now(), + }, + ); }); } catch { this.update((_state) => { _state.tokensLastFetched = 0; - _state.chainCache = getNewChainCache(_state.chainCache, chainId, { - tokensLastFetched: 0, - }); + _state.chainCache = getNewChainCache( + _state.chainCache, + this.#chainId, + { + tokensLastFetched: 0, + }, + ); }); } finally { releaseLock(); @@ -874,22 +967,23 @@ export default class SwapsController extends BaseController< /** * Fetches the top assets and updates the state with them. */ - public async fetchTopAssetsWithCache() { - const { chainId, clientId, fetchTopAssetsThreshold, supportedChainIds } = - this.state.config; + async fetchTopAssetsWithCache() { const { topAssets, topAssetsLastFetched } = this.state; - if (!supportedChainIds.includes(chainId)) { + if (!this.#supportedChainIds.includes(this.#chainId)) { return; } if ( !topAssets || - fetchTopAssetsThreshold < Date.now() - topAssetsLastFetched + this.#fetchTopAssetsThreshold < Date.now() - topAssetsLastFetched ) { const releaseLock = await this.#mutex.acquire(); try { - const newTopAssets = await fetchTopAssets(chainId, clientId); + const newTopAssets = await fetchTopAssets( + this.#chainId, + this.#clientId, + ); const data = { topAssets: newTopAssets, topAssetsLastFetched: Date.now(), @@ -899,7 +993,7 @@ export default class SwapsController extends BaseController< _state.topAssetsLastFetched = data.topAssetsLastFetched; _state.chainCache = getNewChainCache( _state.chainCache, - chainId, + this.#chainId, data, ); }); @@ -909,7 +1003,7 @@ export default class SwapsController extends BaseController< _state.topAssetsLastFetched = data.topAssetsLastFetched; _state.chainCache = getNewChainCache( _state.chainCache, - chainId, + this.#chainId, data, ); }); @@ -922,29 +1016,23 @@ export default class SwapsController extends BaseController< /** * Fetches the aggregator metadata and updates the state with it. */ - public async fetchAggregatorMetadataWithCache() { - const { - chainId, - clientId, - fetchAggregatorMetadataThreshold, - supportedChainIds, - } = this.state.config; + async fetchAggregatorMetadataWithCache() { const { aggregatorMetadata, aggregatorMetadataLastFetched } = this.state; - if (!supportedChainIds.includes(chainId)) { + if (!this.#supportedChainIds.includes(this.#chainId)) { return; } if ( !aggregatorMetadata || - fetchAggregatorMetadataThreshold < + this.#fetchAggregatorMetadataThreshold < Date.now() - aggregatorMetadataLastFetched ) { const releaseLock = await this.#mutex.acquire(); try { const newAggregatorMetada = await fetchAggregatorMetadata( - chainId, - clientId, + this.#chainId, + this.#clientId, ); const data = { aggregatorMetadata: newAggregatorMetada, @@ -956,7 +1044,7 @@ export default class SwapsController extends BaseController< data.aggregatorMetadataLastFetched; _state.chainCache = getNewChainCache( _state.chainCache, - chainId, + this.#chainId, data, ); }); @@ -967,7 +1055,7 @@ export default class SwapsController extends BaseController< data.aggregatorMetadataLastFetched; _state.chainCache = getNewChainCache( _state.chainCache, - chainId, + this.#chainId, data, ); }); @@ -983,9 +1071,9 @@ export default class SwapsController extends BaseController< * @param error.key - Error key. * @param error.description - Error description. */ - public stopPollingAndResetState( + stopPollingAndResetState( error: { - key: SwapsError | null; + key: null | SwapsError; description: string | null; } = { key: null, @@ -993,17 +1081,16 @@ export default class SwapsController extends BaseController< }, ) { this.#abortController && this.#abortController.abort(); - this.handle && clearTimeout(this.handle); - this.#pollCount = Number(this.state.config.pollCountLimit) + 1; + this.#handle && clearTimeout(this.#handle); + this.#pollCount = Number(this.#pollCountLimit) + 1; this.update((_state) => { const currentState = { ..._state }; const defaultState = getDefaultSwapsControllerState(); - getKnownPropertyTypes(defaultState).forEach((key) => { + getKnownPropertyNames(defaultState).forEach((key) => { const typedKey = key; (_state as any)[typedKey] = defaultState[typedKey]; }); _state.isInPolling = false; - _state.config = currentState.config; _state.tokensLastFetched = currentState.tokensLastFetched; _state.topAssetsLastFetched = currentState.topAssetsLastFetched; _state.aggregatorMetadataLastFetched = @@ -1017,50 +1104,25 @@ export default class SwapsController extends BaseController< }); } - /** - * Internal method used to set the chainId in the state. Users should use the `configure` method instead. - * @param chainId - The chainId to set. - */ - #setChainId(chainId: Hex) { - if (!this.state.config.supportedChainIds.includes(chainId)) { - return; - } - - const { chainCache } = this.state; - if (!chainCache?.[chainId]) { - this.update((_state) => { - _state.config.chainId = chainId; - _state.aggregatorMetadata = null; - _state.tokens = null; - _state.topAssets = null; - _state.aggregatorMetadataLastFetched = 0; - _state.topAssetsLastFetched = 0; - _state.tokensLastFetched = 0; - _state.chainCache = getNewChainCache(chainCache, chainId, { - ...INITIAL_CHAIN_DATA, - }); - }); - return; - } - - const cachedData = chainCache[chainId]; + setChainId = (chainId: Hex): void => { + this.#chainId = chainId; + this.#buildChainCache(chainId); + }; - this.update((_state) => { - _state.config.chainId = chainId; - _state.aggregatorMetadata = cachedData.aggregatorMetadata; - _state.tokens = cachedData.tokens; - _state.topAssets = cachedData.topAssets; - _state.aggregatorMetadataLastFetched = - #setProvider(provider: Provider) { + setProvider( + provider: Provider, + opts?: { chainId: Hex; pollCountLimit: number }, + ): void { // @ts-expect-error TODO: align `Web3` with EIP-1193 provider - this.web3 = new Web3(provider); - this.ethQuery = new EthQuery(provider); - } - } + this.#web3 = new Web3(provider); + this.#ethQuery = new EthQuery(provider); - #setProvider(provider: any) { - this.web3 = new Web3(provider); - this.ethQuery = new EthQuery(provider); + if (opts?.chainId) { + this.setChainId(opts.chainId); + } + if (opts?.pollCountLimit) { + this.#pollCountLimit = opts.pollCountLimit; + } } /** @@ -1069,11 +1131,77 @@ export default class SwapsController extends BaseController< * @param newState - The new state to set. */ // eslint-disable-next-line @typescript-eslint/naming-convention - public __test__updateState = ( - newState: Partial, - ): void => { + __test__updateState = (newState: Partial): void => { this.update((oldState) => { - return { ...oldState, ...newState }; + return { ...(oldState as SwapsControllerState), ...newState }; }); }; + + /** + * Helper method to update the internal class state. + * This method should not be used outside of testing. + * @param key - The key to update in the internal state. + * @param value - The value to set in the internal state. + * @returns The value set in the internal state. + */ + // eslint-disable-next-line @typescript-eslint/naming-convention + __test__updatePrivate = (key: string, value: any) => { + switch (key) { + case '#fetchAggregatorMetadataThreshold': + this.#fetchAggregatorMetadataThreshold = value; + return this.#fetchAggregatorMetadataThreshold; + case '#fetchTokensThreshold': + this.#fetchTokensThreshold = value; + return this.#fetchTokensThreshold; + case '#fetchTopAssetsThreshold': + this.#fetchTopAssetsThreshold = value; + return this.#fetchTopAssetsThreshold; + case '#supportedChainIds': + this.#supportedChainIds = value; + return this.#supportedChainIds; + case '#handle': + this.#handle = value; + return this.#handle; + default: + return undefined; + } + }; + + /** + * Helper method to get the internal class state. + * This method should not be used outside of testing. + * @param key - The key to get from the internal state. + * @returns The value from the internal state. + */ + // eslint-disable-next-line @typescript-eslint/naming-convention + __test__getInternal = (key: string) => { + switch (key) { + case '#fetchAggregatorMetadataThreshold': + return this.#fetchAggregatorMetadataThreshold; + case '#fetchTokensThreshold': + return this.#fetchTokensThreshold; + case '#fetchTopAssetsThreshold': + return this.#fetchTopAssetsThreshold; + case '#pollCountLimit': + return this.#pollCountLimit; + case '#chainId': + return this.#chainId; + case '#supportedChainIds': + return this.#supportedChainIds; + case '#clientId': + return this.#clientId; + case '#web3': + return this.#web3; + case '#ethQuery': + return this.#ethQuery; + case '#handle': + return this.#handle; + case '#fetchGasFeeEstimates': + return this.#fetchGasFeeEstimates; + case '#fetchEstimatedMultiLayerL1Fee': + return this.#fetchEstimatedMultiLayerL1Fee; + default: + return undefined; + } + }; } diff --git a/src/constants.ts b/src/constants.ts index 590e38e8..40b19a28 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -269,6 +269,6 @@ export const getDefaultSwapsControllerState = (): SwapsControllerState => ({ usedGasEstimate: null, usedCustomGas: null, chainCache: { - '0x1': INITIAL_CHAIN_DATA, + [ETH_CHAIN_ID]: INITIAL_CHAIN_DATA, }, }); diff --git a/src/index.ts b/src/index.ts index 6575d434..8e827e08 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,6 @@ import SwapsController from './SwapsController'; export * as swapsUtils from './swapsUtil'; export type { - SwapsConfig, SwapsControllerState, SwapsControllerGetStateAction, SwapsControllerStateChangeEvent, @@ -17,7 +16,6 @@ export type { SwapsControllerFetchTopAssetsWithCache, SwapsControllerFetchAggregatorMetadataWithCache, SwapsControllerStopPollingAndResetState, - SwapsControllerConfigure, } from './types'; export default SwapsController; diff --git a/src/swapsUtil.ts b/src/swapsUtil.ts index 6f56c7a4..66e671f3 100644 --- a/src/swapsUtil.ts +++ b/src/swapsUtil.ts @@ -735,7 +735,10 @@ export function calcTokenAmount(value: number | BigNumber, decimals: number) { * @param ethQuery - The ethQuery object. * @returns Promise resolving to an object containing gas and gasPrice. */ -export async function estimateGas(transaction: Omit & Partial>, ethQuery: any) { +export async function estimateGas( + transaction: Omit & Partial>, + ethQuery: any, +) { const estimatedTransaction = { ...transaction }; const { value, data } = estimatedTransaction; const { gasLimit } = await query(ethQuery, 'getBlockByNumber', [ diff --git a/src/types.ts b/src/types.ts index b427d22d..1600066b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -9,7 +9,7 @@ import type { GasFeeEstimates, GasFeeState, } from '@metamask/gas-fee-controller'; -import type { Hex } from '@metamask/utils'; +import type { Hex, JsonRpcError } from '@metamask/utils'; import type SwapsController from './SwapsController'; import type { controllerName, SwapsError } from './swapsUtil'; @@ -281,14 +281,13 @@ export type CustomGasFee = { selected?: 'low' | 'medium' | 'high'; }; - export type SwapsControllerState = { quotes: { [key: string]: Quote }; fetchParams: APIFetchQuotesParams; fetchParamsMetaData: APIFetchQuotesMetadata; topAggSavings: QuoteSavings | null; quotesLastFetched: null | number; - error: { key: null | keyof typeof SwapsError; description: null | string }; + error: { key: null | SwapsError; description: null | string }; topAggId: null | string; isInPolling: boolean; pollingCyclesLeft: number; @@ -339,8 +338,7 @@ export type SwapsControllerActions = | SwapsControllerFetchTokenWithCache | SwapsControllerFetchTopAssetsWithCache | SwapsControllerFetchAggregatorMetadataWithCache - | SwapsControllerStopPollingAndResetState - | SwapsControllerConfigure; + | SwapsControllerStopPollingAndResetState; /** * The events that the SwapsController can emit. @@ -360,13 +358,12 @@ export type SwapsControllerMessenger = RestrictedControllerMessenger< export type SwapsControllerOptions = { clientId?: string; - maxGasLimit: number; - pollCountLimit: number; - fetchAggregatorMetadataThreshold: number; - fetchTokensThreshold: number; - fetchTopAssetsThreshold: number; - chainId: Hex; - supportedChainIds: Hex[]; + pollCountLimit?: number; + fetchAggregatorMetadataThreshold?: number; + fetchTokensThreshold?: number; + fetchTopAssetsThreshold?: number; + chainId?: Hex; + supportedChainIds?: Hex[]; // TODO: Remove once GasFeeController exports this action type fetchGasFeeEstimates?: () => Promise; fetchEstimatedMultiLayerL1Fee?: ( @@ -377,7 +374,6 @@ export type SwapsControllerOptions = { }, ) => Promise; messenger: SwapsControllerMessenger; - state: Partial }; /** @@ -435,4 +431,3 @@ export type SwapsControllerStopPollingAndResetState = { type: `SwapsController:stopPollingAndResetState`; handler: SwapsController['stopPollingAndResetState']; }; - From c84386d4099b4818666b4a4e7518bd5c2c151c66 Mon Sep 17 00:00:00 2001 From: nikoferro Date: Fri, 23 Aug 2024 15:39:08 +0200 Subject: [PATCH 26/32] chore: make explicit which properties are already anonymous --- src/SwapsController.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/SwapsController.ts b/src/SwapsController.ts index e67d5f82..bba1b32e 100644 --- a/src/SwapsController.ts +++ b/src/SwapsController.ts @@ -75,19 +75,19 @@ const metadata: StateMetadata = { fetchParams: { persist: false, anonymous: false }, fetchParamsMetaData: { persist: false, anonymous: false }, topAggSavings: { persist: false, anonymous: false }, - aggregatorMetadata: { persist: false, anonymous: false }, - tokens: { persist: false, anonymous: false }, - topAssets: { persist: false, anonymous: false }, + aggregatorMetadata: { persist: false, anonymous: true }, + tokens: { persist: false, anonymous: true }, + topAssets: { persist: false, anonymous: true }, approvalTransaction: { persist: false, anonymous: false }, - aggregatorMetadataLastFetched: { persist: false, anonymous: false }, - quotesLastFetched: { persist: false, anonymous: false }, - topAssetsLastFetched: { persist: false, anonymous: false }, + aggregatorMetadataLastFetched: { persist: false, anonymous: true }, + quotesLastFetched: { persist: false, anonymous: true }, + topAssetsLastFetched: { persist: false, anonymous: true }, error: { persist: false, anonymous: false }, topAggId: { persist: false, anonymous: false }, - tokensLastFetched: { persist: false, anonymous: false }, - isInPolling: { persist: false, anonymous: false }, - pollingCyclesLeft: { persist: false, anonymous: false }, - quoteRefreshSeconds: { persist: false, anonymous: false }, + tokensLastFetched: { persist: false, anonymous: true }, + isInPolling: { persist: false, anonymous: true }, + pollingCyclesLeft: { persist: false, anonymous: true }, + quoteRefreshSeconds: { persist: false, anonymous: true }, usedGasEstimate: { persist: false, anonymous: false }, usedCustomGas: { persist: false, anonymous: false }, chainCache: { persist: false, anonymous: false }, From 160ffed8fd9cf5c22f2956900c6876431d200d89 Mon Sep 17 00:00:00 2001 From: nikoferro Date: Fri, 23 Aug 2024 16:03:18 +0200 Subject: [PATCH 27/32] chore: default value for state as an optional parameter --- src/SwapsController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SwapsController.ts b/src/SwapsController.ts index bba1b32e..7dc26752 100644 --- a/src/SwapsController.ts +++ b/src/SwapsController.ts @@ -524,7 +524,7 @@ export default class SwapsController extends BaseController< fetchGasFeeEstimates, fetchEstimatedMultiLayerL1Fee, }: SwapsControllerOptions, - state: Partial, + state: Partial = {}, ) { super({ name: controllerName, From 5fd1d18135cab39a2340df0493d973cda822f155 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Thu, 22 Aug 2024 14:19:39 -0400 Subject: [PATCH 28/32] chore: remove web3 package --- package.json | 3 +- yarn.lock | 288 +-------------------------------------------------- 2 files changed, 6 insertions(+), 285 deletions(-) diff --git a/package.json b/package.json index bde4b074..85a5e6df 100644 --- a/package.json +++ b/package.json @@ -77,8 +77,7 @@ "async-mutex": "^0.5.0", "bignumber.js": "^9.0.1", "bn.js": "^5.2.1", - "human-standard-token-abi": "^2.0.0", - "web3": "^4.2.2" + "human-standard-token-abi": "^2.0.0" }, "lavamoat": { "allowScripts": { diff --git a/yarn.lock b/yarn.lock index 8f8c3511..3eb793bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,11 +7,6 @@ resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== -"@adraffy/ens-normalize@^1.8.8": - version "1.10.1" - resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz#63430d04bd8c5e74f8d7d049338f1cd9d4f02069" - integrity sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw== - "@ampproject/remapping@^2.2.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" @@ -1757,13 +1752,6 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== -"@types/ws@8.5.3": - version "8.5.3" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" - integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== - dependencies: - "@types/node" "*" - "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -1882,11 +1870,6 @@ abbrev@^2.0.0: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-2.0.0.tgz#cf59829b8b4f03f89dda2771cb7f3653828c89bf" integrity sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ== -abitype@0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.7.1.tgz#16db20abe67de80f6183cf75f3de1ff86453b745" - integrity sha512-VBkRHTDZf9Myaek/dO3yMmOzB/y2s3Zo6nVU7yaw1G+TvCHAjwaJzNGN9yo4K5D8bU/VZXKP1EJpRhFr862PlQ== - acorn-globals@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-7.0.1.tgz#0dbf05c44fa7c94332914c02066d5beff62c40c3" @@ -2490,7 +2473,7 @@ core-js@^2.4.0: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== -crc-32@^1.2.0, crc-32@^1.2.2: +crc-32@^1.2.0: version "1.2.2" resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== @@ -2536,13 +2519,6 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -cross-fetch@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983" - integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g== - dependencies: - node-fetch "^2.6.12" - cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -3258,11 +3234,6 @@ ethjs-schema@0.2.1: resolved "https://registry.yarnpkg.com/ethjs-schema/-/ethjs-schema-0.2.1.tgz#47e138920421453617069034684642e26bb310f4" integrity sha512-DXd8lwNrhT9sjsh/Vd2Z+4pfyGxhc0POVnLBUfwk5udtdoBzADyq+sK39dcb48+ZU+2VgtwHxtGWnLnCfmfW5g== -eventemitter3@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" - integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== - evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" @@ -3823,14 +3794,6 @@ ip-address@^9.0.5: jsbn "1.1.0" sprintf-js "^1.1.3" -is-arguments@^1.0.4: - version "1.1.1" - resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" - integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - is-array-buffer@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" @@ -3905,13 +3868,6 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== -is-generator-function@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" - integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== - dependencies: - has-tostringtag "^1.0.0" - is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -3990,7 +3946,7 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" -is-typed-array@^1.1.13, is-typed-array@^1.1.3: +is-typed-array@^1.1.13: version "1.1.13" resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" integrity sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw== @@ -4019,11 +3975,6 @@ isexe@^3.1.1: resolved "https://registry.yarnpkg.com/isexe/-/isexe-3.1.1.tgz#4a407e2bd78ddfb14bea0c27c6f7072dde775f0d" integrity sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ== -isomorphic-ws@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz#e5529148912ecb9b451b46ed44d53dae1ce04bbf" - integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== - istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" @@ -4917,7 +4868,7 @@ node-addon-api@^2.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32" integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA== -node-fetch@^2.6.12, node-fetch@^2.7.0: +node-fetch@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -6134,17 +6085,6 @@ util-deprecate@^1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -util@^0.12.5: - version "0.12.5" - resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" - integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== - dependencies: - inherits "^2.0.3" - is-arguments "^1.0.4" - is-generator-function "^1.0.7" - is-typed-array "^1.1.3" - which-typed-array "^1.1.2" - uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" @@ -6203,219 +6143,6 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -web3-core@^4.3.0, web3-core@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-4.3.2.tgz#f24b11d6a57dee527de8d42c89de2a439f0c4bed" - integrity sha512-uIMVd/j4BgOnwfpY8ZT+QKubOyM4xohEhFZXz9xB8wimXWMMlYVlIK/TbfHqFolS9uOerdSGhsMbcK9lETae8g== - dependencies: - web3-errors "^1.1.4" - web3-eth-accounts "^4.1.0" - web3-eth-iban "^4.0.7" - web3-providers-http "^4.1.0" - web3-providers-ws "^4.0.7" - web3-types "^1.3.1" - web3-utils "^4.1.0" - web3-validator "^2.0.3" - optionalDependencies: - web3-providers-ipc "^4.0.7" - -web3-errors@^1.1.3, web3-errors@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/web3-errors/-/web3-errors-1.1.4.tgz#5667a0a5f66fc936e101ef32032ccc1e8ca4d5a1" - integrity sha512-WahtszSqILez+83AxGecVroyZsMuuRT+KmQp4Si5P4Rnqbczno1k748PCrZTS1J4UCPmXMG2/Vt+0Bz2zwXkwQ== - dependencies: - web3-types "^1.3.1" - -web3-eth-abi@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-4.2.0.tgz#398d415e7783442d06fb7939e40ce3de7a3f04e9" - integrity sha512-x7dUCmk6th+5N63s5kUusoNtsDJKUUQgl9+jECvGTBOTiyHe/V6aOY0120FUjaAGaapOnR7BImQdhqHv6yT2YQ== - dependencies: - abitype "0.7.1" - web3-errors "^1.1.4" - web3-types "^1.3.1" - web3-utils "^4.1.1" - web3-validator "^2.0.4" - -web3-eth-accounts@^4.1.0, web3-eth-accounts@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-4.1.1.tgz#55225e5510b961e1cacb4eccc996544998e907fc" - integrity sha512-9JqhRi1YhO1hQOEmmBHgEGsME/B1FHMxpA/AK3vhpvQ8QeP6KbJW+cForTLfPpUbkmPxnRunG4PNNaETNlZfrA== - dependencies: - "@ethereumjs/rlp" "^4.0.1" - crc-32 "^1.2.2" - ethereum-cryptography "^2.0.0" - web3-errors "^1.1.4" - web3-types "^1.3.1" - web3-utils "^4.1.1" - web3-validator "^2.0.4" - -web3-eth-contract@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-4.3.0.tgz#5cacbac25f9dbb27bea90ea99fea290e5ebd3f87" - integrity sha512-4fzSklA65zUn6SthU3T3tbVJacfP8/wkJmCuvmPaf2ZTFdnhsF96G5IQtCRf0+wASb4yk0A6IBvXZfk1B4R4HA== - dependencies: - web3-core "^4.3.2" - web3-errors "^1.1.4" - web3-eth "^4.5.0" - web3-eth-abi "^4.2.0" - web3-types "^1.5.0" - web3-utils "^4.2.2" - web3-validator "^2.0.5" - -web3-eth-ens@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-4.2.0.tgz#8734b034efd48a735f7052fef0205653a78b84cb" - integrity sha512-qYj34te2UctoObt8rlEIY/t2MuTMiMiiHhO2JAHRGqSLCQ7b8DM3RpvkiiSB0N0ZyEn+CetZqJCTYb8DNKBS/g== - dependencies: - "@adraffy/ens-normalize" "^1.8.8" - web3-core "^4.3.2" - web3-errors "^1.1.4" - web3-eth "^4.5.0" - web3-eth-contract "^4.3.0" - web3-net "^4.0.7" - web3-types "^1.5.0" - web3-utils "^4.2.2" - web3-validator "^2.0.5" - -web3-eth-iban@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-4.0.7.tgz#ee504f845d7b6315f0be78fcf070ccd5d38e4aaf" - integrity sha512-8weKLa9KuKRzibC87vNLdkinpUE30gn0IGY027F8doeJdcPUfsa4IlBgNC4k4HLBembBB2CTU0Kr/HAOqMeYVQ== - dependencies: - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - -web3-eth-personal@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-4.0.8.tgz#b51628c560de550ca8b354fa784f9556aae6065c" - integrity sha512-sXeyLKJ7ddQdMxz1BZkAwImjqh7OmKxhXoBNF3isDmD4QDpMIwv/t237S3q4Z0sZQamPa/pHebJRWVuvP8jZdw== - dependencies: - web3-core "^4.3.0" - web3-eth "^4.3.1" - web3-rpc-methods "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - web3-validator "^2.0.3" - -web3-eth@^4.3.1, web3-eth@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-4.5.0.tgz#57f5cc020c9b3c4c20d0dacbd87eaa1a9d6c86c0" - integrity sha512-crisE46o/SHMVm+XHAXEaR8k76NCImq+hi0QQEJ+VaLZbDobI/Gvog1HwTukDUDRgnYSAFGqD0cTRyAwDurwpA== - dependencies: - setimmediate "^1.0.5" - web3-core "^4.3.2" - web3-errors "^1.1.4" - web3-eth-abi "^4.2.0" - web3-eth-accounts "^4.1.1" - web3-net "^4.0.7" - web3-providers-ws "^4.0.7" - web3-rpc-methods "^1.2.0" - web3-types "^1.5.0" - web3-utils "^4.2.1" - web3-validator "^2.0.4" - -web3-net@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-4.0.7.tgz#ed2c1bd700cf94be93a6dbd8bd8aa413d8681942" - integrity sha512-SzEaXFrBjY25iQGk5myaOfO9ZyfTwQEa4l4Ps4HDNVMibgZji3WPzpjq8zomVHMwi8bRp6VV7YS71eEsX7zLow== - dependencies: - web3-core "^4.3.0" - web3-rpc-methods "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - -web3-providers-http@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-4.1.0.tgz#8d7afda67d1d8542ca85b30f60a3d1fe1993b561" - integrity sha512-6qRUGAhJfVQM41E5t+re5IHYmb5hSaLc02BE2MaRQsz2xKA6RjmHpOA5h/+ojJxEpI9NI2CrfDKOAgtJfoUJQg== - dependencies: - cross-fetch "^4.0.0" - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - -web3-providers-ipc@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-4.0.7.tgz#9ec4c8565053af005a5170ba80cddeb40ff3e3d3" - integrity sha512-YbNqY4zUvIaK2MHr1lQFE53/8t/ejHtJchrWn9zVbFMGXlTsOAbNoIoZWROrg1v+hCBvT2c9z8xt7e/+uz5p1g== - dependencies: - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - -web3-providers-ws@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-4.0.7.tgz#7a78a0dcf077e0e802da524fbb37d080b356c14b" - integrity sha512-n4Dal9/rQWjS7d6LjyEPM2R458V8blRm0eLJupDEJOOIBhGYlxw5/4FthZZ/cqB7y/sLVi7K09DdYx2MeRtU5w== - dependencies: - "@types/ws" "8.5.3" - isomorphic-ws "^5.0.0" - web3-errors "^1.1.3" - web3-types "^1.3.0" - web3-utils "^4.0.7" - ws "^8.8.1" - -web3-rpc-methods@^1.1.3, web3-rpc-methods@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/web3-rpc-methods/-/web3-rpc-methods-1.2.0.tgz#761dcb036ab16edb2b03e80c11e3f5df24690345" - integrity sha512-CWJ/g4I4WyYvLkf21wCZAehdhU/VjX/OAPHnqF5/FPDJlogOsOnGXHqi1Z5AP+ocdt395PNubd8jyMMJoYGSBA== - dependencies: - web3-core "^4.3.2" - web3-types "^1.5.0" - web3-validator "^2.0.4" - -web3-types@^1.3.0, web3-types@^1.3.1, web3-types@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/web3-types/-/web3-types-1.5.0.tgz#35b5c0ab149b0d566efeaed8ddaa40db159c748e" - integrity sha512-geWuMIeegQ8AedKAO6wO4G4j1gyQ1F/AyKLMw2vud4bsfZayyzWJgCMDZtjYMm5uo2a7i8j1W3/4QFmzlSy5cw== - -web3-utils@^4.0.7, web3-utils@^4.1.0, web3-utils@^4.1.1, web3-utils@^4.2.1, web3-utils@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-4.2.2.tgz#8fb7c58cfc02d681f17d7806732ce9fb1170c338" - integrity sha512-z+4owWcnoB4EH8yWIL1FBeyqe+sXwaGxUDtVTNPTMf2oB5C+paCToZUdCV5Bi+M543zZEzlzNTabOD+OWNc7NA== - dependencies: - ethereum-cryptography "^2.0.0" - eventemitter3 "^5.0.1" - web3-errors "^1.1.4" - web3-types "^1.5.0" - web3-validator "^2.0.5" - -web3-validator@^2.0.3, web3-validator@^2.0.4, web3-validator@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/web3-validator/-/web3-validator-2.0.5.tgz#de1984bdb34f292251b86400dba7169700db0849" - integrity sha512-2gLOSW8XqEN5pw5jVUm20EB7A8SbQiekpAtiI0JBmCIV0a2rp97v8FgWY5E3UEqnw5WFfEqvcDVW92EyynDTyQ== - dependencies: - ethereum-cryptography "^2.0.0" - util "^0.12.5" - web3-errors "^1.1.4" - web3-types "^1.5.0" - zod "^3.21.4" - -web3@^4.2.2: - version "4.7.0" - resolved "https://registry.yarnpkg.com/web3/-/web3-4.7.0.tgz#d6cb8ff8653b92f26ddd6da0957999e61ae7f107" - integrity sha512-3g+1e7B/IW0Nw9WP1dotrZKWD9o5IBfl27dxEnE1LxBZBax6ZkviiAwf18utIhlNBD07RgI+PPfKDXxfDBlHWA== - dependencies: - web3-core "^4.3.2" - web3-errors "^1.1.4" - web3-eth "^4.5.0" - web3-eth-abi "^4.2.0" - web3-eth-accounts "^4.1.1" - web3-eth-contract "^4.3.0" - web3-eth-ens "^4.2.0" - web3-eth-iban "^4.0.7" - web3-eth-personal "^4.0.8" - web3-net "^4.0.7" - web3-providers-http "^4.1.0" - web3-providers-ws "^4.0.7" - web3-rpc-methods "^1.2.0" - web3-types "^1.5.0" - web3-utils "^4.2.2" - web3-validator "^2.0.5" - webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -6465,7 +6192,7 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" -which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.2: +which-typed-array@^1.1.14, which-typed-array@^1.1.15: version "1.1.15" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== @@ -6543,7 +6270,7 @@ ws@7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== -ws@^8.11.0, ws@^8.8.1: +ws@^8.11.0: version "8.16.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== @@ -6610,8 +6337,3 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -zod@^3.21.4: - version "3.22.4" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff" - integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg== From ae1e8fef2647b97dec6e308d24f134b3f38c9922 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Thu, 22 Aug 2024 14:20:14 -0400 Subject: [PATCH 29/32] chore: add @ethersproject/contracts --- package.json | 1 + yarn.lock | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/package.json b/package.json index 85a5e6df..8b19d856 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "typescript": "^5.1.0" }, "dependencies": { + "@ethersproject/contracts": "^5.7.0", "@metamask/base-controller": "^5.0.0", "@metamask/controller-utils": "^10.0.0", "@metamask/eth-query": "^4.0.0", diff --git a/yarn.lock b/yarn.lock index 3eb793bf..6a5235c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -478,6 +478,22 @@ dependencies: "@ethersproject/bignumber" "^5.7.0" +"@ethersproject/contracts@^5.7.0": + version "5.7.0" + resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.7.0.tgz#c305e775abd07e48aa590e1a877ed5c316f8bd1e" + integrity sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg== + dependencies: + "@ethersproject/abi" "^5.7.0" + "@ethersproject/abstract-provider" "^5.7.0" + "@ethersproject/abstract-signer" "^5.7.0" + "@ethersproject/address" "^5.7.0" + "@ethersproject/bignumber" "^5.7.0" + "@ethersproject/bytes" "^5.7.0" + "@ethersproject/constants" "^5.7.0" + "@ethersproject/logger" "^5.7.0" + "@ethersproject/properties" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@ethersproject/hash@^5.7.0": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.7.0.tgz#eb7aca84a588508369562e16e514b539ba5240a7" From 163ba05996803287520373e3714fc83a46dac5d3 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:37:46 -0400 Subject: [PATCH 30/32] chore: add @ethersproject/providers --- package.json | 1 + yarn.lock | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 8b19d856..5a7bc8e4 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ }, "dependencies": { "@ethersproject/contracts": "^5.7.0", + "@ethersproject/providers": "^5.7.0", "@metamask/base-controller": "^5.0.0", "@metamask/controller-utils": "^10.0.0", "@metamask/eth-query": "^4.0.0", diff --git a/yarn.lock b/yarn.lock index 6a5235c3..20c2a273 100644 --- a/yarn.lock +++ b/yarn.lock @@ -536,7 +536,7 @@ dependencies: "@ethersproject/logger" "^5.7.0" -"@ethersproject/providers@^5.7.2": +"@ethersproject/providers@^5.7.0", "@ethersproject/providers@^5.7.2": version "5.7.2" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.7.2.tgz#f8b1a4f275d7ce58cf0a2eec222269a08beb18cb" integrity sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg== From 3732338d3fdb5eb0d8270499de30d662d311f911 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Fri, 23 Aug 2024 14:42:20 -0400 Subject: [PATCH 31/32] chore: remove web3 and use ethers contracts instead --- src/SwapsController.ts | 34 +++++++++++++++++++--------------- src/types.ts | 8 +++++++- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/SwapsController.ts b/src/SwapsController.ts index 7dc26752..de9845fb 100644 --- a/src/SwapsController.ts +++ b/src/SwapsController.ts @@ -1,3 +1,5 @@ +import { Contract } from '@ethersproject/contracts'; +import { Web3Provider } from '@ethersproject/providers'; import type { StateMetadata } from '@metamask/base-controller'; import { BaseController } from '@metamask/base-controller'; import { @@ -18,8 +20,6 @@ import { getKnownPropertyNames, type Hex } from '@metamask/utils'; import { Mutex } from 'async-mutex'; import { BigNumber } from 'bignumber.js'; import abiERC20 from 'human-standard-token-abi'; -import type { Web3 as Web3Type } from 'web3'; -import * as web3 from 'web3'; import { AVALANCHE_CHAIN_ID, @@ -66,9 +66,6 @@ import type { TxParams, } from './types'; -// Hack to fix the issue with the web3 import that works different in app vs tests -const Web3 = web3.Web3 === undefined ? web3.default : web3.Web3; - const metadata: StateMetadata = { quotes: { persist: false, anonymous: false }, quoteValues: { persist: false, anonymous: false }, @@ -120,8 +117,6 @@ export default class SwapsController extends BaseController< #supportedChainIds: Hex[]; - #web3: Web3Type; - #chainId: Hex; // TODO: Remove once GasFeeController exports this action type @@ -224,7 +219,18 @@ export default class SwapsController extends BaseController< contractAddress: string, walletAddress: string, ): Promise { - const contract = new this.#web3.eth.Contract(abiERC20, contractAddress); + const networkClientId = this.messagingSystem.call( + 'NetworkController:findNetworkClientIdByChainId', + this.#chainId, + ); + const { provider } = this.messagingSystem.call( + 'NetworkController:getNetworkClientById', + networkClientId, + ); + const web3provider = new Web3Provider(provider as any); + + const contract = new Contract(contractAddress, abiERC20, web3provider); + const allowanceTimeout = new Promise((_, reject) => { setTimeout(() => { reject(new Error(SwapsError.SWAPS_ALLOWANCE_TIMEOUT)); @@ -232,9 +238,11 @@ export default class SwapsController extends BaseController< }); const allowancePromise = async () => { - const result: bigint = await contract.methods - .allowance(walletAddress, getSwapsContractAddress(this.#chainId)) - .call(); + const result = await contract.allowance( + walletAddress, + getSwapsContractAddress(this.#chainId), + ); + return new BigNumber(result.toString()); }; @@ -1113,8 +1121,6 @@ export default class SwapsController extends BaseController< provider: Provider, opts?: { chainId: Hex; pollCountLimit: number }, ): void { - // @ts-expect-error TODO: align `Web3` with EIP-1193 provider - this.#web3 = new Web3(provider); this.#ethQuery = new EthQuery(provider); if (opts?.chainId) { @@ -1190,8 +1196,6 @@ export default class SwapsController extends BaseController< return this.#supportedChainIds; case '#clientId': return this.#clientId; - case '#web3': - return this.#web3; case '#ethQuery': return this.#ethQuery; case '#handle': diff --git a/src/types.ts b/src/types.ts index 1600066b..ba9571ec 100644 --- a/src/types.ts +++ b/src/types.ts @@ -9,6 +9,10 @@ import type { GasFeeEstimates, GasFeeState, } from '@metamask/gas-fee-controller'; +import type { + NetworkControllerFindNetworkClientIdByChainIdAction, + NetworkControllerGetNetworkClientByIdAction, +} from '@metamask/network-controller'; import type { Hex, JsonRpcError } from '@metamask/utils'; import type SwapsController from './SwapsController'; @@ -325,7 +329,9 @@ export type SwapsControllerStateChangeEvent = ControllerStateChangeEvent< * The external actions available to the {@link SwapsController}. * TODO: Add GasFeeControllerFetchGasFeeEstimates once GasFeeController exports this action type */ -export type AllowedActions = never; +export type AllowedActions = + | NetworkControllerFindNetworkClientIdByChainIdAction + | NetworkControllerGetNetworkClientByIdAction; /** * The internal actions available to the SwapsController. From c48237d3b3941d0b22b629fab427f7d62dc480d1 Mon Sep 17 00:00:00 2001 From: IF <139582705+infiniteflower@users.noreply.github.com> Date: Fri, 23 Aug 2024 16:35:55 -0400 Subject: [PATCH 32/32] fix: broken tests --- src/SwapsController.test.ts | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/SwapsController.test.ts b/src/SwapsController.test.ts index c094c4d1..e96332a4 100644 --- a/src/SwapsController.test.ts +++ b/src/SwapsController.test.ts @@ -146,19 +146,12 @@ jest.mock('@metamask/eth-query', () => }), ); -// Mock implementation of web3 -jest.mock('web3', () => { +jest.mock('@ethersproject/contracts', () => { return { - Web3: jest.fn(() => ({ - eth: { - Contract: jest.fn(() => ({ - methods: { - allowance: jest.fn(() => ({ - call: jest.fn().mockResolvedValue('1000000000000000000'), // Mocked allowance value - })), - }, - })), - }, + Contract: jest.fn(() => ({ + allowance: jest.fn(() => ({ + call: jest.fn().mockResolvedValue('1000000000000000000'), // Mocked allowance value + })), })), }; }); @@ -383,12 +376,10 @@ describe('SwapsController', () => { rpcUrl: 'test', } as unknown as Provider; - expect(swapsController.__test__getInternal('#web3')).toBeUndefined(); expect(swapsController.__test__getInternal('#ethQuery')).toBeUndefined(); swapsController.setProvider(provider); - expect(swapsController.__test__getInternal('#web3')).toBeDefined(); expect(swapsController.__test__getInternal('#ethQuery')).toBeDefined(); }); }); @@ -402,7 +393,6 @@ describe('SwapsController', () => { rpcUrl: 'test', } as unknown as Provider; - expect(swapsController.__test__getInternal('#web3')).toBeUndefined(); expect(swapsController.__test__getInternal('#ethQuery')).toBeUndefined(); swapsController.setProvider(provider, { @@ -410,7 +400,6 @@ describe('SwapsController', () => { pollCountLimit: 10, }); - expect(swapsController.__test__getInternal('#web3')).toBeDefined(); expect(swapsController.__test__getInternal('#ethQuery')).toBeDefined(); expect(swapsController.__test__getInternal('#chainId')).toBe('0x23'); expect(swapsController.__test__getInternal('#pollCountLimit')).toBe(10);