mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 09:57:02 +01:00
Quotes prefetching in Swaps (#11915)
This commit is contained in:
parent
9e4b43defd
commit
28fc2d471f
@ -2155,10 +2155,6 @@
|
|||||||
"message": "No tokens available matching $1",
|
"message": "No tokens available matching $1",
|
||||||
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
||||||
},
|
},
|
||||||
"swapCheckingQuote": {
|
|
||||||
"message": "Checking $1",
|
|
||||||
"description": "Shown to the user during quote loading. $1 is the name of an aggregator. The message indicates that metamask is currently checking if that aggregator has a trade/quote for their requested swap."
|
|
||||||
},
|
|
||||||
"swapConfirmWithHwWallet": {
|
"swapConfirmWithHwWallet": {
|
||||||
"message": "Confirm with your hardware wallet"
|
"message": "Confirm with your hardware wallet"
|
||||||
},
|
},
|
||||||
@ -2204,6 +2200,9 @@
|
|||||||
"swapFailedErrorTitle": {
|
"swapFailedErrorTitle": {
|
||||||
"message": "Swap failed"
|
"message": "Swap failed"
|
||||||
},
|
},
|
||||||
|
"swapFetchingQuotes": {
|
||||||
|
"message": "Fetching quotes"
|
||||||
|
},
|
||||||
"swapFetchingQuotesErrorDescription": {
|
"swapFetchingQuotesErrorDescription": {
|
||||||
"message": "Hmmm... something went wrong. Try again, or if errors persist, contact customer support."
|
"message": "Hmmm... something went wrong. Try again, or if errors persist, contact customer support."
|
||||||
},
|
},
|
||||||
@ -2213,9 +2212,6 @@
|
|||||||
"swapFetchingTokens": {
|
"swapFetchingTokens": {
|
||||||
"message": "Fetching tokens..."
|
"message": "Fetching tokens..."
|
||||||
},
|
},
|
||||||
"swapFinalizing": {
|
|
||||||
"message": "Finalizing..."
|
|
||||||
},
|
|
||||||
"swapFromTo": {
|
"swapFromTo": {
|
||||||
"message": "The swap of $1 to $2",
|
"message": "The swap of $1 to $2",
|
||||||
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
||||||
|
@ -1870,10 +1870,6 @@
|
|||||||
"message": "No hay tokens disponibles que coincidan con $1",
|
"message": "No hay tokens disponibles que coincidan con $1",
|
||||||
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
||||||
},
|
},
|
||||||
"swapCheckingQuote": {
|
|
||||||
"message": "Comprobando $1",
|
|
||||||
"description": "Shown to the user during quote loading. $1 is the name of an aggregator. The message indicates that metamask is currently checking if that aggregator has a trade/quote for their requested swap."
|
|
||||||
},
|
|
||||||
"swapConfirmWithHwWallet": {
|
"swapConfirmWithHwWallet": {
|
||||||
"message": "Confirmar con la cartera de hardware"
|
"message": "Confirmar con la cartera de hardware"
|
||||||
},
|
},
|
||||||
@ -1925,9 +1921,6 @@
|
|||||||
"swapFetchingTokens": {
|
"swapFetchingTokens": {
|
||||||
"message": "Capturando tokens…"
|
"message": "Capturando tokens…"
|
||||||
},
|
},
|
||||||
"swapFinalizing": {
|
|
||||||
"message": "Finalizando…"
|
|
||||||
},
|
|
||||||
"swapFromTo": {
|
"swapFromTo": {
|
||||||
"message": "El canje de $1 por $2",
|
"message": "El canje de $1 por $2",
|
||||||
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
||||||
|
@ -1870,10 +1870,6 @@
|
|||||||
"message": "No hay tokens disponibles que coincidan con $1",
|
"message": "No hay tokens disponibles que coincidan con $1",
|
||||||
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
||||||
},
|
},
|
||||||
"swapCheckingQuote": {
|
|
||||||
"message": "Comprobando $1",
|
|
||||||
"description": "Shown to the user during quote loading. $1 is the name of an aggregator. The message indicates that metamask is currently checking if that aggregator has a trade/quote for their requested swap."
|
|
||||||
},
|
|
||||||
"swapConfirmWithHwWallet": {
|
"swapConfirmWithHwWallet": {
|
||||||
"message": "Confirmar con la cartera de hardware"
|
"message": "Confirmar con la cartera de hardware"
|
||||||
},
|
},
|
||||||
@ -1925,9 +1921,6 @@
|
|||||||
"swapFetchingTokens": {
|
"swapFetchingTokens": {
|
||||||
"message": "Capturando tokens…"
|
"message": "Capturando tokens…"
|
||||||
},
|
},
|
||||||
"swapFinalizing": {
|
|
||||||
"message": "Finalizando…"
|
|
||||||
},
|
|
||||||
"swapFromTo": {
|
"swapFromTo": {
|
||||||
"message": "El canje de $1 por $2",
|
"message": "El canje de $1 por $2",
|
||||||
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
||||||
|
@ -1870,10 +1870,6 @@
|
|||||||
"message": "$1 के मिलान वाले कोई भी टोकन उपलब्ध नहीं हैं",
|
"message": "$1 के मिलान वाले कोई भी टोकन उपलब्ध नहीं हैं",
|
||||||
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
||||||
},
|
},
|
||||||
"swapCheckingQuote": {
|
|
||||||
"message": "$1 की जाँच की जा रही है",
|
|
||||||
"description": "Shown to the user during quote loading. $1 is the name of an aggregator. The message indicates that metamask is currently checking if that aggregator has a trade/quote for their requested swap."
|
|
||||||
},
|
|
||||||
"swapConfirmWithHwWallet": {
|
"swapConfirmWithHwWallet": {
|
||||||
"message": "अपने हार्डवेयर वॉलेट से पुष्टि करें"
|
"message": "अपने हार्डवेयर वॉलेट से पुष्टि करें"
|
||||||
},
|
},
|
||||||
@ -1925,9 +1921,6 @@
|
|||||||
"swapFetchingTokens": {
|
"swapFetchingTokens": {
|
||||||
"message": "टोकन प्राप्त किए जा रहे हैं..."
|
"message": "टोकन प्राप्त किए जा रहे हैं..."
|
||||||
},
|
},
|
||||||
"swapFinalizing": {
|
|
||||||
"message": "अंतिम रूप दिया जा रहा है..."
|
|
||||||
},
|
|
||||||
"swapFromTo": {
|
"swapFromTo": {
|
||||||
"message": "$1 से $2 का स्वैप",
|
"message": "$1 से $2 का स्वैप",
|
||||||
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
||||||
|
@ -1870,10 +1870,6 @@
|
|||||||
"message": "Tidak ada token yang cocok yang tersedia $1",
|
"message": "Tidak ada token yang cocok yang tersedia $1",
|
||||||
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
||||||
},
|
},
|
||||||
"swapCheckingQuote": {
|
|
||||||
"message": "Memeriksa $1",
|
|
||||||
"description": "Shown to the user during quote loading. $1 is the name of an aggregator. The message indicates that metamask is currently checking if that aggregator has a trade/quote for their requested swap."
|
|
||||||
},
|
|
||||||
"swapConfirmWithHwWallet": {
|
"swapConfirmWithHwWallet": {
|
||||||
"message": "Konfirmasikan dengan dompet perangkat keras Anda"
|
"message": "Konfirmasikan dengan dompet perangkat keras Anda"
|
||||||
},
|
},
|
||||||
@ -1925,9 +1921,6 @@
|
|||||||
"swapFetchingTokens": {
|
"swapFetchingTokens": {
|
||||||
"message": "Mengambil token..."
|
"message": "Mengambil token..."
|
||||||
},
|
},
|
||||||
"swapFinalizing": {
|
|
||||||
"message": "Menyelesaikan..."
|
|
||||||
},
|
|
||||||
"swapFromTo": {
|
"swapFromTo": {
|
||||||
"message": "Penukaran dari $1 ke $2",
|
"message": "Penukaran dari $1 ke $2",
|
||||||
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
||||||
|
@ -1522,10 +1522,6 @@
|
|||||||
"message": "Non ci sono token disponibile con questo nome $1",
|
"message": "Non ci sono token disponibile con questo nome $1",
|
||||||
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
||||||
},
|
},
|
||||||
"swapCheckingQuote": {
|
|
||||||
"message": "Verificando $1",
|
|
||||||
"description": "Shown to the user during quote loading. $1 is the name of an aggregator. The message indicates that metamask is currently checking if that aggregator has a trade/quote for their requested swap."
|
|
||||||
},
|
|
||||||
"swapCustom": {
|
"swapCustom": {
|
||||||
"message": "personalizza"
|
"message": "personalizza"
|
||||||
},
|
},
|
||||||
@ -1564,9 +1560,6 @@
|
|||||||
"swapFetchingTokens": {
|
"swapFetchingTokens": {
|
||||||
"message": "Recuperando i token..."
|
"message": "Recuperando i token..."
|
||||||
},
|
},
|
||||||
"swapFinalizing": {
|
|
||||||
"message": "Finalizzando..."
|
|
||||||
},
|
|
||||||
"swapLowSlippageError": {
|
"swapLowSlippageError": {
|
||||||
"message": "La transazione può fallire, il massimo slippage è troppo basso."
|
"message": "La transazione può fallire, il massimo slippage è troppo basso."
|
||||||
},
|
},
|
||||||
|
@ -1870,10 +1870,6 @@
|
|||||||
"message": "$1 と一致するトークンがありません",
|
"message": "$1 と一致するトークンがありません",
|
||||||
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
||||||
},
|
},
|
||||||
"swapCheckingQuote": {
|
|
||||||
"message": "$1 をチェック中",
|
|
||||||
"description": "Shown to the user during quote loading. $1 is the name of an aggregator. The message indicates that metamask is currently checking if that aggregator has a trade/quote for their requested swap."
|
|
||||||
},
|
|
||||||
"swapConfirmWithHwWallet": {
|
"swapConfirmWithHwWallet": {
|
||||||
"message": "ハードウェア ウォレットで確認する"
|
"message": "ハードウェア ウォレットで確認する"
|
||||||
},
|
},
|
||||||
@ -1925,9 +1921,6 @@
|
|||||||
"swapFetchingTokens": {
|
"swapFetchingTokens": {
|
||||||
"message": "トークンを取り出し中..."
|
"message": "トークンを取り出し中..."
|
||||||
},
|
},
|
||||||
"swapFinalizing": {
|
|
||||||
"message": "終了中..."
|
|
||||||
},
|
|
||||||
"swapFromTo": {
|
"swapFromTo": {
|
||||||
"message": "$1 から $2 のスワップ",
|
"message": "$1 から $2 のスワップ",
|
||||||
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
||||||
|
@ -1870,10 +1870,6 @@
|
|||||||
"message": "$1와(과) 일치하는 토큰이 없습니다.",
|
"message": "$1와(과) 일치하는 토큰이 없습니다.",
|
||||||
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
||||||
},
|
},
|
||||||
"swapCheckingQuote": {
|
|
||||||
"message": "$1 확인 중",
|
|
||||||
"description": "Shown to the user during quote loading. $1 is the name of an aggregator. The message indicates that metamask is currently checking if that aggregator has a trade/quote for their requested swap."
|
|
||||||
},
|
|
||||||
"swapConfirmWithHwWallet": {
|
"swapConfirmWithHwWallet": {
|
||||||
"message": "하드웨어 지갑으로 확인합니다."
|
"message": "하드웨어 지갑으로 확인합니다."
|
||||||
},
|
},
|
||||||
@ -1925,9 +1921,6 @@
|
|||||||
"swapFetchingTokens": {
|
"swapFetchingTokens": {
|
||||||
"message": "토큰 가져오는 중..."
|
"message": "토큰 가져오는 중..."
|
||||||
},
|
},
|
||||||
"swapFinalizing": {
|
|
||||||
"message": "마무리 중..."
|
|
||||||
},
|
|
||||||
"swapFromTo": {
|
"swapFromTo": {
|
||||||
"message": "$1을(를) $2(으)로 스왑",
|
"message": "$1을(를) $2(으)로 스왑",
|
||||||
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
||||||
|
@ -1870,10 +1870,6 @@
|
|||||||
"message": "Walang available na token na tumutugma sa $1",
|
"message": "Walang available na token na tumutugma sa $1",
|
||||||
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
||||||
},
|
},
|
||||||
"swapCheckingQuote": {
|
|
||||||
"message": "Sinusuri ang $1",
|
|
||||||
"description": "Shown to the user during quote loading. $1 is the name of an aggregator. The message indicates that metamask is currently checking if that aggregator has a trade/quote for their requested swap."
|
|
||||||
},
|
|
||||||
"swapConfirmWithHwWallet": {
|
"swapConfirmWithHwWallet": {
|
||||||
"message": "Kumpirmahin ang iyong hardware wallet"
|
"message": "Kumpirmahin ang iyong hardware wallet"
|
||||||
},
|
},
|
||||||
@ -1925,9 +1921,6 @@
|
|||||||
"swapFetchingTokens": {
|
"swapFetchingTokens": {
|
||||||
"message": "Kinukuha ang mga token..."
|
"message": "Kinukuha ang mga token..."
|
||||||
},
|
},
|
||||||
"swapFinalizing": {
|
|
||||||
"message": "Isinasapinal..."
|
|
||||||
},
|
|
||||||
"swapFromTo": {
|
"swapFromTo": {
|
||||||
"message": "Ang pag-swap ng $1 sa $2",
|
"message": "Ang pag-swap ng $1 sa $2",
|
||||||
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
||||||
|
@ -1870,10 +1870,6 @@
|
|||||||
"message": "Nenhum token disponível correspondente a $1",
|
"message": "Nenhum token disponível correspondente a $1",
|
||||||
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
||||||
},
|
},
|
||||||
"swapCheckingQuote": {
|
|
||||||
"message": "Verificando $1",
|
|
||||||
"description": "Shown to the user during quote loading. $1 is the name of an aggregator. The message indicates that metamask is currently checking if that aggregator has a trade/quote for their requested swap."
|
|
||||||
},
|
|
||||||
"swapConfirmWithHwWallet": {
|
"swapConfirmWithHwWallet": {
|
||||||
"message": "Confirme com sua carteira de hardware"
|
"message": "Confirme com sua carteira de hardware"
|
||||||
},
|
},
|
||||||
@ -1925,9 +1921,6 @@
|
|||||||
"swapFetchingTokens": {
|
"swapFetchingTokens": {
|
||||||
"message": "Fetch dos tokens..."
|
"message": "Fetch dos tokens..."
|
||||||
},
|
},
|
||||||
"swapFinalizing": {
|
|
||||||
"message": "Finalizando..."
|
|
||||||
},
|
|
||||||
"swapFromTo": {
|
"swapFromTo": {
|
||||||
"message": "O swap de $1 para $2",
|
"message": "O swap de $1 para $2",
|
||||||
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
||||||
|
@ -1870,10 +1870,6 @@
|
|||||||
"message": "Нет доступных токенов соответствующих $1",
|
"message": "Нет доступных токенов соответствующих $1",
|
||||||
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
||||||
},
|
},
|
||||||
"swapCheckingQuote": {
|
|
||||||
"message": "Проверка $1",
|
|
||||||
"description": "Shown to the user during quote loading. $1 is the name of an aggregator. The message indicates that metamask is currently checking if that aggregator has a trade/quote for their requested swap."
|
|
||||||
},
|
|
||||||
"swapConfirmWithHwWallet": {
|
"swapConfirmWithHwWallet": {
|
||||||
"message": "Подтвердить с помощью аппаратного кошелька"
|
"message": "Подтвердить с помощью аппаратного кошелька"
|
||||||
},
|
},
|
||||||
@ -1925,9 +1921,6 @@
|
|||||||
"swapFetchingTokens": {
|
"swapFetchingTokens": {
|
||||||
"message": "Получение токенов..."
|
"message": "Получение токенов..."
|
||||||
},
|
},
|
||||||
"swapFinalizing": {
|
|
||||||
"message": "Завершение..."
|
|
||||||
},
|
|
||||||
"swapFromTo": {
|
"swapFromTo": {
|
||||||
"message": "Своп $1 на $2",
|
"message": "Своп $1 на $2",
|
||||||
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
||||||
|
@ -1510,10 +1510,6 @@
|
|||||||
"message": "Walang available na token na tumutugma sa $1",
|
"message": "Walang available na token na tumutugma sa $1",
|
||||||
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
||||||
},
|
},
|
||||||
"swapCheckingQuote": {
|
|
||||||
"message": "Sinusuri ang $1",
|
|
||||||
"description": "Shown to the user during quote loading. $1 is the name of an aggregator. The message indicates that metamask is currently checking if that aggregator has a trade/quote for their requested swap."
|
|
||||||
},
|
|
||||||
"swapCustom": {
|
"swapCustom": {
|
||||||
"message": "custom"
|
"message": "custom"
|
||||||
},
|
},
|
||||||
@ -1552,9 +1548,6 @@
|
|||||||
"swapFetchingTokens": {
|
"swapFetchingTokens": {
|
||||||
"message": "Kinukuha ang mga token..."
|
"message": "Kinukuha ang mga token..."
|
||||||
},
|
},
|
||||||
"swapFinalizing": {
|
|
||||||
"message": "Isinasapinal..."
|
|
||||||
},
|
|
||||||
"swapLowSlippageError": {
|
"swapLowSlippageError": {
|
||||||
"message": "Maaaring hindi magtagumpay ang transaksyon, masyadong mababa ang max na slippage."
|
"message": "Maaaring hindi magtagumpay ang transaksyon, masyadong mababa ang max na slippage."
|
||||||
},
|
},
|
||||||
|
@ -1870,10 +1870,6 @@
|
|||||||
"message": "Không có token nào khớp với $1",
|
"message": "Không có token nào khớp với $1",
|
||||||
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
||||||
},
|
},
|
||||||
"swapCheckingQuote": {
|
|
||||||
"message": "Đang kiểm tra $1",
|
|
||||||
"description": "Shown to the user during quote loading. $1 is the name of an aggregator. The message indicates that metamask is currently checking if that aggregator has a trade/quote for their requested swap."
|
|
||||||
},
|
|
||||||
"swapConfirmWithHwWallet": {
|
"swapConfirmWithHwWallet": {
|
||||||
"message": "Xác nhận ví cứng của bạn"
|
"message": "Xác nhận ví cứng của bạn"
|
||||||
},
|
},
|
||||||
@ -1925,9 +1921,6 @@
|
|||||||
"swapFetchingTokens": {
|
"swapFetchingTokens": {
|
||||||
"message": "Đang tìm nạp token..."
|
"message": "Đang tìm nạp token..."
|
||||||
},
|
},
|
||||||
"swapFinalizing": {
|
|
||||||
"message": "Đang hoàn tất..."
|
|
||||||
},
|
|
||||||
"swapFromTo": {
|
"swapFromTo": {
|
||||||
"message": "Giao dịch hoán đổi $1 sang $2",
|
"message": "Giao dịch hoán đổi $1 sang $2",
|
||||||
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
"description": "Tells a user that they need to confirm on their hardware wallet a swap of 2 tokens. $1 is a source token and $2 is a destination token"
|
||||||
|
@ -1516,10 +1516,6 @@
|
|||||||
"message": "没有匹配的代币符合 $1",
|
"message": "没有匹配的代币符合 $1",
|
||||||
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
"description": "Tells the user that a given search string does not match any tokens in our token lists. $1 can be any string of text"
|
||||||
},
|
},
|
||||||
"swapCheckingQuote": {
|
|
||||||
"message": "正在检查 $1",
|
|
||||||
"description": "Shown to the user during quote loading. $1 is the name of an aggregator. The message indicates that metamask is currently checking if that aggregator has a trade/quote for their requested swap."
|
|
||||||
},
|
|
||||||
"swapCustom": {
|
"swapCustom": {
|
||||||
"message": "自定义"
|
"message": "自定义"
|
||||||
},
|
},
|
||||||
@ -1558,9 +1554,6 @@
|
|||||||
"swapFetchingTokens": {
|
"swapFetchingTokens": {
|
||||||
"message": "获取代币中……"
|
"message": "获取代币中……"
|
||||||
},
|
},
|
||||||
"swapFinalizing": {
|
|
||||||
"message": "确定中……"
|
|
||||||
},
|
|
||||||
"swapLowSlippageError": {
|
"swapLowSlippageError": {
|
||||||
"message": "交易可能失败,最大滑点过低。"
|
"message": "交易可能失败,最大滑点过低。"
|
||||||
},
|
},
|
||||||
|
@ -24,8 +24,9 @@ import { isSwapsDefaultTokenAddress } from '../../../shared/modules/swaps.utils'
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
fetchTradesInfo as defaultFetchTradesInfo,
|
fetchTradesInfo as defaultFetchTradesInfo,
|
||||||
fetchSwapsQuoteRefreshTime as defaultFetchSwapsQuoteRefreshTime,
|
getBaseApi,
|
||||||
} from '../../../ui/pages/swaps/swaps.util';
|
} from '../../../ui/pages/swaps/swaps.util';
|
||||||
|
import fetchWithCache from '../../../ui/helpers/utils/fetch-with-cache';
|
||||||
import { MINUTE, SECOND } from '../../../shared/constants/time';
|
import { MINUTE, SECOND } from '../../../shared/constants/time';
|
||||||
import { NETWORK_EVENTS } from './network';
|
import { NETWORK_EVENTS } from './network';
|
||||||
|
|
||||||
@ -40,10 +41,6 @@ const POLL_COUNT_LIMIT = 3;
|
|||||||
// provide a reasonable fallback to avoid further errors
|
// provide a reasonable fallback to avoid further errors
|
||||||
const FALLBACK_QUOTE_REFRESH_TIME = MINUTE;
|
const FALLBACK_QUOTE_REFRESH_TIME = MINUTE;
|
||||||
|
|
||||||
// This is the amount of time to wait, after successfully fetching quotes
|
|
||||||
// and their gas estimates, before fetching for new quotes
|
|
||||||
const QUOTE_POLLING_DIFFERENCE_INTERVAL = SECOND * 10;
|
|
||||||
|
|
||||||
function calculateGasEstimateWithRefund(
|
function calculateGasEstimateWithRefund(
|
||||||
maxGas = MAX_GAS_LIMIT,
|
maxGas = MAX_GAS_LIMIT,
|
||||||
estimatedRefund = 0,
|
estimatedRefund = 0,
|
||||||
@ -64,6 +61,7 @@ function calculateGasEstimateWithRefund(
|
|||||||
const initialState = {
|
const initialState = {
|
||||||
swapsState: {
|
swapsState: {
|
||||||
quotes: {},
|
quotes: {},
|
||||||
|
quotesPollingLimitEnabled: false,
|
||||||
fetchParams: null,
|
fetchParams: null,
|
||||||
tokens: null,
|
tokens: null,
|
||||||
tradeTxId: null,
|
tradeTxId: null,
|
||||||
@ -82,6 +80,7 @@ const initialState = {
|
|||||||
swapsFeatureIsLive: true,
|
swapsFeatureIsLive: true,
|
||||||
useNewSwapsApi: false,
|
useNewSwapsApi: false,
|
||||||
swapsQuoteRefreshTime: FALLBACK_QUOTE_REFRESH_TIME,
|
swapsQuoteRefreshTime: FALLBACK_QUOTE_REFRESH_TIME,
|
||||||
|
swapsQuotePrefetchingRefreshTime: FALLBACK_QUOTE_REFRESH_TIME,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -93,7 +92,6 @@ export default class SwapsController {
|
|||||||
getProviderConfig,
|
getProviderConfig,
|
||||||
tokenRatesStore,
|
tokenRatesStore,
|
||||||
fetchTradesInfo = defaultFetchTradesInfo,
|
fetchTradesInfo = defaultFetchTradesInfo,
|
||||||
fetchSwapsQuoteRefreshTime = defaultFetchSwapsQuoteRefreshTime,
|
|
||||||
getCurrentChainId,
|
getCurrentChainId,
|
||||||
getEIP1559GasFeeEstimates,
|
getEIP1559GasFeeEstimates,
|
||||||
}) {
|
}) {
|
||||||
@ -102,7 +100,6 @@ export default class SwapsController {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this._fetchTradesInfo = fetchTradesInfo;
|
this._fetchTradesInfo = fetchTradesInfo;
|
||||||
this._fetchSwapsQuoteRefreshTime = fetchSwapsQuoteRefreshTime;
|
|
||||||
this._getCurrentChainId = getCurrentChainId;
|
this._getCurrentChainId = getCurrentChainId;
|
||||||
this._getEIP1559GasFeeEstimates = getEIP1559GasFeeEstimates;
|
this._getEIP1559GasFeeEstimates = getEIP1559GasFeeEstimates;
|
||||||
|
|
||||||
@ -124,38 +121,70 @@ export default class SwapsController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fetchSwapsRefreshRates(chainId, useNewSwapsApi) {
|
||||||
|
const response = await fetchWithCache(
|
||||||
|
getBaseApi('network', chainId, useNewSwapsApi),
|
||||||
|
{ method: 'GET' },
|
||||||
|
{ cacheRefreshTime: 600000 },
|
||||||
|
);
|
||||||
|
const { refreshRates } = response || {};
|
||||||
|
if (
|
||||||
|
!refreshRates ||
|
||||||
|
typeof refreshRates.quotes !== 'number' ||
|
||||||
|
typeof refreshRates.quotesPrefetching !== 'number'
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
`MetaMask - invalid response for refreshRates: ${response}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// We presently use milliseconds in the UI.
|
||||||
|
return {
|
||||||
|
quotes: refreshRates.quotes * 1000,
|
||||||
|
quotesPrefetching: refreshRates.quotesPrefetching * 1000,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Sets the refresh rate for quote updates from the MetaSwap API
|
// Sets the refresh rate for quote updates from the MetaSwap API
|
||||||
async _setSwapsQuoteRefreshTime() {
|
async _setSwapsRefreshRates() {
|
||||||
const chainId = this._getCurrentChainId();
|
const chainId = this._getCurrentChainId();
|
||||||
const { swapsState } = this.store.getState();
|
const { swapsState } = this.store.getState();
|
||||||
|
let swapsRefreshRates;
|
||||||
// Default to fallback time unless API returns valid response
|
|
||||||
let swapsQuoteRefreshTime = FALLBACK_QUOTE_REFRESH_TIME;
|
|
||||||
try {
|
try {
|
||||||
swapsQuoteRefreshTime = await this._fetchSwapsQuoteRefreshTime(
|
swapsRefreshRates = await this.fetchSwapsRefreshRates(
|
||||||
chainId,
|
chainId,
|
||||||
swapsState.useNewSwapsApi,
|
swapsState.useNewSwapsApi,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Request for swaps quote refresh time failed: ', e);
|
console.error('Request for swaps quote refresh time failed: ', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { swapsState: latestSwapsState } = this.store.getState();
|
const { swapsState: latestSwapsState } = this.store.getState();
|
||||||
|
|
||||||
this.store.updateState({
|
this.store.updateState({
|
||||||
swapsState: { ...latestSwapsState, swapsQuoteRefreshTime },
|
swapsState: {
|
||||||
|
...latestSwapsState,
|
||||||
|
swapsQuoteRefreshTime:
|
||||||
|
swapsRefreshRates?.quotes || FALLBACK_QUOTE_REFRESH_TIME,
|
||||||
|
swapsQuotePrefetchingRefreshTime:
|
||||||
|
swapsRefreshRates?.quotesPrefetching || FALLBACK_QUOTE_REFRESH_TIME,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Once quotes are fetched, we poll for new ones to keep the quotes up to date. Market and aggregator contract conditions can change fast enough
|
// Once quotes are fetched, we poll for new ones to keep the quotes up to date. Market and aggregator contract conditions can change fast enough
|
||||||
// that quotes will no longer be available after 1 or 2 minutes. When fetchAndSetQuotes is first called it, receives fetch that parameters are stored in
|
// that quotes will no longer be available after 1 or 2 minutes. When fetchAndSetQuotes is first called, it receives fetch parameters that are stored in
|
||||||
// state. These stored parameters are used on subsequent calls made during polling.
|
// state. These stored parameters are used on subsequent calls made during polling.
|
||||||
// Note: we stop polling after 3 requests, until new quotes are explicitly asked for. The logic that enforces that maximum is in the body of fetchAndSetQuotes
|
// Note: we stop polling after 3 requests, until new quotes are explicitly asked for. The logic that enforces that maximum is in the body of fetchAndSetQuotes
|
||||||
pollForNewQuotes() {
|
pollForNewQuotes() {
|
||||||
const {
|
const {
|
||||||
swapsState: { swapsQuoteRefreshTime },
|
swapsState: {
|
||||||
|
swapsQuoteRefreshTime,
|
||||||
|
swapsQuotePrefetchingRefreshTime,
|
||||||
|
quotesPollingLimitEnabled,
|
||||||
|
},
|
||||||
} = this.store.getState();
|
} = this.store.getState();
|
||||||
|
// swapsQuoteRefreshTime is used on the View Quote page, swapsQuotePrefetchingRefreshTime is used on the Build Quote page.
|
||||||
|
const quotesRefreshRateInMs = quotesPollingLimitEnabled
|
||||||
|
? swapsQuoteRefreshTime
|
||||||
|
: swapsQuotePrefetchingRefreshTime;
|
||||||
this.pollingTimeout = setTimeout(() => {
|
this.pollingTimeout = setTimeout(() => {
|
||||||
const { swapsState } = this.store.getState();
|
const { swapsState } = this.store.getState();
|
||||||
this.fetchAndSetQuotes(
|
this.fetchAndSetQuotes(
|
||||||
@ -163,12 +192,14 @@ export default class SwapsController {
|
|||||||
swapsState.fetchParams?.metaData,
|
swapsState.fetchParams?.metaData,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
}, swapsQuoteRefreshTime - QUOTE_POLLING_DIFFERENCE_INTERVAL);
|
}, quotesRefreshRateInMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
stopPollingForQuotes() {
|
stopPollingForQuotes() {
|
||||||
|
if (this.pollingTimeout) {
|
||||||
clearTimeout(this.pollingTimeout);
|
clearTimeout(this.pollingTimeout);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fetchAndSetQuotes(
|
async fetchAndSetQuotes(
|
||||||
fetchParams,
|
fetchParams,
|
||||||
@ -177,7 +208,7 @@ export default class SwapsController {
|
|||||||
) {
|
) {
|
||||||
const { chainId } = fetchParamsMetaData;
|
const { chainId } = fetchParamsMetaData;
|
||||||
const {
|
const {
|
||||||
swapsState: { useNewSwapsApi },
|
swapsState: { useNewSwapsApi, quotesPollingLimitEnabled },
|
||||||
} = this.store.getState();
|
} = this.store.getState();
|
||||||
|
|
||||||
if (!fetchParams) {
|
if (!fetchParams) {
|
||||||
@ -203,7 +234,7 @@ export default class SwapsController {
|
|||||||
...fetchParamsMetaData,
|
...fetchParamsMetaData,
|
||||||
useNewSwapsApi,
|
useNewSwapsApi,
|
||||||
}),
|
}),
|
||||||
this._setSwapsQuoteRefreshTime(),
|
this._setSwapsRefreshRates(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
newQuotes = mapValues(newQuotes, (quote) => ({
|
newQuotes = mapValues(newQuotes, (quote) => ({
|
||||||
@ -292,9 +323,13 @@ export default class SwapsController {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// We only want to do up to a maximum of three requests from polling.
|
if (quotesPollingLimitEnabled) {
|
||||||
|
// We only want to do up to a maximum of three requests from polling if polling limit is enabled.
|
||||||
|
// Otherwise we won't increase pollCount, so polling will run without a limit.
|
||||||
this.pollCount += 1;
|
this.pollCount += 1;
|
||||||
if (this.pollCount < POLL_COUNT_LIMIT + 1) {
|
}
|
||||||
|
|
||||||
|
if (!quotesPollingLimitEnabled || this.pollCount < POLL_COUNT_LIMIT + 1) {
|
||||||
this.pollForNewQuotes();
|
this.pollForNewQuotes();
|
||||||
} else {
|
} else {
|
||||||
this.resetPostFetchState();
|
this.resetPostFetchState();
|
||||||
@ -322,6 +357,11 @@ export default class SwapsController {
|
|||||||
this.store.updateState({ swapsState: { ...swapsState, tokens } });
|
this.store.updateState({ swapsState: { ...swapsState, tokens } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearSwapsQuotes() {
|
||||||
|
const { swapsState } = this.store.getState();
|
||||||
|
this.store.updateState({ swapsState: { ...swapsState, quotes: {} } });
|
||||||
|
}
|
||||||
|
|
||||||
setSwapsErrorKey(errorKey) {
|
setSwapsErrorKey(errorKey) {
|
||||||
const { swapsState } = this.store.getState();
|
const { swapsState } = this.store.getState();
|
||||||
this.store.updateState({ swapsState: { ...swapsState, errorKey } });
|
this.store.updateState({ swapsState: { ...swapsState, errorKey } });
|
||||||
@ -464,6 +504,13 @@ export default class SwapsController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setSwapsQuotesPollingLimitEnabled(quotesPollingLimitEnabled) {
|
||||||
|
const { swapsState } = this.store.getState();
|
||||||
|
this.store.updateState({
|
||||||
|
swapsState: { ...swapsState, quotesPollingLimitEnabled },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setSwapsTxMaxFeePriorityPerGas(maxPriorityFeePerGas) {
|
setSwapsTxMaxFeePriorityPerGas(maxPriorityFeePerGas) {
|
||||||
const { swapsState } = this.store.getState();
|
const { swapsState } = this.store.getState();
|
||||||
this.store.updateState({
|
this.store.updateState({
|
||||||
@ -511,6 +558,8 @@ export default class SwapsController {
|
|||||||
swapsFeatureIsLive: swapsState.swapsFeatureIsLive,
|
swapsFeatureIsLive: swapsState.swapsFeatureIsLive,
|
||||||
useNewSwapsApi: swapsState.useNewSwapsApi,
|
useNewSwapsApi: swapsState.useNewSwapsApi,
|
||||||
swapsQuoteRefreshTime: swapsState.swapsQuoteRefreshTime,
|
swapsQuoteRefreshTime: swapsState.swapsQuoteRefreshTime,
|
||||||
|
swapsQuotePrefetchingRefreshTime:
|
||||||
|
swapsState.swapsQuotePrefetchingRefreshTime,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
clearTimeout(this.pollingTimeout);
|
clearTimeout(this.pollingTimeout);
|
||||||
@ -523,6 +572,8 @@ export default class SwapsController {
|
|||||||
...initialState.swapsState,
|
...initialState.swapsState,
|
||||||
tokens: swapsState.tokens,
|
tokens: swapsState.tokens,
|
||||||
swapsQuoteRefreshTime: swapsState.swapsQuoteRefreshTime,
|
swapsQuoteRefreshTime: swapsState.swapsQuoteRefreshTime,
|
||||||
|
swapsQuotePrefetchingRefreshTime:
|
||||||
|
swapsState.swapsQuotePrefetchingRefreshTime,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
clearTimeout(this.pollingTimeout);
|
clearTimeout(this.pollingTimeout);
|
||||||
|
@ -116,6 +116,7 @@ function getMockNetworkController() {
|
|||||||
const EMPTY_INIT_STATE = {
|
const EMPTY_INIT_STATE = {
|
||||||
swapsState: {
|
swapsState: {
|
||||||
quotes: {},
|
quotes: {},
|
||||||
|
quotesPollingLimitEnabled: false,
|
||||||
fetchParams: null,
|
fetchParams: null,
|
||||||
tokens: null,
|
tokens: null,
|
||||||
tradeTxId: null,
|
tradeTxId: null,
|
||||||
@ -133,13 +134,13 @@ const EMPTY_INIT_STATE = {
|
|||||||
swapsFeatureIsLive: true,
|
swapsFeatureIsLive: true,
|
||||||
useNewSwapsApi: false,
|
useNewSwapsApi: false,
|
||||||
swapsQuoteRefreshTime: 60000,
|
swapsQuoteRefreshTime: 60000,
|
||||||
|
swapsQuotePrefetchingRefreshTime: 60000,
|
||||||
swapsUserFeeLevel: '',
|
swapsUserFeeLevel: '',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const sandbox = sinon.createSandbox();
|
const sandbox = sinon.createSandbox();
|
||||||
const fetchTradesInfoStub = sandbox.stub();
|
const fetchTradesInfoStub = sandbox.stub();
|
||||||
const fetchSwapsQuoteRefreshTimeStub = sandbox.stub();
|
|
||||||
const getCurrentChainIdStub = sandbox.stub();
|
const getCurrentChainIdStub = sandbox.stub();
|
||||||
getCurrentChainIdStub.returns(MAINNET_CHAIN_ID);
|
getCurrentChainIdStub.returns(MAINNET_CHAIN_ID);
|
||||||
const getEIP1559GasFeeEstimatesStub = sandbox.stub(() => {
|
const getEIP1559GasFeeEstimatesStub = sandbox.stub(() => {
|
||||||
@ -162,7 +163,6 @@ describe('SwapsController', function () {
|
|||||||
getProviderConfig: MOCK_GET_PROVIDER_CONFIG,
|
getProviderConfig: MOCK_GET_PROVIDER_CONFIG,
|
||||||
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
|
tokenRatesStore: MOCK_TOKEN_RATES_STORE,
|
||||||
fetchTradesInfo: fetchTradesInfoStub,
|
fetchTradesInfo: fetchTradesInfoStub,
|
||||||
fetchSwapsQuoteRefreshTime: fetchSwapsQuoteRefreshTimeStub,
|
|
||||||
getCurrentChainId: getCurrentChainIdStub,
|
getCurrentChainId: getCurrentChainIdStub,
|
||||||
getEIP1559GasFeeEstimates: getEIP1559GasFeeEstimatesStub,
|
getEIP1559GasFeeEstimates: getEIP1559GasFeeEstimatesStub,
|
||||||
});
|
});
|
||||||
@ -670,7 +670,6 @@ describe('SwapsController', function () {
|
|||||||
|
|
||||||
it('calls fetchTradesInfo with the given fetchParams and returns the correct quotes', async function () {
|
it('calls fetchTradesInfo with the given fetchParams and returns the correct quotes', async function () {
|
||||||
fetchTradesInfoStub.resolves(getMockQuotes());
|
fetchTradesInfoStub.resolves(getMockQuotes());
|
||||||
fetchSwapsQuoteRefreshTimeStub.resolves(getMockQuoteRefreshTime());
|
|
||||||
|
|
||||||
// Make it so approval is not required
|
// Make it so approval is not required
|
||||||
sandbox
|
sandbox
|
||||||
@ -716,7 +715,6 @@ describe('SwapsController', function () {
|
|||||||
|
|
||||||
it('performs the allowance check', async function () {
|
it('performs the allowance check', async function () {
|
||||||
fetchTradesInfoStub.resolves(getMockQuotes());
|
fetchTradesInfoStub.resolves(getMockQuotes());
|
||||||
fetchSwapsQuoteRefreshTimeStub.resolves(getMockQuoteRefreshTime());
|
|
||||||
|
|
||||||
// Make it so approval is not required
|
// Make it so approval is not required
|
||||||
const allowanceStub = sandbox
|
const allowanceStub = sandbox
|
||||||
@ -740,7 +738,6 @@ describe('SwapsController', function () {
|
|||||||
|
|
||||||
it('gets the gas limit if approval is required', async function () {
|
it('gets the gas limit if approval is required', async function () {
|
||||||
fetchTradesInfoStub.resolves(MOCK_QUOTES_APPROVAL_REQUIRED);
|
fetchTradesInfoStub.resolves(MOCK_QUOTES_APPROVAL_REQUIRED);
|
||||||
fetchSwapsQuoteRefreshTimeStub.resolves(getMockQuoteRefreshTime());
|
|
||||||
|
|
||||||
// Ensure approval is required
|
// Ensure approval is required
|
||||||
sandbox
|
sandbox
|
||||||
@ -766,7 +763,6 @@ describe('SwapsController', function () {
|
|||||||
|
|
||||||
it('marks the best quote', async function () {
|
it('marks the best quote', async function () {
|
||||||
fetchTradesInfoStub.resolves(getMockQuotes());
|
fetchTradesInfoStub.resolves(getMockQuotes());
|
||||||
fetchSwapsQuoteRefreshTimeStub.resolves(getMockQuoteRefreshTime());
|
|
||||||
|
|
||||||
// Make it so approval is not required
|
// Make it so approval is not required
|
||||||
sandbox
|
sandbox
|
||||||
@ -797,7 +793,6 @@ describe('SwapsController', function () {
|
|||||||
};
|
};
|
||||||
const quotes = { ...getMockQuotes(), [bestAggId]: bestQuote };
|
const quotes = { ...getMockQuotes(), [bestAggId]: bestQuote };
|
||||||
fetchTradesInfoStub.resolves(quotes);
|
fetchTradesInfoStub.resolves(quotes);
|
||||||
fetchSwapsQuoteRefreshTimeStub.resolves(getMockQuoteRefreshTime());
|
|
||||||
|
|
||||||
// Make it so approval is not required
|
// Make it so approval is not required
|
||||||
sandbox
|
sandbox
|
||||||
@ -815,7 +810,6 @@ describe('SwapsController', function () {
|
|||||||
|
|
||||||
it('does not mark as best quote if no conversion rate exists for destination token', async function () {
|
it('does not mark as best quote if no conversion rate exists for destination token', async function () {
|
||||||
fetchTradesInfoStub.resolves(getMockQuotes());
|
fetchTradesInfoStub.resolves(getMockQuotes());
|
||||||
fetchSwapsQuoteRefreshTimeStub.resolves(getMockQuoteRefreshTime());
|
|
||||||
|
|
||||||
// Make it so approval is not required
|
// Make it so approval is not required
|
||||||
sandbox
|
sandbox
|
||||||
@ -843,6 +837,8 @@ describe('SwapsController', function () {
|
|||||||
...EMPTY_INIT_STATE.swapsState,
|
...EMPTY_INIT_STATE.swapsState,
|
||||||
tokens: old.tokens,
|
tokens: old.tokens,
|
||||||
swapsQuoteRefreshTime: old.swapsQuoteRefreshTime,
|
swapsQuoteRefreshTime: old.swapsQuoteRefreshTime,
|
||||||
|
swapsQuotePrefetchingRefreshTime:
|
||||||
|
old.swapsQuotePrefetchingRefreshTime,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -890,6 +886,7 @@ describe('SwapsController', function () {
|
|||||||
const swapsFeatureIsLive = false;
|
const swapsFeatureIsLive = false;
|
||||||
const useNewSwapsApi = false;
|
const useNewSwapsApi = false;
|
||||||
const swapsQuoteRefreshTime = 0;
|
const swapsQuoteRefreshTime = 0;
|
||||||
|
const swapsQuotePrefetchingRefreshTime = 0;
|
||||||
swapsController.store.updateState({
|
swapsController.store.updateState({
|
||||||
swapsState: {
|
swapsState: {
|
||||||
tokens,
|
tokens,
|
||||||
@ -897,6 +894,7 @@ describe('SwapsController', function () {
|
|||||||
swapsFeatureIsLive,
|
swapsFeatureIsLive,
|
||||||
useNewSwapsApi,
|
useNewSwapsApi,
|
||||||
swapsQuoteRefreshTime,
|
swapsQuoteRefreshTime,
|
||||||
|
swapsQuotePrefetchingRefreshTime,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -909,6 +907,7 @@ describe('SwapsController', function () {
|
|||||||
fetchParams,
|
fetchParams,
|
||||||
swapsFeatureIsLive,
|
swapsFeatureIsLive,
|
||||||
swapsQuoteRefreshTime,
|
swapsQuoteRefreshTime,
|
||||||
|
swapsQuotePrefetchingRefreshTime,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -1387,7 +1386,3 @@ function getTopQuoteAndSavingsBaseExpectedResults() {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMockQuoteRefreshTime() {
|
|
||||||
return 45000;
|
|
||||||
}
|
|
||||||
|
@ -1073,6 +1073,10 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
swapsController,
|
swapsController,
|
||||||
),
|
),
|
||||||
setSwapsTokens: nodeify(swapsController.setSwapsTokens, swapsController),
|
setSwapsTokens: nodeify(swapsController.setSwapsTokens, swapsController),
|
||||||
|
clearSwapsQuotes: nodeify(
|
||||||
|
swapsController.clearSwapsQuotes,
|
||||||
|
swapsController,
|
||||||
|
),
|
||||||
setApproveTxId: nodeify(swapsController.setApproveTxId, swapsController),
|
setApproveTxId: nodeify(swapsController.setApproveTxId, swapsController),
|
||||||
setTradeTxId: nodeify(swapsController.setTradeTxId, swapsController),
|
setTradeTxId: nodeify(swapsController.setTradeTxId, swapsController),
|
||||||
setSwapsTxGasPrice: nodeify(
|
setSwapsTxGasPrice: nodeify(
|
||||||
@ -1127,6 +1131,10 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
swapsController.setSwapsUserFeeLevel,
|
swapsController.setSwapsUserFeeLevel,
|
||||||
swapsController,
|
swapsController,
|
||||||
),
|
),
|
||||||
|
setSwapsQuotesPollingLimitEnabled: nodeify(
|
||||||
|
swapsController.setSwapsQuotesPollingLimitEnabled,
|
||||||
|
swapsController,
|
||||||
|
),
|
||||||
|
|
||||||
// MetaMetrics
|
// MetaMetrics
|
||||||
trackMetaMetricsEvent: nodeify(
|
trackMetaMetricsEvent: nodeify(
|
||||||
|
@ -212,6 +212,7 @@ export const createSwapsMockStore = () => {
|
|||||||
approveTxId: null,
|
approveTxId: null,
|
||||||
quotesLastFetched: 1519211809934,
|
quotesLastFetched: 1519211809934,
|
||||||
swapsQuoteRefreshTime: 60000,
|
swapsQuoteRefreshTime: 60000,
|
||||||
|
swapsQuotePrefetchingRefreshTime: 60000,
|
||||||
customMaxGas: '',
|
customMaxGas: '',
|
||||||
customGasPrice: null,
|
customGasPrice: null,
|
||||||
selectedAggId: 'TEST_AGG_2',
|
selectedAggId: 'TEST_AGG_2',
|
||||||
|
@ -86,6 +86,7 @@ const initialState = {
|
|||||||
fetchingQuotes: false,
|
fetchingQuotes: false,
|
||||||
fromToken: null,
|
fromToken: null,
|
||||||
quotesFetchStartTime: null,
|
quotesFetchStartTime: null,
|
||||||
|
reviewSwapClickedTimestamp: null,
|
||||||
topAssets: {},
|
topAssets: {},
|
||||||
toToken: null,
|
toToken: null,
|
||||||
customGas: {
|
customGas: {
|
||||||
@ -130,6 +131,9 @@ const slice = createSlice({
|
|||||||
setQuotesFetchStartTime: (state, action) => {
|
setQuotesFetchStartTime: (state, action) => {
|
||||||
state.quotesFetchStartTime = action.payload;
|
state.quotesFetchStartTime = action.payload;
|
||||||
},
|
},
|
||||||
|
setReviewSwapClickedTimestamp: (state, action) => {
|
||||||
|
state.reviewSwapClickedTimestamp = action.payload;
|
||||||
|
},
|
||||||
setTopAssets: (state, action) => {
|
setTopAssets: (state, action) => {
|
||||||
state.topAssets = action.payload;
|
state.topAssets = action.payload;
|
||||||
},
|
},
|
||||||
@ -183,6 +187,9 @@ export const getFetchingQuotes = (state) => state.swaps.fetchingQuotes;
|
|||||||
export const getQuotesFetchStartTime = (state) =>
|
export const getQuotesFetchStartTime = (state) =>
|
||||||
state.swaps.quotesFetchStartTime;
|
state.swaps.quotesFetchStartTime;
|
||||||
|
|
||||||
|
export const getReviewSwapClickedTimestamp = (state) =>
|
||||||
|
state.swaps.reviewSwapClickedTimestamp;
|
||||||
|
|
||||||
export const getSwapsCustomizationModalPrice = (state) =>
|
export const getSwapsCustomizationModalPrice = (state) =>
|
||||||
state.swaps.customGas.price;
|
state.swaps.customGas.price;
|
||||||
|
|
||||||
@ -236,6 +243,9 @@ export const getUseNewSwapsApi = (state) =>
|
|||||||
export const getSwapsQuoteRefreshTime = (state) =>
|
export const getSwapsQuoteRefreshTime = (state) =>
|
||||||
state.metamask.swapsState.swapsQuoteRefreshTime;
|
state.metamask.swapsState.swapsQuoteRefreshTime;
|
||||||
|
|
||||||
|
export const getSwapsQuotePrefetchingRefreshTime = (state) =>
|
||||||
|
state.metamask.swapsState.swapsQuotePrefetchingRefreshTime;
|
||||||
|
|
||||||
export const getBackgroundSwapRouteState = (state) =>
|
export const getBackgroundSwapRouteState = (state) =>
|
||||||
state.metamask.swapsState.routeState;
|
state.metamask.swapsState.routeState;
|
||||||
|
|
||||||
@ -323,6 +333,7 @@ const {
|
|||||||
setFetchingQuotes,
|
setFetchingQuotes,
|
||||||
setFromToken,
|
setFromToken,
|
||||||
setQuotesFetchStartTime,
|
setQuotesFetchStartTime,
|
||||||
|
setReviewSwapClickedTimestamp,
|
||||||
setTopAssets,
|
setTopAssets,
|
||||||
setToToken,
|
setToToken,
|
||||||
swapCustomGasModalPriceEdited,
|
swapCustomGasModalPriceEdited,
|
||||||
@ -338,6 +349,7 @@ export {
|
|||||||
setFetchingQuotes,
|
setFetchingQuotes,
|
||||||
setFromToken as setSwapsFromToken,
|
setFromToken as setSwapsFromToken,
|
||||||
setQuotesFetchStartTime as setSwapQuotesFetchStartTime,
|
setQuotesFetchStartTime as setSwapQuotesFetchStartTime,
|
||||||
|
setReviewSwapClickedTimestamp,
|
||||||
setTopAssets,
|
setTopAssets,
|
||||||
setToToken as setSwapToToken,
|
setToToken as setSwapToToken,
|
||||||
swapCustomGasModalPriceEdited,
|
swapCustomGasModalPriceEdited,
|
||||||
@ -412,6 +424,7 @@ export const fetchQuotesAndSetQuoteState = (
|
|||||||
inputValue,
|
inputValue,
|
||||||
maxSlippage,
|
maxSlippage,
|
||||||
metaMetricsEvent,
|
metaMetricsEvent,
|
||||||
|
pageRedirectionDisabled,
|
||||||
) => {
|
) => {
|
||||||
return async (dispatch, getState) => {
|
return async (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
@ -461,8 +474,12 @@ export const fetchQuotesAndSetQuoteState = (
|
|||||||
decimals: toTokenDecimals,
|
decimals: toTokenDecimals,
|
||||||
iconUrl: toTokenIconUrl,
|
iconUrl: toTokenIconUrl,
|
||||||
} = selectedToToken;
|
} = selectedToToken;
|
||||||
|
// pageRedirectionDisabled is true if quotes prefetching is active (a user is on the Build Quote page).
|
||||||
|
// In that case we just want to silently prefetch quotes without redirecting to the quotes loading page.
|
||||||
|
if (!pageRedirectionDisabled) {
|
||||||
await dispatch(setBackgroundSwapRouteState('loading'));
|
await dispatch(setBackgroundSwapRouteState('loading'));
|
||||||
history.push(LOADING_QUOTES_ROUTE);
|
history.push(LOADING_QUOTES_ROUTE);
|
||||||
|
}
|
||||||
dispatch(setFetchingQuotes(true));
|
dispatch(setFetchingQuotes(true));
|
||||||
|
|
||||||
const contractExchangeRates = getTokenExchangeRates(state);
|
const contractExchangeRates = getTokenExchangeRates(state);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
import React, { useContext, useRef, useState } from 'react';
|
import React, { useContext, useRef, useState, useEffect } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
@ -42,6 +42,7 @@ import { isSwapsDefaultTokenSymbol } from '../../../../shared/modules/swaps.util
|
|||||||
import PulseLoader from '../../../components/ui/pulse-loader';
|
import PulseLoader from '../../../components/ui/pulse-loader';
|
||||||
|
|
||||||
import { ASSET_ROUTE, DEFAULT_ROUTE } from '../../../helpers/constants/routes';
|
import { ASSET_ROUTE, DEFAULT_ROUTE } from '../../../helpers/constants/routes';
|
||||||
|
import { stopPollingForQuotes } from '../../../store/actions';
|
||||||
|
|
||||||
import { getRenderableNetworkFeesForQuote } from '../swaps.util';
|
import { getRenderableNetworkFeesForQuote } from '../swaps.util';
|
||||||
import SwapsFooter from '../swaps-footer';
|
import SwapsFooter from '../swaps-footer';
|
||||||
@ -249,6 +250,13 @@ export default function AwaitingSwap({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (errorKey) {
|
||||||
|
// If there was an error, stop polling for quotes.
|
||||||
|
dispatch(stopPollingForQuotes());
|
||||||
|
}
|
||||||
|
}, [dispatch, errorKey]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="awaiting-swap">
|
<div className="awaiting-swap">
|
||||||
<div className="awaiting-swap__content">
|
<div className="awaiting-swap__content">
|
||||||
|
@ -19,6 +19,10 @@ import SlippageButtons from '../slippage-buttons';
|
|||||||
import { getTokens, getConversionRate } from '../../../ducks/metamask/metamask';
|
import { getTokens, getConversionRate } from '../../../ducks/metamask/metamask';
|
||||||
import InfoTooltip from '../../../components/ui/info-tooltip';
|
import InfoTooltip from '../../../components/ui/info-tooltip';
|
||||||
import ActionableMessage from '../../../components/ui/actionable-message/actionable-message';
|
import ActionableMessage from '../../../components/ui/actionable-message/actionable-message';
|
||||||
|
import {
|
||||||
|
VIEW_QUOTE_ROUTE,
|
||||||
|
LOADING_QUOTES_ROUTE,
|
||||||
|
} from '../../../helpers/constants/routes';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
fetchQuotesAndSetQuoteState,
|
fetchQuotesAndSetQuoteState,
|
||||||
@ -29,6 +33,8 @@ import {
|
|||||||
getBalanceError,
|
getBalanceError,
|
||||||
getTopAssets,
|
getTopAssets,
|
||||||
getFetchParams,
|
getFetchParams,
|
||||||
|
getQuotes,
|
||||||
|
setReviewSwapClickedTimestamp,
|
||||||
} from '../../../ducks/swaps/swaps';
|
} from '../../../ducks/swaps/swaps';
|
||||||
import {
|
import {
|
||||||
getSwapsDefaultToken,
|
getSwapsDefaultToken,
|
||||||
@ -60,7 +66,13 @@ import {
|
|||||||
SWAPS_CHAINID_DEFAULT_TOKEN_MAP,
|
SWAPS_CHAINID_DEFAULT_TOKEN_MAP,
|
||||||
} from '../../../../shared/constants/swaps';
|
} from '../../../../shared/constants/swaps';
|
||||||
|
|
||||||
import { resetSwapsPostFetchState, removeToken } from '../../../store/actions';
|
import {
|
||||||
|
resetSwapsPostFetchState,
|
||||||
|
removeToken,
|
||||||
|
setBackgroundSwapRouteState,
|
||||||
|
clearSwapsQuotes,
|
||||||
|
stopPollingForQuotes,
|
||||||
|
} from '../../../store/actions';
|
||||||
import {
|
import {
|
||||||
fetchTokenPrice,
|
fetchTokenPrice,
|
||||||
fetchTokenBalance,
|
fetchTokenBalance,
|
||||||
@ -76,6 +88,8 @@ const fuseSearchKeys = [
|
|||||||
|
|
||||||
const MAX_ALLOWED_SLIPPAGE = 15;
|
const MAX_ALLOWED_SLIPPAGE = 15;
|
||||||
|
|
||||||
|
let timeoutIdForQuotesPrefetching;
|
||||||
|
|
||||||
export default function BuildQuote({
|
export default function BuildQuote({
|
||||||
inputValue,
|
inputValue,
|
||||||
onInputChange,
|
onInputChange,
|
||||||
@ -110,6 +124,8 @@ export default function BuildQuote({
|
|||||||
const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider);
|
const rpcPrefs = useSelector(getRpcPrefsForCurrentProvider);
|
||||||
const tokenList = useSelector(getTokenList);
|
const tokenList = useSelector(getTokenList);
|
||||||
const useTokenDetection = useSelector(getUseTokenDetection);
|
const useTokenDetection = useSelector(getUseTokenDetection);
|
||||||
|
const quotes = useSelector(getQuotes, isEqual);
|
||||||
|
const areQuotesPresent = Object.keys(quotes).length > 0;
|
||||||
|
|
||||||
const tokenConversionRates = useSelector(getTokenExchangeRates, isEqual);
|
const tokenConversionRates = useSelector(getTokenExchangeRates, isEqual);
|
||||||
const conversionRate = useSelector(getConversionRate);
|
const conversionRate = useSelector(getConversionRate);
|
||||||
@ -168,6 +184,7 @@ export default function BuildQuote({
|
|||||||
decimals: fromTokenDecimals,
|
decimals: fromTokenDecimals,
|
||||||
balance: rawFromTokenBalance,
|
balance: rawFromTokenBalance,
|
||||||
} = selectedFromToken || {};
|
} = selectedFromToken || {};
|
||||||
|
const { address: toTokenAddress } = selectedToToken || {};
|
||||||
|
|
||||||
const fromTokenBalance =
|
const fromTokenBalance =
|
||||||
rawFromTokenBalance &&
|
rawFromTokenBalance &&
|
||||||
@ -348,6 +365,7 @@ export default function BuildQuote({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(resetSwapsPostFetchState());
|
dispatch(resetSwapsPostFetchState());
|
||||||
|
dispatch(setReviewSwapClickedTimestamp());
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
const BlockExplorerLink = () => {
|
const BlockExplorerLink = () => {
|
||||||
@ -392,6 +410,51 @@ export default function BuildQuote({
|
|||||||
fromTokenAddress,
|
fromTokenAddress,
|
||||||
selectedToToken.address,
|
selectedToToken.address,
|
||||||
);
|
);
|
||||||
|
const isReviewSwapButtonDisabled =
|
||||||
|
tokenFromError ||
|
||||||
|
!isFeatureFlagLoaded ||
|
||||||
|
!Number(inputValue) ||
|
||||||
|
!selectedToToken?.address ||
|
||||||
|
Number(maxSlippage) < 0 ||
|
||||||
|
Number(maxSlippage) > MAX_ALLOWED_SLIPPAGE ||
|
||||||
|
(toTokenIsNotDefault && occurrences < 2 && !verificationClicked);
|
||||||
|
|
||||||
|
// It's triggered every time there is a change in form values (token from, token to, amount and slippage).
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(clearSwapsQuotes());
|
||||||
|
dispatch(stopPollingForQuotes());
|
||||||
|
const prefetchQuotesWithoutRedirecting = async () => {
|
||||||
|
const pageRedirectionDisabled = true;
|
||||||
|
await dispatch(
|
||||||
|
fetchQuotesAndSetQuoteState(
|
||||||
|
history,
|
||||||
|
inputValue,
|
||||||
|
maxSlippage,
|
||||||
|
metaMetricsEvent,
|
||||||
|
pageRedirectionDisabled,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
// Delay fetching quotes until a user is done typing an input value. If they type a new char in less than a second,
|
||||||
|
// we will cancel previous setTimeout call and start running a new one.
|
||||||
|
timeoutIdForQuotesPrefetching = setTimeout(() => {
|
||||||
|
timeoutIdForQuotesPrefetching = null;
|
||||||
|
if (!isReviewSwapButtonDisabled) {
|
||||||
|
// Only do quotes prefetching if the Review Swap button is enabled.
|
||||||
|
prefetchQuotesWithoutRedirecting();
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
return () => clearTimeout(timeoutIdForQuotesPrefetching);
|
||||||
|
}, [
|
||||||
|
dispatch,
|
||||||
|
history,
|
||||||
|
maxSlippage,
|
||||||
|
metaMetricsEvent,
|
||||||
|
isReviewSwapButtonDisabled,
|
||||||
|
inputValue,
|
||||||
|
fromTokenAddress,
|
||||||
|
toTokenAddress,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="build-quote">
|
<div className="build-quote">
|
||||||
@ -401,6 +464,7 @@ export default function BuildQuote({
|
|||||||
{!isSwapsDefaultTokenSymbol(fromTokenSymbol, chainId) && (
|
{!isSwapsDefaultTokenSymbol(fromTokenSymbol, chainId) && (
|
||||||
<div
|
<div
|
||||||
className="build-quote__max-button"
|
className="build-quote__max-button"
|
||||||
|
data-testid="build-quote__max-button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
onInputChange(fromTokenBalance || '0', fromTokenBalance)
|
onInputChange(fromTokenBalance || '0', fromTokenBalance)
|
||||||
}
|
}
|
||||||
@ -583,7 +647,13 @@ export default function BuildQuote({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<SwapsFooter
|
<SwapsFooter
|
||||||
onSubmit={() => {
|
onSubmit={async () => {
|
||||||
|
// We need this to know how long it took to go from clicking on the Review Swap button to rendered View Quote page.
|
||||||
|
dispatch(setReviewSwapClickedTimestamp(Date.now()));
|
||||||
|
// In case that quotes prefetching is waiting to be executed, but hasn't started yet,
|
||||||
|
// we want to cancel it and fetch quotes from here.
|
||||||
|
if (timeoutIdForQuotesPrefetching) {
|
||||||
|
clearTimeout(timeoutIdForQuotesPrefetching);
|
||||||
dispatch(
|
dispatch(
|
||||||
fetchQuotesAndSetQuoteState(
|
fetchQuotesAndSetQuoteState(
|
||||||
history,
|
history,
|
||||||
@ -592,17 +662,17 @@ export default function BuildQuote({
|
|||||||
metaMetricsEvent,
|
metaMetricsEvent,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
} else if (areQuotesPresent) {
|
||||||
|
// If there are prefetched quotes already, go directly to the View Quote page.
|
||||||
|
history.push(VIEW_QUOTE_ROUTE);
|
||||||
|
} else {
|
||||||
|
// If the "Review Swap" button was clicked while quotes are being fetched, go to the Loading Quotes page.
|
||||||
|
await dispatch(setBackgroundSwapRouteState('loading'));
|
||||||
|
history.push(LOADING_QUOTES_ROUTE);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
submitText={t('swapReviewSwap')}
|
submitText={t('swapReviewSwap')}
|
||||||
disabled={
|
disabled={isReviewSwapButtonDisabled}
|
||||||
tokenFromError ||
|
|
||||||
!isFeatureFlagLoaded ||
|
|
||||||
!Number(inputValue) ||
|
|
||||||
!selectedToToken?.address ||
|
|
||||||
Number(maxSlippage) < 0 ||
|
|
||||||
Number(maxSlippage) > MAX_ALLOWED_SLIPPAGE ||
|
|
||||||
(toTokenIsNotDefault && occurrences < 2 && !verificationClicked)
|
|
||||||
}
|
|
||||||
hideCancel
|
hideCancel
|
||||||
showTermsOfService
|
showTermsOfService
|
||||||
/>
|
/>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import configureMockStore from 'redux-mock-store';
|
import configureMockStore from 'redux-mock-store';
|
||||||
import thunk from 'redux-thunk';
|
import thunk from 'redux-thunk';
|
||||||
|
import { fireEvent } from '@testing-library/react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
renderWithProvider,
|
renderWithProvider,
|
||||||
@ -14,7 +15,7 @@ const createProps = (customProps = {}) => {
|
|||||||
return {
|
return {
|
||||||
inputValue: '5',
|
inputValue: '5',
|
||||||
onInputChange: jest.fn(),
|
onInputChange: jest.fn(),
|
||||||
ethBalance: '6 ETH',
|
ethBalance: '0x8',
|
||||||
setMaxSlippage: jest.fn(),
|
setMaxSlippage: jest.fn(),
|
||||||
maxSlippage: 15,
|
maxSlippage: 15,
|
||||||
selectedAccountAddress: 'selectedAccountAddress',
|
selectedAccountAddress: 'selectedAccountAddress',
|
||||||
@ -26,6 +27,10 @@ const createProps = (customProps = {}) => {
|
|||||||
|
|
||||||
setBackgroundConnection({
|
setBackgroundConnection({
|
||||||
resetPostFetchState: jest.fn(),
|
resetPostFetchState: jest.fn(),
|
||||||
|
removeToken: jest.fn(),
|
||||||
|
setBackgroundSwapRouteState: jest.fn(),
|
||||||
|
clearSwapsQuotes: jest.fn(),
|
||||||
|
stopPollingForQuotes: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('BuildQuote', () => {
|
describe('BuildQuote', () => {
|
||||||
@ -44,4 +49,28 @@ describe('BuildQuote', () => {
|
|||||||
document.querySelector('.slippage-buttons__button-group'),
|
document.querySelector('.slippage-buttons__button-group'),
|
||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('clicks on the max button', () => {
|
||||||
|
const store = configureMockStore(middleware)(createSwapsMockStore());
|
||||||
|
const props = createProps();
|
||||||
|
const { getByTestId } = renderWithProvider(
|
||||||
|
<BuildQuote {...props} />,
|
||||||
|
store,
|
||||||
|
);
|
||||||
|
fireEvent.click(getByTestId('build-quote__max-button'));
|
||||||
|
expect(props.onInputChange).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('types a number inside the input field', () => {
|
||||||
|
const store = configureMockStore(middleware)(createSwapsMockStore());
|
||||||
|
const props = createProps();
|
||||||
|
const { getByDisplayValue } = renderWithProvider(
|
||||||
|
<BuildQuote {...props} />,
|
||||||
|
store,
|
||||||
|
);
|
||||||
|
fireEvent.change(getByDisplayValue('5'), {
|
||||||
|
target: { value: '8' },
|
||||||
|
});
|
||||||
|
expect(props.onInputChange).toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -37,6 +37,7 @@ import {
|
|||||||
fetchSwapsLiveness,
|
fetchSwapsLiveness,
|
||||||
getUseNewSwapsApi,
|
getUseNewSwapsApi,
|
||||||
getFromToken,
|
getFromToken,
|
||||||
|
getReviewSwapClickedTimestamp,
|
||||||
} from '../../ducks/swaps/swaps';
|
} from '../../ducks/swaps/swaps';
|
||||||
import {
|
import {
|
||||||
checkNetworkAndAccountSupports1559,
|
checkNetworkAndAccountSupports1559,
|
||||||
@ -123,6 +124,8 @@ export default function Swap() {
|
|||||||
const fromToken = useSelector(getFromToken);
|
const fromToken = useSelector(getFromToken);
|
||||||
const tokenList = useSelector(getTokenList);
|
const tokenList = useSelector(getTokenList);
|
||||||
const listTokenValues = shuffle(Object.values(tokenList));
|
const listTokenValues = shuffle(Object.values(tokenList));
|
||||||
|
const reviewSwapClickedTimestamp = useSelector(getReviewSwapClickedTimestamp);
|
||||||
|
const reviewSwapClicked = Boolean(reviewSwapClickedTimestamp);
|
||||||
|
|
||||||
if (networkAndAccountSupports1559) {
|
if (networkAndAccountSupports1559) {
|
||||||
// This will pre-load gas fees before going to the View Quote page.
|
// This will pre-load gas fees before going to the View Quote page.
|
||||||
@ -255,10 +258,12 @@ export default function Swap() {
|
|||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (swapsErrorKey && !isSwapsErrorRoute) {
|
// If there is a swapsErrorKey and reviewSwapClicked is false, there was an error in silent quotes prefetching
|
||||||
|
// and we don't want to show the error page in that case, because another API call for quotes can be successful.
|
||||||
|
if (swapsErrorKey && !isSwapsErrorRoute && reviewSwapClicked) {
|
||||||
history.push(SWAPS_ERROR_ROUTE);
|
history.push(SWAPS_ERROR_ROUTE);
|
||||||
}
|
}
|
||||||
}, [history, swapsErrorKey, isSwapsErrorRoute]);
|
}, [history, swapsErrorKey, isSwapsErrorRoute, reviewSwapClicked]);
|
||||||
|
|
||||||
const beforeUnloadEventAddedRef = useRef();
|
const beforeUnloadEventAddedRef = useRef();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -393,7 +398,6 @@ export default function Swap() {
|
|||||||
}
|
}
|
||||||
onDone={async () => {
|
onDone={async () => {
|
||||||
await dispatch(setBackgroundSwapRouteState(''));
|
await dispatch(setBackgroundSwapRouteState(''));
|
||||||
|
|
||||||
if (
|
if (
|
||||||
swapsErrorKey === ERROR_FETCHING_QUOTES ||
|
swapsErrorKey === ERROR_FETCHING_QUOTES ||
|
||||||
swapsErrorKey === QUOTES_NOT_AVAILABLE_ERROR
|
swapsErrorKey === QUOTES_NOT_AVAILABLE_ERROR
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`AggregatorLogo renders the component with initial props 1`] = `
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
class="loading-swaps-quotes__logo"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style="background: rgb(0, 0, 0); box-shadow: 0px 4px 20px rgba(0, 0, NaN, 0.25);"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt=""
|
|
||||||
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASkAAAB5CAYAAABlYNfBAAAACXBIWXMAACxLAAAsSwGlPZapAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAlASURBVHgB7d3/eeO2GcDx1336f3UThNcF4g3KLNA4HaDnywC5Xgeo6Q5Q+zpAz+kCd+0AJyUDxNcFTsoCsbtA3gIVFL0CIRGgKMm2vp/nwWPxF0hT4isABCERAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADhCJ4LeVLVyf05d+sylZy7dufSjS7OTk5OPgrXcufN/KpfO16zyfqhzmLGvEjOX7mWA9zgcVx3SwrXL916AvtwHa+TShUtT3cwvfxUCGSLuvJy4dLPh/H2Qgfhg4NIXOrw7l9659EJ6CMfVmPx+5vOCrYSgc6dlpi79SbBC50FquuG8+Qt2JAPQ3QUp65NLdY/jIkh1+LUgi/vwXLk/qWDz/6J/+Ov56p+9uCqXrtz2n7li/GvBoppzJvNzs8m5S9eyG/eyfM9KjGT1/V147tIH979duvf5UoB98gEq8c35dt03p5t/qumqzFvBohT1zpyXqUvnLr3U1ZLqIFU+bZekfInlXHoIefkq/9ma99jnnVVypiSFQei8/UmjC6rO3LZOfIiPuuoXLszn0Tn5h1kWX7RbV/mGDFKJfKtEsPo55zNCkMLWwgcwDlBVwfYXmnYqRypcmC+jC/PULIvbjhrZ0q6ClMnflwyvo+PuLAUSpLA194EZRx+8unD7qaaN5UiFC9qe108dy3+QLe06SIV9xMfdWZoiSOX5lSApfMBqM+vGNYhOJFO4CCoz61vzutbCgPcU6LK/Um1m/zNezaXvzPTpIzlX/rj/aqZ9H8RasDWC1Hrn0XTpHZsL83oi7TuDF3KcavPaX9g3iXXemNf+Yv9SHoe4c+fngq0RpNazF8bElaJmkilRiroMvYgnZp4vIQzSD+gR8QHnGzP9MT6vbtr/ic9Vr86S+2SOe2ZmE6QGQJBKCNULG0BupMxKKcpUE22Vz+d/NA3ooap3Ksv/2c94s251l/5tpkePqHo8EwyKIJV2Fk1/J5lSpSjz+n3HfuK8LnTeH8unRrag897yi7wO0V/LFzVeRfM2ndebaNs/CoC56C7NtHDbqdn2tmP5uCOvM13Vq4+VtrtSNLJn2n4M5kPG+mOz/k99q8e6h7t7sjzmW7OfHzKOi7t7HShJpdlq2EwyabsUlXqkY+XOlWzgqom+5DUxsy56Xqg2GPqn9xvZI10+7V8tZslq1Te5maze+fP/95k8fPY9ZTSDARCk0mwg+I/ks21RPhikLkT7wR1lBJ3X0XEV3RVMBM5G9s9X1+LG75wqtA/S9yaPB1vlC4H4PJr9vQBDS1SNcp/DOo+2e5G5XpWRd9ybuZYM4X+Zmu0O8uygtqt67wq2exdVh4pLkrq/zpzTaB+nGcdFda8DJam2KprOLbLnlKJy9pfSRMeRW5pqZH0j/l5oe8QDP+N97uYu/T2ady4PTPgf/XtSmdkTBj4cBkGq26xrhVCyqcyspiC/30iH0MfKVvtq7RhoLXyL23UuS/p6DSiupt0XBHDPX+i2yvd7eSBCSciPyOpLqPaLw0etrwXYBW2PXFBnbDM2608L8z+X/GOz+7nbVPWJqh5FdyiHohtGPCjII354t7hKpLsbquVa04MgNgV5Ud3rwKB3bVXJytp+xq+R3bk0+1o0or9OHJPvj1SZWYccbK82r+M7djn8Nv+SZR8rX5o6l+3Os8/DjxHW59GkdYPeLVzu++4pjoy2+ybVHeuPS0osukVJKmwfN6KfRssfRGN5OJaNIx4U5nPXNx9l+OBHjTaptrihfG2bUfhA1WZWI+XuCtdvZPUYrxLLq/B6JgdoLPc0PeLBRPqz7ViVPozHZBbPGH7lSk+/LRklA/mo7nV7tmFZY17n3tGLqwr/lQK+Ed1doD7wLILTohHd9zuqZLWx/M2BGssXbOdLH7W+36KkYPurLUZGmEh/76WsD9zCLPz9yN07HIRm9pNKrJf1pL62+0nV0oO2B+SLHaSx3Bxf16/BbOungmPZeT+pPpTqXhaqe21xda9as15jXs8k/yHkKpru+23cVY17KQeiyxEPKtmdxzQyArZAkIqEPkk2ULXapMK3nS05fVtQrfo8sb8+ftex/JUcTmrEg13s4xvBk0ebVNpMlg+K1onlTbTujeSrzOtepagQJBtZPQaf7BhV/i5lfcDGXBtE/WgQf5Dt+cDk+1nVYdq3x434WXIcHW3f5h+ZZXFb1FVBvqNo217dA7TdqbMy+dtb9VPd8+ifa9p/sp5/zMz7dWnetEk9blT30uISji0VNNGydaNLptTR9EQKhYurNrN+edwllChsW1Ul6V9d3qXUiAe5z+rluIn29WAekwH2Zl2JJ1GKKioJaftHJKvC7eOOmtM1642j/ZzKnmi74+Ugv0Ic5T+OSh+jjm0oSeHp0cRzctsEGW0HuLEUyt2/zn/mXXcVKDYcn0/xD39mdc0o3EdRlY8ghSdJ24+vvI2nC/O7iLYvuni13b+q6Vj/uuRCHoKmx3+qZEDhwn4WnYsPGdsQpPD06ObOiFVBPnEpqnTc9FF0LP71KGMbW+3qPUZ45jGmRjzYSQlOC38tmCD1uNFwvtm6DpE3uf2iQmCIq3aNlIlHNWgybrv75X820/7xnr/JbtXmdc445n3Foynwa8E4XtpuB1ItG773Ntr2WgroFo31pSWObWiPBu0t9rWo8q2UFDvWpySFp0nn1aY40ExdeqHrG679NhfaHhDttvTC1XY1ryrYNlUF+6QDBw8dYHC7Hvv0QfEmusBrWX98BCk8XWsC1cJYzY9uhunUaI19AtRFlEfxnbLEheA1MiBN33H7UnZI02NEXWWuS5DC06Ttnui5/HalAcpX82ywu5UedFk1mu7qYtD2iAfZIxQMsN/OmwNKkHrUaDgv4Bqr/W3855LfIDxx6Qu/XY/ny3ypyW8zC+kr6cHt1//xA+t9bfL60aW/yAB0ObidmPx31WDe2r3Mf4B1FtK9rB95wZ5Ln0oHG9wVfxwzkxA5EfQSvrFrmT/Q618vRkvwAcA/VjPhwVcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJH7H5NlZI/0GQ+cAAAAAElFTkSuQmCC"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
@ -1,40 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
// Inspired by https://stackoverflow.com/a/28056903/4727685
|
|
||||||
function hexToRGB(hex, alpha) {
|
|
||||||
const r = parseInt(hex.slice(1, 3), 16);
|
|
||||||
const g = parseInt(hex.slice(3, 5), 16);
|
|
||||||
const b = parseInt(hex.slice(5, 7), 16);
|
|
||||||
|
|
||||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AggregatorLogo({ name, icon, color }) {
|
|
||||||
return (
|
|
||||||
<div className="loading-swaps-quotes__logo">
|
|
||||||
{icon && color ? (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
background: color,
|
|
||||||
boxShadow: `0px 4px 20px ${hexToRGB(color, 0.25)}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<img src={icon} alt="" />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
name && (
|
|
||||||
<div>
|
|
||||||
<span>{name}</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
AggregatorLogo.propTypes = {
|
|
||||||
name: PropTypes.string,
|
|
||||||
icon: PropTypes.string,
|
|
||||||
color: PropTypes.string,
|
|
||||||
};
|
|
@ -1,22 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { renderWithProvider } from '../../../../test/jest';
|
|
||||||
import AggregatorLogo from './aggregator-logo';
|
|
||||||
|
|
||||||
const createProps = (customProps = {}) => {
|
|
||||||
return {
|
|
||||||
color: '#000',
|
|
||||||
icon:
|
|
||||||
'data:image/png;base64,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',
|
|
||||||
...customProps,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('AggregatorLogo', () => {
|
|
||||||
it('renders the component with initial props', () => {
|
|
||||||
const { container } = renderWithProvider(
|
|
||||||
<AggregatorLogo {...createProps()} />,
|
|
||||||
);
|
|
||||||
expect(container).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
@ -4,7 +4,6 @@ import { useDispatch, useSelector } from 'react-redux';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { shuffle } from 'lodash';
|
import { shuffle } from 'lodash';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import classnames from 'classnames';
|
|
||||||
import {
|
import {
|
||||||
navigateBackToBuildQuote,
|
navigateBackToBuildQuote,
|
||||||
getFetchParams,
|
getFetchParams,
|
||||||
@ -19,44 +18,6 @@ import { MetaMetricsContext } from '../../../contexts/metametrics.new';
|
|||||||
import Mascot from '../../../components/ui/mascot';
|
import Mascot from '../../../components/ui/mascot';
|
||||||
import SwapsFooter from '../swaps-footer';
|
import SwapsFooter from '../swaps-footer';
|
||||||
import BackgroundAnimation from './background-animation';
|
import BackgroundAnimation from './background-animation';
|
||||||
import AggregatorLogo from './aggregator-logo';
|
|
||||||
|
|
||||||
// These locations reference where we want the top-left corner of the logo div to appear in relation to the
|
|
||||||
// centre point of the fox
|
|
||||||
const AGGREGATOR_LOCATIONS = [
|
|
||||||
{ x: -125, y: -75 },
|
|
||||||
{ x: 30, y: -75 },
|
|
||||||
{ x: -145, y: 0 },
|
|
||||||
{ x: 50, y: 0 },
|
|
||||||
{ x: -135, y: 46 },
|
|
||||||
{ x: 40, y: 46 },
|
|
||||||
];
|
|
||||||
|
|
||||||
function getRandomLocations(numberOfLocations) {
|
|
||||||
const randomLocations = shuffle(AGGREGATOR_LOCATIONS);
|
|
||||||
if (numberOfLocations <= AGGREGATOR_LOCATIONS.length) {
|
|
||||||
return randomLocations.slice(0, numberOfLocations);
|
|
||||||
}
|
|
||||||
const numberOfExtraLocations =
|
|
||||||
numberOfLocations - AGGREGATOR_LOCATIONS.length;
|
|
||||||
return [...randomLocations, ...getRandomLocations(numberOfExtraLocations)];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMascotTarget(aggregatorName, centerPoint, aggregatorLocationMap) {
|
|
||||||
const location = aggregatorLocationMap[aggregatorName];
|
|
||||||
|
|
||||||
if (!location || !centerPoint) {
|
|
||||||
return centerPoint ?? {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// The aggregator logos are 94px x 40px. For the fox to look at the center of each logo, the target needs to be
|
|
||||||
// the coordinates for the centre point of the fox + the desired top and left coordinates of the logo + half
|
|
||||||
// the height and width of the logo.
|
|
||||||
return {
|
|
||||||
x: location.x + centerPoint.x + 47,
|
|
||||||
y: location.y + centerPoint.y + 20,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function LoadingSwapsQuotes({
|
export default function LoadingSwapsQuotes({
|
||||||
aggregatorMetadata,
|
aggregatorMetadata,
|
||||||
@ -97,20 +58,6 @@ export default function LoadingSwapsQuotes({
|
|||||||
const currentMascotContainer = mascotContainer.current;
|
const currentMascotContainer = mascotContainer.current;
|
||||||
|
|
||||||
const [quoteCount, updateQuoteCount] = useState(0);
|
const [quoteCount, updateQuoteCount] = useState(0);
|
||||||
// is an array of randomized items from AGGREGATOR_LOCATIONS, containing
|
|
||||||
// numberOfQuotes number of items it is randomized so that the order in
|
|
||||||
// which the fox looks at locations is random
|
|
||||||
const [aggregatorLocations] = useState(() =>
|
|
||||||
getRandomLocations(numberOfQuotes),
|
|
||||||
);
|
|
||||||
const _aggregatorLocationMap = aggregatorNames.reduce(
|
|
||||||
(nameLocationMap, name, index) => ({
|
|
||||||
...nameLocationMap,
|
|
||||||
[name]: aggregatorLocations[index],
|
|
||||||
}),
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
const [aggregatorLocationMap] = useState(_aggregatorLocationMap);
|
|
||||||
const [midPointTarget, setMidpointTarget] = useState(null);
|
const [midPointTarget, setMidpointTarget] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -122,7 +69,7 @@ export default function LoadingSwapsQuotes({
|
|||||||
|
|
||||||
if (loadingComplete) {
|
if (loadingComplete) {
|
||||||
// If loading is complete, but the quoteCount is not, we quickly display the remaining logos/names/fox looks. 0.2s each
|
// If loading is complete, but the quoteCount is not, we quickly display the remaining logos/names/fox looks. 0.2s each
|
||||||
timeoutLength = 200;
|
timeoutLength = 20;
|
||||||
} else {
|
} else {
|
||||||
// If loading is not complete, we display remaining logos/names/fox looks at random intervals between 0.5s and 2s, to simulate the
|
// If loading is not complete, we display remaining logos/names/fox looks at random intervals between 0.5s and 2s, to simulate the
|
||||||
// sort of loading a user would experience in most async scenarios
|
// sort of loading a user would experience in most async scenarios
|
||||||
@ -167,13 +114,7 @@ export default function LoadingSwapsQuotes({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="loading-swaps-quotes__quote-name-check">
|
<div className="loading-swaps-quotes__quote-name-check">
|
||||||
<span>
|
<span>{t('swapFetchingQuotes')}</span>
|
||||||
{quoteCount === numberOfQuotes
|
|
||||||
? t('swapFinalizing')
|
|
||||||
: t('swapCheckingQuote', [
|
|
||||||
aggregatorMetadata[aggregatorNames[quoteCount]].title,
|
|
||||||
])}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="loading-swaps-quotes__loading-bar-container">
|
<div className="loading-swaps-quotes__loading-bar-container">
|
||||||
<div
|
<div
|
||||||
@ -195,37 +136,9 @@ export default function LoadingSwapsQuotes({
|
|||||||
width="90"
|
width="90"
|
||||||
height="90"
|
height="90"
|
||||||
followMouse={false}
|
followMouse={false}
|
||||||
lookAtTarget={getMascotTarget(
|
lookAtTarget={midPointTarget}
|
||||||
aggregatorNames[quoteCount],
|
|
||||||
midPointTarget,
|
|
||||||
aggregatorLocationMap,
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{currentMascotContainer &&
|
|
||||||
midPointTarget &&
|
|
||||||
aggregatorNames.map((aggName) => (
|
|
||||||
<div
|
|
||||||
className={classnames('loading-swaps-quotes__logo', {
|
|
||||||
'loading-swaps-quotes__logo--transition':
|
|
||||||
aggName === aggregatorNames[quoteCount],
|
|
||||||
})}
|
|
||||||
style={{
|
|
||||||
opacity: aggName === aggregatorNames[quoteCount] ? 1 : 0,
|
|
||||||
top:
|
|
||||||
aggregatorLocationMap[aggName]?.y + midPointTarget?.y ?? 0,
|
|
||||||
left:
|
|
||||||
aggregatorLocationMap[aggName]?.x + midPointTarget?.x ?? 0,
|
|
||||||
}}
|
|
||||||
key={`aggregator-logo-${aggName}`}
|
|
||||||
>
|
|
||||||
<AggregatorLogo
|
|
||||||
name={aggregatorMetadata[aggName]?.title}
|
|
||||||
icon={aggregatorMetadata[aggName]?.icon}
|
|
||||||
color={aggregatorMetadata[aggName]?.color}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SwapsFooter
|
<SwapsFooter
|
||||||
|
@ -35,7 +35,6 @@ describe('LoadingSwapsQuotes', () => {
|
|||||||
store,
|
store,
|
||||||
);
|
);
|
||||||
expect(getByText('Quote 1 of 2')).toBeInTheDocument();
|
expect(getByText('Quote 1 of 2')).toBeInTheDocument();
|
||||||
expect(getByText('Checking agg', { exact: false })).toBeInTheDocument();
|
|
||||||
expect(getByText('Back')).toBeInTheDocument();
|
expect(getByText('Back')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -76,7 +76,7 @@ const getBaseUrlForNewSwapsApi = (type, chainId) => {
|
|||||||
return `${v2ApiBaseUrl}/networks/${chainIdDecimal}`;
|
return `${v2ApiBaseUrl}/networks/${chainIdDecimal}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getBaseApi = function (
|
export const getBaseApi = function (
|
||||||
type,
|
type,
|
||||||
chainId = MAINNET_CHAIN_ID,
|
chainId = MAINNET_CHAIN_ID,
|
||||||
useNewSwapsApi = false,
|
useNewSwapsApi = false,
|
||||||
@ -84,6 +84,7 @@ const getBaseApi = function (
|
|||||||
const baseUrl = useNewSwapsApi
|
const baseUrl = useNewSwapsApi
|
||||||
? getBaseUrlForNewSwapsApi(type, chainId)
|
? getBaseUrlForNewSwapsApi(type, chainId)
|
||||||
: METASWAP_CHAINID_API_HOST_MAP[chainId];
|
: METASWAP_CHAINID_API_HOST_MAP[chainId];
|
||||||
|
const chainIdDecimal = chainId && parseInt(chainId, 16);
|
||||||
if (!baseUrl) {
|
if (!baseUrl) {
|
||||||
throw new Error(`Swaps API calls are disabled for chainId: ${chainId}`);
|
throw new Error(`Swaps API calls are disabled for chainId: ${chainId}`);
|
||||||
}
|
}
|
||||||
@ -100,8 +101,9 @@ const getBaseApi = function (
|
|||||||
return `${baseUrl}/aggregatorMetadata`;
|
return `${baseUrl}/aggregatorMetadata`;
|
||||||
case 'gasPrices':
|
case 'gasPrices':
|
||||||
return `${baseUrl}/gasPrices`;
|
return `${baseUrl}/gasPrices`;
|
||||||
case 'refreshTime':
|
case 'network':
|
||||||
return `${baseUrl}/quoteRefreshRate`;
|
// Only use v2 for this endpoint.
|
||||||
|
return `${SWAPS_API_V2_BASE_URL}/networks/${chainIdDecimal}`;
|
||||||
default:
|
default:
|
||||||
throw new Error('getBaseApi requires an api call type');
|
throw new Error('getBaseApi requires an api call type');
|
||||||
}
|
}
|
||||||
@ -441,23 +443,6 @@ export async function fetchSwapsFeatureFlags() {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchSwapsQuoteRefreshTime(chainId, useNewSwapsApi) {
|
|
||||||
const response = await fetchWithCache(
|
|
||||||
getBaseApi('refreshTime', chainId, useNewSwapsApi),
|
|
||||||
{ method: 'GET' },
|
|
||||||
{ cacheRefreshTime: 600000 },
|
|
||||||
);
|
|
||||||
|
|
||||||
// We presently use milliseconds in the UI
|
|
||||||
if (typeof response?.seconds === 'number' && response.seconds > 0) {
|
|
||||||
return response.seconds * 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(
|
|
||||||
`MetaMask - refreshTime provided invalid response: ${response}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchTokenPrice(address) {
|
export async function fetchTokenPrice(address) {
|
||||||
const query = `contract_addresses=${address}&vs_currencies=eth`;
|
const query = `contract_addresses=${address}&vs_currencies=eth`;
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ import {
|
|||||||
getBackgroundSwapRouteState,
|
getBackgroundSwapRouteState,
|
||||||
swapsQuoteSelected,
|
swapsQuoteSelected,
|
||||||
getSwapsQuoteRefreshTime,
|
getSwapsQuoteRefreshTime,
|
||||||
|
getReviewSwapClickedTimestamp,
|
||||||
} from '../../../ducks/swaps/swaps';
|
} from '../../../ducks/swaps/swaps';
|
||||||
import {
|
import {
|
||||||
conversionRateSelector,
|
conversionRateSelector,
|
||||||
@ -56,6 +57,7 @@ import {
|
|||||||
setCustomApproveTxData,
|
setCustomApproveTxData,
|
||||||
setSwapsErrorKey,
|
setSwapsErrorKey,
|
||||||
showModal,
|
showModal,
|
||||||
|
setSwapsQuotesPollingLimitEnabled,
|
||||||
} from '../../../store/actions';
|
} from '../../../store/actions';
|
||||||
import {
|
import {
|
||||||
ASSET_ROUTE,
|
ASSET_ROUTE,
|
||||||
@ -105,6 +107,8 @@ export default function ViewQuote() {
|
|||||||
const [warningHidden, setWarningHidden] = useState(false);
|
const [warningHidden, setWarningHidden] = useState(false);
|
||||||
const [originalApproveAmount, setOriginalApproveAmount] = useState(null);
|
const [originalApproveAmount, setOriginalApproveAmount] = useState(null);
|
||||||
const [showEditGasPopover, setShowEditGasPopover] = useState(false);
|
const [showEditGasPopover, setShowEditGasPopover] = useState(false);
|
||||||
|
// We need to have currentTimestamp in state, otherwise it would change with each rerender.
|
||||||
|
const [currentTimestamp] = useState(Date.now());
|
||||||
|
|
||||||
const [
|
const [
|
||||||
acknowledgedPriceDifference,
|
acknowledgedPriceDifference,
|
||||||
@ -150,6 +154,7 @@ export default function ViewQuote() {
|
|||||||
const defaultSwapsToken = useSelector(getSwapsDefaultToken);
|
const defaultSwapsToken = useSelector(getSwapsDefaultToken);
|
||||||
const chainId = useSelector(getCurrentChainId);
|
const chainId = useSelector(getCurrentChainId);
|
||||||
const nativeCurrencySymbol = useSelector(getNativeCurrency);
|
const nativeCurrencySymbol = useSelector(getNativeCurrency);
|
||||||
|
const reviewSwapClickedTimestamp = useSelector(getReviewSwapClickedTimestamp);
|
||||||
|
|
||||||
let gasFeeInputs;
|
let gasFeeInputs;
|
||||||
if (networkAndAccountSupports1559) {
|
if (networkAndAccountSupports1559) {
|
||||||
@ -371,6 +376,9 @@ export default function ViewQuote() {
|
|||||||
const showInsufficientWarning =
|
const showInsufficientWarning =
|
||||||
(balanceError || tokenBalanceNeeded || ethBalanceNeeded) && !warningHidden;
|
(balanceError || tokenBalanceNeeded || ethBalanceNeeded) && !warningHidden;
|
||||||
|
|
||||||
|
const hardwareWalletUsed = useSelector(isHardwareWallet);
|
||||||
|
const hardwareWalletType = useSelector(getHardwareWalletType);
|
||||||
|
|
||||||
const numberOfQuotes = Object.values(quotes).length;
|
const numberOfQuotes = Object.values(quotes).length;
|
||||||
const bestQuoteReviewedEventSent = useRef();
|
const bestQuoteReviewedEventSent = useRef();
|
||||||
const eventObjectBase = {
|
const eventObjectBase = {
|
||||||
@ -384,10 +392,10 @@ export default function ViewQuote() {
|
|||||||
response_time: fetchParams?.responseTime,
|
response_time: fetchParams?.responseTime,
|
||||||
best_quote_source: topQuote?.aggregator,
|
best_quote_source: topQuote?.aggregator,
|
||||||
available_quotes: numberOfQuotes,
|
available_quotes: numberOfQuotes,
|
||||||
|
is_hardware_wallet: hardwareWalletUsed,
|
||||||
|
hardware_wallet_type: hardwareWalletType,
|
||||||
};
|
};
|
||||||
|
|
||||||
const hardwareWalletUsed = useSelector(isHardwareWallet);
|
|
||||||
const hardwareWalletType = useSelector(getHardwareWalletType);
|
|
||||||
const allAvailableQuotesOpened = useNewMetricEvent({
|
const allAvailableQuotesOpened = useNewMetricEvent({
|
||||||
event: 'All Available Quotes Opened',
|
event: 'All Available Quotes Opened',
|
||||||
category: 'swaps',
|
category: 'swaps',
|
||||||
@ -398,8 +406,6 @@ export default function ViewQuote() {
|
|||||||
usedQuote?.aggregator === topQuote?.aggregator
|
usedQuote?.aggregator === topQuote?.aggregator
|
||||||
? null
|
? null
|
||||||
: usedQuote?.aggregator,
|
: usedQuote?.aggregator,
|
||||||
is_hardware_wallet: hardwareWalletUsed,
|
|
||||||
hardware_wallet_type: hardwareWalletType,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const quoteDetailsOpened = useNewMetricEvent({
|
const quoteDetailsOpened = useNewMetricEvent({
|
||||||
@ -412,8 +418,6 @@ export default function ViewQuote() {
|
|||||||
usedQuote?.aggregator === topQuote?.aggregator
|
usedQuote?.aggregator === topQuote?.aggregator
|
||||||
? null
|
? null
|
||||||
: usedQuote?.aggregator,
|
: usedQuote?.aggregator,
|
||||||
is_hardware_wallet: hardwareWalletUsed,
|
|
||||||
hardware_wallet_type: hardwareWalletType,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const editSpendLimitOpened = useNewMetricEvent({
|
const editSpendLimitOpened = useNewMetricEvent({
|
||||||
@ -424,8 +428,6 @@ export default function ViewQuote() {
|
|||||||
custom_spend_limit_set: originalApproveAmount === approveAmount,
|
custom_spend_limit_set: originalApproveAmount === approveAmount,
|
||||||
custom_spend_limit_amount:
|
custom_spend_limit_amount:
|
||||||
originalApproveAmount === approveAmount ? null : approveAmount,
|
originalApproveAmount === approveAmount ? null : approveAmount,
|
||||||
is_hardware_wallet: hardwareWalletUsed,
|
|
||||||
hardware_wallet_type: hardwareWalletType,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -435,10 +437,18 @@ export default function ViewQuote() {
|
|||||||
sensitiveProperties: {
|
sensitiveProperties: {
|
||||||
...eventObjectBase,
|
...eventObjectBase,
|
||||||
network_fees: feeInFiat,
|
network_fees: feeInFiat,
|
||||||
is_hardware_wallet: hardwareWalletUsed,
|
|
||||||
hardware_wallet_type: hardwareWalletType,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const viewQuotePageLoadedEvent = useNewMetricEvent({
|
||||||
|
event: 'View Quote Page Loaded',
|
||||||
|
category: 'swaps',
|
||||||
|
sensitiveProperties: {
|
||||||
|
...eventObjectBase,
|
||||||
|
response_time: currentTimestamp - reviewSwapClickedTimestamp,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
!bestQuoteReviewedEventSent.current &&
|
!bestQuoteReviewedEventSent.current &&
|
||||||
@ -653,6 +663,14 @@ export default function ViewQuote() {
|
|||||||
setShowEditGasPopover(false);
|
setShowEditGasPopover(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Thanks to the next line we will only do quotes polling 3 times before showing a Quote Timeout modal.
|
||||||
|
dispatch(setSwapsQuotesPollingLimitEnabled(true));
|
||||||
|
if (reviewSwapClickedTimestamp) {
|
||||||
|
viewQuotePageLoadedEvent();
|
||||||
|
}
|
||||||
|
}, [dispatch, viewQuotePageLoadedEvent, reviewSwapClickedTimestamp]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="view-quote">
|
<div className="view-quote">
|
||||||
<div
|
<div
|
||||||
|
@ -47,6 +47,7 @@ setBackgroundConnection({
|
|||||||
getGasFeeEstimatesAndStartPolling: jest.fn(),
|
getGasFeeEstimatesAndStartPolling: jest.fn(),
|
||||||
updateTransaction: jest.fn(),
|
updateTransaction: jest.fn(),
|
||||||
getGasFeeTimeEstimate: jest.fn(),
|
getGasFeeTimeEstimate: jest.fn(),
|
||||||
|
setSwapsQuotesPollingLimitEnabled: jest.fn(),
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('ViewQuote', () => {
|
describe('ViewQuote', () => {
|
||||||
|
@ -2161,6 +2161,13 @@ export function setSwapsTokens(tokens) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function clearSwapsQuotes() {
|
||||||
|
return async (dispatch) => {
|
||||||
|
await promisifiedBackground.clearSwapsQuotes();
|
||||||
|
await forceUpdateMetamaskState(dispatch);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function resetBackgroundSwapsState() {
|
export function resetBackgroundSwapsState() {
|
||||||
return async (dispatch) => {
|
return async (dispatch) => {
|
||||||
const id = await promisifiedBackground.resetSwapsState();
|
const id = await promisifiedBackground.resetSwapsState();
|
||||||
@ -2214,6 +2221,15 @@ export function updateSwapsUserFeeLevel(swapsCustomUserFeeLevel) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setSwapsQuotesPollingLimitEnabled(quotesPollingLimitEnabled) {
|
||||||
|
return async (dispatch) => {
|
||||||
|
await promisifiedBackground.setSwapsQuotesPollingLimitEnabled(
|
||||||
|
quotesPollingLimitEnabled,
|
||||||
|
);
|
||||||
|
await forceUpdateMetamaskState(dispatch);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function customSwapsGasParamsUpdated(gasLimit, gasPrice) {
|
export function customSwapsGasParamsUpdated(gasLimit, gasPrice) {
|
||||||
return async (dispatch) => {
|
return async (dispatch) => {
|
||||||
await promisifiedBackground.setSwapsTxGasPrice(gasPrice);
|
await promisifiedBackground.setSwapsTxGasPrice(gasPrice);
|
||||||
|
Loading…
Reference in New Issue
Block a user