1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-22 11:22:43 +02:00

Swaps UI update (#19169)

This commit is contained in:
Daniel 2023-06-15 20:17:21 +02:00 committed by GitHub
parent 1f576641dc
commit 8b3e3c8a58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
122 changed files with 6610 additions and 1156 deletions

View File

@ -195,10 +195,6 @@
"addCustomToken": {
"message": "Kunden-Token hinzufügen"
},
"addCustomTokenByContractAddress": {
"message": "Sie können kein Token finden? Sie können ein beliebiges Token manuell hinzufügen, indem Sie seine Adresse eingeben. Token-Vertragsadressen finden Sie auf $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "Dadurch kann dieses Netzwerk innerhalb MetaMask verwendet werden."
},
@ -263,6 +259,10 @@
"addToken": {
"message": "Token hinzufügen"
},
"addTokenByContractAddress": {
"message": "Sie können kein Token finden? Sie können ein beliebiges Token manuell hinzufügen, indem Sie seine Adresse eingeben. Token-Vertragsadressen finden Sie auf $1",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"address": {
"message": "Adresse"
},
@ -548,13 +548,6 @@
"message": "Für eine Transaktion im Wert von $1 muss die Gasgebühr um mindestens 10 % erhöht werden, damit sie vom Netz erkannt wird.",
"description": "$1 is string 'cancel' or 'speed up'"
},
"cancelSwapForFee": {
"message": "Tausch für ~$1 abbrechen",
"description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
},
"cancelSwapForFree": {
"message": "Tausch kostenlos abbrechen"
},
"cancelled": {
"message": "Abgebrochen"
},
@ -1200,9 +1193,6 @@
"enableOpenSeaAPIDescription": {
"message": "Verwenden Sie die OpenSea's API, um NFT-Daten abzurufen. Die NFT-Auto-Erkennung basiert auf der OpenSea's API und wird nicht verfügbar sein, wenn diese deaktiviert ist."
},
"enableSmartTransactions": {
"message": "Intelligente Transaktionen ermöglichen"
},
"enableToken": {
"message": "$1 aktivieren",
"description": "$1 is a token symbol, e.g. ETH"
@ -3239,9 +3229,6 @@
"skipAccountSecurityDetails": {
"message": "Mir ist klar, dass ich meine Konten und alle dazugehörigen Vermögenswerte verlieren kann, solange ich keine Sicherungskopie meiner Geheimen Wiederherstellungsphrase erstelle."
},
"smartTransaction": {
"message": "Intelligente Transaktionen"
},
"snapContent": {
"message": "Diese Inhalte stammen von $1",
"description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap."
@ -3425,9 +3412,6 @@
"strong": {
"message": "Stark"
},
"stxAreHere": {
"message": "Intelligente Transaktionen sind da!"
},
"stxBenefit1": {
"message": "Transaktionskosten minimieren"
},
@ -3449,15 +3433,6 @@
"stxCancelledSubDescription": {
"message": "Versuchen Sie Ihren Swap erneut. Wir werden hier sein, um Sie beim nächsten Mal vor ähnlichen Risiken zu schützen."
},
"stxDescription": {
"message": "MetaMask-Swap ist jetzt viel intelligenter geworden! Wenn Sie intelligente Transaktionen aktivieren, kann MetaMask Ihren Swap programmgesteuert optimieren, um Ihnen zu helfen:"
},
"stxErrorNotEnoughFunds": {
"message": "Nicht genügend Mittel für eine intelligente Transaktion."
},
"stxErrorUnavailable": {
"message": "Intelligente Transaktionen sind vorübergehend nicht verfügbar."
},
"stxFailure": {
"message": "Swap fehlgeschlagen"
},
@ -3471,9 +3446,6 @@
"stxPendingPubliclySubmittingSwap": {
"message": "Ihr Swap wird öffentlich eingereicht ..."
},
"stxSubDescription": {
"message": "* Intelligente Transaktionen versuchen mehrmals, Ihre Transaktion privat zu übermitteln. Wenn alle Versuche fehlschlagen, wird die Transaktion öffentlich übertragen, um sicherzustellen, dass Ihr Swap erfolgreich durchgeführt wird."
},
"stxSuccess": {
"message": "Swap abgeschlossen!"
},

View File

@ -195,10 +195,6 @@
"addCustomToken": {
"message": "Προσθήκη Προσαρμοσμένου Token"
},
"addCustomTokenByContractAddress": {
"message": "Αδυναμία εύρεσης token; Μπορείτε να προσθέσετε χειροκίνητα οποιοδήποτε διακριτικό επικολλώντας τη διεύθυνσή του. Οι διευθύνσεις συμβολαίων Token μπορούν να βρεθούν στο $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "Αυτό θα επιτρέψει σε αυτό το δίκτυο να χρησιμοποιηθεί στο MetaMask."
},
@ -263,6 +259,10 @@
"addToken": {
"message": "Προσθήκη Token"
},
"addTokenByContractAddress": {
"message": "Αδυναμία εύρεσης token; Μπορείτε να προσθέσετε χειροκίνητα οποιοδήποτε διακριτικό επικολλώντας τη διεύθυνσή του. Οι διευθύνσεις συμβολαίων Token μπορούν να βρεθούν στο $1",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"address": {
"message": "Διεύθυνση"
},
@ -548,13 +548,6 @@
"message": "Για να $1 τη συναλλαγή, τα τέλη συναλλαγής πρέπει να αυξηθούν κατά τουλάχιστον 10% ώστε να αναγνωριστούν από το δίκτυο.",
"description": "$1 is string 'cancel' or 'speed up'"
},
"cancelSwapForFee": {
"message": "Ακυρώστε τη συναλλαγή για ~$1",
"description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
},
"cancelSwapForFree": {
"message": "Ακυρώστε τη συναλλαγή δωρεάν"
},
"cancelled": {
"message": "Ακυρώθηκε"
},
@ -1200,9 +1193,6 @@
"enableOpenSeaAPIDescription": {
"message": "Χρησιμοποιήστε το API OpenSea για λήψη δεδομένων NFT. Η αυτόματη ανίχνευση NFT βασίζεται στο API του OpenSea, και δεν θα είναι διαθέσιμη όταν αυτό είναι απενεργοποιημένο."
},
"enableSmartTransactions": {
"message": "Ενεργοποίηση Έξυπνων Συναλλαγών"
},
"enableToken": {
"message": "ενεργοποίηση $1",
"description": "$1 is a token symbol, e.g. ETH"
@ -3236,9 +3226,6 @@
"skipAccountSecurityDetails": {
"message": "Καταλαβαίνω ότι μέχρι να δημιουργήσω αντίγραφα ασφαλείας για τη Μυστική Φράση Ανάκτησής μου, μπορεί να χάσω τους λογαριασμούς μου και όλα τα περιουσιακά στοιχεία τους."
},
"smartTransaction": {
"message": "Έξυπνη Συναλλαγή"
},
"snapContent": {
"message": "Αυτό το περιεχόμενο προέρχεται από το $1",
"description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap."
@ -3422,9 +3409,6 @@
"strong": {
"message": "Ισχυρός"
},
"stxAreHere": {
"message": "Οι Έξυπνες Συναλλαγές είναι εδώ!"
},
"stxBenefit1": {
"message": "Ελαχιστοποίηση του κόστους συναλλαγής"
},
@ -3446,15 +3430,6 @@
"stxCancelledSubDescription": {
"message": "Προσπαθήστε ξανά να κάνετε ανταλλαγή. Θα είμαστε εδώ για να σας προστατεύσουμε από παρόμοιους κινδύνους και την επόμενη φορά."
},
"stxDescription": {
"message": "Οι Ανταλλαγές MetaMask μόλις έγιναν πολύ πιο έξυπνες! Η ενεργοποίηση των Έξυπνων Συναλλαγών θα επιτρέψει στο MetaMask να βελτιώσει προγραμματικά τις Ανταλλαγές σας ώστε να απολαμβάνετε:"
},
"stxErrorNotEnoughFunds": {
"message": "Δεν υπάρχουν αρκετοί πόροι για αυτή την έξυπνη συναλλαγή."
},
"stxErrorUnavailable": {
"message": "Οι Έξυπνες Συναλλαγές είναι προσωρινά μη διαθέσιμες."
},
"stxFailure": {
"message": "Η Ανταλλαγή απέτυχε"
},
@ -3468,9 +3443,6 @@
"stxPendingPubliclySubmittingSwap": {
"message": "Δημόσια υποβολή της Ανταλλαγής σας..."
},
"stxSubDescription": {
"message": "* Οι Έξυπνες Συναλλαγές θα προσπαθήσουν να υποβάλουν τη συναλλαγή σας ιδιωτικά, πολλές φορές. Εάν όλες οι προσπάθειες αποτύχουν, η συναλλαγή θα μεταδοθεί δημόσια για να διασφαλιστεί η επιτυχής πραγματοποίηση της ανταλλαγής σας."
},
"stxSuccess": {
"message": "Η ανταλλαγή ολοκληρώθηκε!"
},

View File

@ -204,10 +204,6 @@
"addCustomToken": {
"message": "Add custom token"
},
"addCustomTokenByContractAddress": {
"message": "Cant find a token? You can manually add any token by pasting its address. Token contract addresses can be found on $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "This will allow this network to be used within MetaMask."
},
@ -275,6 +271,10 @@
"addToken": {
"message": "Add token"
},
"addTokenByContractAddress": {
"message": "Cant find a token? You can manually add any token by pasting its address. Token contract addresses can be found on $1",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addingCustomNetwork": {
"message": "Adding Network"
},
@ -438,6 +438,10 @@
"attemptSendingAssets": {
"message": "If you attempt to send assets directly from one network to another, this may result in permanent asset loss. Make sure to use a bridge."
},
"attemptToCancelSwap": {
"message": "Attempt to cancel swap for ~$1",
"description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Swap"
},
"attemptingConnect": {
"message": "Attempting to connect to blockchain."
},
@ -600,13 +604,6 @@
"message": "To $1 a transaction the gas fee must be increased by at least 10% for it to be recognized by the network.",
"description": "$1 is string 'cancel' or 'speed up'"
},
"cancelSwapForFee": {
"message": "Cancel swap for ~$1",
"description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
},
"cancelSwapForFree": {
"message": "Cancel swap for free"
},
"cancelled": {
"message": "Cancelled"
},
@ -1378,8 +1375,8 @@
"enableOpenSeaAPIDescription": {
"message": "Use OpenSea's API to fetch NFT data. NFT auto-detection relies on OpenSea's API, and will not be available when this is turned off."
},
"enableSmartTransactions": {
"message": "Enable smart transactions"
"enableSmartSwaps": {
"message": "Enable smart swaps"
},
"enableSnap": {
"message": "Enable"
@ -1438,6 +1435,9 @@
"enterPasswordContinue": {
"message": "Enter password to continue"
},
"enterTokenNameOrAddress": {
"message": "Enter token name or paste address"
},
"enterYourPassword": {
"message": "Enter your password"
},
@ -2535,6 +2535,9 @@
"notCurrentAccount": {
"message": "Is this the correct account? It's different from the currently selected account in your wallet"
},
"notEnoughBalance": {
"message": "Insufficient balance"
},
"notEnoughGas": {
"message": "Not enough gas"
},
@ -2661,6 +2664,16 @@
"message": "Ledger and Firefox Users Experiencing Connection Issues",
"description": "Title for a notification in the 'See What's New' popup. Tells users that latest firefox users using U2F may experience connection issues."
},
"notifications21ActionText": {
"message": "Try it out"
},
"notifications21Description": {
"message": "We've updated Swaps in the MetaMask extension to be easier and faster to use.",
"description": "Description of a notification in the 'See What's New' popup. Describes NFT autodetection feature."
},
"notifications21Title": {
"message": "Introducing new and refreshed Swaps!"
},
"notifications3ActionText": {
"message": "Read more",
"description": "The 'call to action' on the button, or link, of the 'Stay secure' notification. Upon clicking, users will be taken to a page about security on the metamask support website."
@ -3004,6 +3017,9 @@
"message": "You have (1) pending transaction.",
"description": "$1 is count of pending transactions"
},
"percentage": {
"message": "$1%"
},
"permissionRequest": {
"message": "Permission request"
},
@ -3261,6 +3277,9 @@
"queued": {
"message": "Queued"
},
"quoteRate": {
"message": "Quote rate"
},
"reAddAccounts": {
"message": "re-add any other accounts"
},
@ -3776,8 +3795,23 @@
"skipAccountSecurityDetails": {
"message": "I understand that until I back up my Secret Recovery Phrase, I may lose my accounts and all of their assets."
},
"smartTransaction": {
"message": "Smart transaction"
"smartSwap": {
"message": "Smart swap"
},
"smartSwapsAreHere": {
"message": "Smart Swaps are here!"
},
"smartSwapsDescription": {
"message": "MetaMask Swaps just got a whole lot smarter! Enabling Smart Swaps will allow MetaMask to programmatically optimize your Swap to help:"
},
"smartSwapsErrorNotEnoughFunds": {
"message": "Not enough funds for a smart swap."
},
"smartSwapsErrorUnavailable": {
"message": "Smart Swaps are temporarily unavailable."
},
"smartSwapsSubDescription": {
"message": "* Smart Swaps will attempt to submit your transaction privately, multiple times. If all attempts fail, the transaction will be broadcast publicly to ensure your Swap successfully goes through."
},
"snapConnectionWarning": {
"message": "$1 wants to connect to $2. Only continue if you trust this website.",
@ -4036,9 +4070,6 @@
"strong": {
"message": "Strong"
},
"stxAreHere": {
"message": "Smart Transactions are here!"
},
"stxBenefit1": {
"message": "Minimize transaction costs"
},
@ -4060,15 +4091,6 @@
"stxCancelledSubDescription": {
"message": "Try your swap again. Well be here to protect you against similar risks next time."
},
"stxDescription": {
"message": "MetaMask Swaps just got a whole lot smarter! Enabling Smart Transactions will allow MetaMask to programmatically optimize your Swap to help:"
},
"stxErrorNotEnoughFunds": {
"message": "Not enough funds for a smart transaction."
},
"stxErrorUnavailable": {
"message": "Smart Transactions are temporarily unavailable."
},
"stxFailure": {
"message": "Swap failed"
},
@ -4082,9 +4104,6 @@
"stxPendingPubliclySubmittingSwap": {
"message": "Publicly submitting your Swap..."
},
"stxSubDescription": {
"message": "* Smart Transactions will attempt to submit your transaction privately, multiple times. If all attempts fail, the transaction will be broadcast publicly to ensure your Swap successfully goes through."
},
"stxSuccess": {
"message": "Swap complete!"
},
@ -4145,6 +4164,9 @@
"swapAmountReceivedInfo": {
"message": "This is the minimum amount you will receive. You may receive more depending on slippage."
},
"swapAnyway": {
"message": "Swap anyway"
},
"swapApproval": {
"message": "Approve $1 for swaps",
"description": "Used in the transaction display list to describe a transaction that is an approve call on a token that is to be swapped.. $1 is the symbol of a token that has been approved."
@ -4153,6 +4175,12 @@
"message": "You need $1 more $2 to complete this swap",
"description": "Tells the user how many more of a given token they need for a specific swap. $1 is an amount of tokens and $2 is the token symbol."
},
"swapAreYouStillThere": {
"message": "Are you still there?"
},
"swapAreYouStillThereDescription": {
"message": "Were ready to show you the latest quotes when you want to continue"
},
"swapBuildQuotePlaceHolderText": {
"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"
@ -4160,6 +4188,9 @@
"swapConfirmWithHwWallet": {
"message": "Confirm with your hardware wallet"
},
"swapContinueSwapping": {
"message": "Continue swapping"
},
"swapContractDataDisabledErrorDescription": {
"message": "In the Ethereum app on your Ledger, go to \"Settings\" and allow contract data. Then, try your swap again."
},
@ -4178,6 +4209,9 @@
"swapEditLimit": {
"message": "Edit limit"
},
"swapEditTransactionSettings": {
"message": "Edit transaction settings"
},
"swapEnableDescription": {
"message": "This is required and gives MetaMask permission to swap your $1.",
"description": "Gives the user info about the required approval transaction for swaps. $1 will be the symbol of a token being approved for swaps."
@ -4186,6 +4220,9 @@
"message": "This will $1 for swapping",
"description": "$1 is for the 'enableToken' key, e.g. 'enable ETH'"
},
"swapEnterAmount": {
"message": "Enter an amount"
},
"swapEstimatedNetworkFees": {
"message": "Estimated network fees"
},
@ -4199,6 +4236,9 @@
"swapFailedErrorTitle": {
"message": "Swap failed"
},
"swapFetchingQuote": {
"message": "Fetching quote"
},
"swapFetchingQuoteNofN": {
"message": "Fetching quote $1 of $2",
"description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap."
@ -4239,6 +4279,9 @@
"message": "Includes a $1% MetaMask fee.",
"description": "Provides information about the fee that metamask takes for swaps. $1 is a decimal number."
},
"swapLearnMore": {
"message": "Learn more about Swaps"
},
"swapLowSlippageError": {
"message": "Transaction may fail, max slippage too low."
},
@ -4260,6 +4303,10 @@
"message": "New quotes in $1",
"description": "Tells the user the amount of time until the currently displayed quotes are update. $1 is a time that is counting down from 1:00 to 0:00"
},
"swapNoTokensAvailable": {
"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"
},
"swapOnceTransactionHasProcess": {
"message": "Your $1 will be added to your account once this transaction has processed.",
"description": "This message communicates the token that is being transferred. It is shown on the awaiting swap screen. The $1 will be a token symbol."
@ -4287,6 +4334,10 @@
"swapQuoteDetails": {
"message": "Quote details"
},
"swapQuoteNofM": {
"message": "$1 of $2",
"description": "A count of possible quotes shown to the user while they are waiting for quotes to be fetched. $1 is the number of quotes already loaded, and $2 is the total number of resources that we check for quotes. Keep in mind that not all resources will have a quote for a particular swap."
},
"swapQuoteSource": {
"message": "Quote source"
},
@ -4296,6 +4347,9 @@
"swapQuotesExpiredErrorTitle": {
"message": "Quotes timeout"
},
"swapQuotesNotAvailableDescription": {
"message": "Reduce the size of your trade or use a different token."
},
"swapQuotesNotAvailableErrorDescription": {
"message": "Try adjusting the amount or slippage settings and try again."
},
@ -4332,16 +4386,52 @@
"swapSelectQuotePopoverDescription": {
"message": "Below are all the quotes gathered from multiple liquidity sources."
},
"swapSelectToken": {
"message": "Select token"
},
"swapShowLatestQuotes": {
"message": "Show latest quotes"
},
"swapSlippageNegative": {
"message": "Slippage must be greater or equal to zero"
},
"swapSlippageNegativeDescription": {
"message": "Slippage must be greater or equal to zero"
},
"swapSlippageNegativeTitle": {
"message": "Increase slippage to continue"
},
"swapSlippageOverLimitDescription": {
"message": "Slippage tolerance must be 15% or less. Anything higher will result in a bad rate."
},
"swapSlippageOverLimitTitle": {
"message": "Reduce slippage to continue"
},
"swapSlippagePercent": {
"message": "$1%",
"description": "$1 is the amount of % for slippage"
},
"swapSlippageTooLowDescription": {
"message": "Max slippage is too low which may cause your transaction to fail."
},
"swapSlippageTooLowTitle": {
"message": "Increase slippage to avoid transaction failure"
},
"swapSlippageTooltip": {
"message": "If the price changes between the time your order is placed and confirmed its called “slippage”. Your swap will automatically cancel if slippage exceeds your “slippage tolerance” setting."
},
"swapSlippageVeryHighDescription": {
"message": "The slippage entered is considered very high and may result in a bad rate"
},
"swapSlippageVeryHighTitle": {
"message": "Very high slippage"
},
"swapSlippageZeroDescription": {
"message": "There are fewer zero-slippage quote providers which will result in a less competitive quote."
},
"swapSlippageZeroTitle": {
"message": "Sourcing zero-slippage providers"
},
"swapSource": {
"message": "Liquidity source"
},
@ -4358,7 +4448,7 @@
"message": "Swap from"
},
"swapSwapSwitch": {
"message": "Switch from and to tokens"
"message": "Switch token order"
},
"swapSwapTo": {
"message": "Swap to"
@ -4366,6 +4456,13 @@
"swapToConfirmWithHwWallet": {
"message": "to confirm with your hardware wallet"
},
"swapTokenAddedManuallyDescription": {
"message": "Verify this token on $1 and make sure it is the token you want to trade.",
"description": "$1 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
},
"swapTokenAddedManuallyTitle": {
"message": "Token added manually"
},
"swapTokenAvailable": {
"message": "Your $1 has been added to your account.",
"description": "This message is shown after a swap is successful and communicates the exact amount of tokens the user has received for a swap. The $1 is a decimal number of tokens followed by the token symbol."
@ -4392,6 +4489,13 @@
"message": "Verified on $1 sources.",
"description": "Indicates the number of token information sources that recognize the symbol + address. $1 is a decimal number."
},
"swapTokenVerifiedOn1SourceDescription": {
"message": "$1 is only verified on 1 source. Consider verifying it on $2 before proceeding.",
"description": "$1 is a token name, $2 points the user to etherscan as a place they can verify information about a token. $1 is replaced with the translation for \"etherscan\""
},
"swapTokenVerifiedOn1SourceTitle": {
"message": "Potentially inauthentic token"
},
"swapTooManyDecimalsError": {
"message": "$1 allows up to $2 decimals",
"description": "$1 is a token symbol and $2 is the max. number of decimals allowed for the token"
@ -4429,9 +4533,16 @@
"message": "Not enough $1 to complete this transaction",
"description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol"
},
"swapsNotEnoughToken": {
"message": "Not enough $1",
"description": "Tells the user that they don't have enough of a token for a proposed swap. $1 is a token symbol"
},
"swapsViewInActivity": {
"message": "View in activity"
},
"switch": {
"message": "Switch"
},
"switchEthereumChainConfirmationDescription": {
"message": "This will switch the selected network within MetaMask to a previously added network:"
},
@ -4734,6 +4845,9 @@
"transactionSecurityCheckDescription": {
"message": "We use third-party APIs to detect and display risks involved in unsigned transaction and signature requests before you sign them. These services will have access to your unsigned transaction and signature requests, your account address, and your preferred language."
},
"transactionSettings": {
"message": "Transaction settings"
},
"transactionSubmitted": {
"message": "Transaction submitted with estimated gas fee of $1 at $2."
},

View File

@ -195,10 +195,6 @@
"addCustomToken": {
"message": "Añadir token personalizado"
},
"addCustomTokenByContractAddress": {
"message": "¿No encuentra un token? Puede agregar cualquier token si copia su dirección. Puede encontrar la dirección de contrato del token en $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "Esto permitirá que la red se utilice en MetaMask."
},
@ -263,6 +259,10 @@
"addToken": {
"message": "Agregar token"
},
"addTokenByContractAddress": {
"message": "¿No encuentra un token? Puede agregar cualquier token si copia su dirección. Puede encontrar la dirección de contrato del token en $1",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"address": {
"message": "Dirección"
},
@ -548,13 +548,6 @@
"message": "Para $1 una transacción, la tarifa de gas debe aumentar al menos un 10% para que sea reconocida por la red.",
"description": "$1 is string 'cancel' or 'speed up'"
},
"cancelSwapForFee": {
"message": "Cancelar el swap por ~$1",
"description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
},
"cancelSwapForFree": {
"message": "Cancelar el swap gratuitamente"
},
"cancelled": {
"message": "Cancelado"
},
@ -1200,9 +1193,6 @@
"enableOpenSeaAPIDescription": {
"message": "Utilice la API de OpenSea para obtener los datos de NFT. La autodetección de NFT depende de la API de OpenSea y no estará disponible si la API está desactivada."
},
"enableSmartTransactions": {
"message": "Habilitar transacciones inteligentes"
},
"enableToken": {
"message": "activar $1",
"description": "$1 is a token symbol, e.g. ETH"
@ -3239,9 +3229,6 @@
"skipAccountSecurityDetails": {
"message": "Entiendo que hasta que no haga una copia de seguridad de mi frase secreta de recuperación, puedo perder mis cuentas y todos los activos asociados."
},
"smartTransaction": {
"message": "Transacción inteligente"
},
"snapContent": {
"message": "Este contenido proviene de $1",
"description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap."
@ -3425,9 +3412,6 @@
"strong": {
"message": "Fuerte"
},
"stxAreHere": {
"message": "¡Las transacciones inteligentes están aquí!"
},
"stxBenefit1": {
"message": "Minimizar los costos de transacción"
},
@ -3449,15 +3433,6 @@
"stxCancelledSubDescription": {
"message": "Intente su swap nuevamente. Estaremos aquí para protegerlo contra riesgos similares la próxima vez."
},
"stxDescription": {
"message": "¡MetaMask Swaps ahora es mucho más inteligente! Habilitar transacciones inteligentes permitirá que MetaMask optimice mediante programación su swap para ayudar:"
},
"stxErrorNotEnoughFunds": {
"message": "No hay suficientes fondos para una transacción inteligente."
},
"stxErrorUnavailable": {
"message": "Las transacciones inteligentes no están disponibles temporalmente."
},
"stxFailure": {
"message": "Error al canjear"
},
@ -3471,9 +3446,6 @@
"stxPendingPubliclySubmittingSwap": {
"message": "Enviando su swap de forma pública..."
},
"stxSubDescription": {
"message": "* Transacciones inteligentes intentará enviar su transacción de forma privada varias veces. Si todos los intentos fallan, la transacción se transmitirá públicamente para garantizar que su swap se realice con éxito."
},
"stxSuccess": {
"message": "¡Swap finalizado!"
},

View File

@ -103,10 +103,6 @@
"addCustomToken": {
"message": "Añadir token personalizado"
},
"addCustomTokenByContractAddress": {
"message": "¿No encuentra un token? Para agregar un token, copie su dirección. Puede encontrar la dirección de contrato del token en $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "Esto permitirá que la red se utilice en MetaMask."
},
@ -139,6 +135,10 @@
"addToken": {
"message": "Agregar token"
},
"addTokenByContractAddress": {
"message": "¿No encuentra un token? Para agregar un token, copie su dirección. Puede encontrar la dirección de contrato del token en $1",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"address": {
"message": "Dirección"
},

View File

@ -195,10 +195,6 @@
"addCustomToken": {
"message": "Ajouter un jeton personnalisé"
},
"addCustomTokenByContractAddress": {
"message": "Vous ne trouvez pas de jeton? Vous pouvez ajouter manuellement nimporte quel jeton avec son adresse par copier-coller. Les adresses des contrats de jetons sont disponibles sur $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "Cela permettra dutiliser ce réseau dans MetaMask."
},
@ -263,6 +259,10 @@
"addToken": {
"message": "Ajouter le jeton"
},
"addTokenByContractAddress": {
"message": "Vous ne trouvez pas de jeton? Vous pouvez ajouter manuellement nimporte quel jeton avec son adresse par copier-coller. Les adresses des contrats de jetons sont disponibles sur $1",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"address": {
"message": "Adresse"
},
@ -548,13 +548,6 @@
"message": "Pour $1 la transaction, les gas fees doivent être augmentés dau moins 10 % pour être reconnus par le réseau.",
"description": "$1 is string 'cancel' or 'speed up'"
},
"cancelSwapForFee": {
"message": "Annuler le swap pour ~$1",
"description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
},
"cancelSwapForFree": {
"message": "Annuler le swap gratuitement"
},
"cancelled": {
"message": "Annulé"
},
@ -1200,9 +1193,6 @@
"enableOpenSeaAPIDescription": {
"message": "Utilisez lAPI OpenSea pour récupérer les données de NFT. La détection automatique de NFT repose sur lAPI OpenSea et ne sera pas disponible si elle est désactivée."
},
"enableSmartTransactions": {
"message": "Activer les transactions intelligentes"
},
"enableToken": {
"message": "activer $1",
"description": "$1 is a token symbol, e.g. ETH"
@ -3239,9 +3229,6 @@
"skipAccountSecurityDetails": {
"message": "Je suis conscient(e) que tant que je naurai pas sauvegardé ma phrase secrète de récupération, je risque de perdre mes comptes et tous leurs actifs."
},
"smartTransaction": {
"message": "Transaction intelligente"
},
"snapContent": {
"message": "Ce contenu provient de $1",
"description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap."
@ -3425,9 +3412,6 @@
"strong": {
"message": "Robuste"
},
"stxAreHere": {
"message": "Les transactions intelligentes sont là !"
},
"stxBenefit1": {
"message": "Minimise les frais de transaction"
},
@ -3449,15 +3433,6 @@
"stxCancelledSubDescription": {
"message": "Réessayez le swap. Nous serons là pour vous protéger contre des risques similaires la prochaine fois."
},
"stxDescription": {
"message": "MetaMask Swaps vient de devenir beaucoup plus intelligent ! Si vous activez les transactions intelligentes, MetaMask pourra optimiser programmatiquement votre swap pour vous aider à :"
},
"stxErrorNotEnoughFunds": {
"message": "Fonds insuffisants pour une transaction intelligente."
},
"stxErrorUnavailable": {
"message": "Les transactions intelligentes sont temporairement indisponibles."
},
"stxFailure": {
"message": "Échec du swap"
},
@ -3471,9 +3446,6 @@
"stxPendingPubliclySubmittingSwap": {
"message": "Soumission publique de votre Swap..."
},
"stxSubDescription": {
"message": "* Avec les transactions intelligentes, votre transaction sera soumise plusieurs fois en privé. Si toutes les tentatives échouent, la transaction sera diffusée publiquement pour sassurer de la réussite de votre swap."
},
"stxSuccess": {
"message": "Swap terminé !"
},

View File

@ -195,10 +195,6 @@
"addCustomToken": {
"message": "कस्टम टोकन जोड़ें"
},
"addCustomTokenByContractAddress": {
"message": "टोकन नहीं मिल रहा है? आप किसी भी टोकन का पता पेस्ट करके उसे मैन्युअल रूप से भी जोड़ सकते हैं। टोकन अनुबंध पते $1 पर मिल सकते हैं।",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "इससे इस नेटवर्क को MetaMask के अंदर उपयोग करने की अनुमति मिलेगी।"
},
@ -263,6 +259,10 @@
"addToken": {
"message": "टोकन जोड़ें"
},
"addTokenByContractAddress": {
"message": "टोकन नहीं मिल रहा है? आप किसी भी टोकन का पता पेस्ट करके उसे मैन्युअल रूप से भी जोड़ सकते हैं। टोकन अनुबंध पते $1 पर मिल सकते हैं।",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"address": {
"message": "पता"
},
@ -548,13 +548,6 @@
"message": "किसी लेनदेन को $1 करने के लिए गैस शुल्क में कम से कम 10% की वृद्धि की जानी चाहिए ताकि उसे नेटवर्क द्वारा मान्यता मिल सके।",
"description": "$1 is string 'cancel' or 'speed up'"
},
"cancelSwapForFee": {
"message": "~$1 में स्वैप रद्द करें",
"description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
},
"cancelSwapForFree": {
"message": "मुफ्त में स्वैप रद्द करें"
},
"cancelled": {
"message": "रद्द किया गया"
},
@ -1200,9 +1193,6 @@
"enableOpenSeaAPIDescription": {
"message": "NFT डेटा लाने के लिए OpenSea के API का उपयोग करें। NFT ऑटो-डिटेक्शन OpenSea के API पर निर्भर करता है, और इसके बंद होने पर उपलब्ध नहीं होगा।"
},
"enableSmartTransactions": {
"message": "स्मार्ट लेनदेन को सक्षम करें"
},
"enableToken": {
"message": "$1 इनेबल करें",
"description": "$1 is a token symbol, e.g. ETH"
@ -3239,9 +3229,6 @@
"skipAccountSecurityDetails": {
"message": "मैं समझता हूं कि जब तक मैं अपने सीक्रेट रिकवरी फ्रेज का बैकअप नहीं लेता, मैं अपने खाते और उनकी सभी संपत्ति खो सकता हूं।"
},
"smartTransaction": {
"message": "स्मार्ट लेनदेन"
},
"snapContent": {
"message": "यह सामग्री $1 से आ रही है",
"description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap."
@ -3425,9 +3412,6 @@
"strong": {
"message": "मजबूत"
},
"stxAreHere": {
"message": "स्मार्ट लेनदेन यहां पर हैं!"
},
"stxBenefit1": {
"message": "लेनदेन लागतें मिनिमाइज़ करें"
},
@ -3449,15 +3433,6 @@
"stxCancelledSubDescription": {
"message": "अपना स्वैप फिर से कोशिश करें। अगली बार भी इस तरह के जोखिमों से आपको बचाने के लिए हम यहां होंगे।"
},
"stxDescription": {
"message": "MetaMask के स्वैप अब और अधिक स्मार्ट हो गए हैं! इन हेतु सहयता के लिए स्मार्ट लेनदेन को सक्षम करने से MetaMask आपके स्वैप को प्रोग्रामेटिक रूप से ऑप्टिमाइज़ कर पाएगा:"
},
"stxErrorNotEnoughFunds": {
"message": "एक स्मार्ट लेनदेन के लिए पर्याप्त फंड नहीं है।"
},
"stxErrorUnavailable": {
"message": "स्मार्ट लेनदेन अस्थाई तौर पर अनुपबल्ध हैं।"
},
"stxFailure": {
"message": "स्वैप विफल हुआ"
},
@ -3471,9 +3446,6 @@
"stxPendingPubliclySubmittingSwap": {
"message": "आपका स्वैप सार्वजनिक रूप से सबमिट किया जा रहा है..."
},
"stxSubDescription": {
"message": "* स्मार्ट लेनदेन आपके लेनदेन को निजी तौर पर, अनेक बार जमा करने का प्रयास करेंगे। यदि सभी प्रयास विफल हो जाते हैं, तो लेनदेन को सार्वजनिक रूप से प्रसारित किया जाएगा ताकि यह सुनिश्चित हो सके कि आपका स्वैप सफलतापूर्वक पूरा हो।"
},
"stxSuccess": {
"message": "स्वैप पूरा हुआ!"
},

View File

@ -195,10 +195,6 @@
"addCustomToken": {
"message": "Tambahkan token kustom"
},
"addCustomTokenByContractAddress": {
"message": "Tidak dapat menemukan token? Tambahkan token secara manual dengan menempelkan alamatnya. Alamat kontrak token dapat ditemukan di $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "Tindakan ini akan membantu jaringan ini agar dapat digunakan dengan MetaMask."
},
@ -263,6 +259,10 @@
"addToken": {
"message": "Tambahkan token"
},
"addTokenByContractAddress": {
"message": "Tidak dapat menemukan token? Tambahkan token secara manual dengan menempelkan alamatnya. Alamat kontrak token dapat ditemukan di $1",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"address": {
"message": "Alamat"
},
@ -548,13 +548,6 @@
"message": "Untuk $1 suatu transaksi, biaya gas harus dinaikkan minimal 10% agar dapat dikenali oleh jaringan.",
"description": "$1 is string 'cancel' or 'speed up'"
},
"cancelSwapForFee": {
"message": "Batalkan swap untuk ~$",
"description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
},
"cancelSwapForFree": {
"message": "Batalkan swap gratis"
},
"cancelled": {
"message": "Dibatalkan"
},
@ -1200,9 +1193,6 @@
"enableOpenSeaAPIDescription": {
"message": "Gunakan API OpenSea untuk mengambil data NFT. Deteksi otomatis NFT bergantung pada API OpenSea, dan tidak akan tersedia saat API ditutup."
},
"enableSmartTransactions": {
"message": "Aktifkan transaksi pintar"
},
"enableToken": {
"message": "aktifkan $1",
"description": "$1 is a token symbol, e.g. ETH"
@ -3239,9 +3229,6 @@
"skipAccountSecurityDetails": {
"message": "Saya memahami bahwa sampai saya mencadangkan Frasa Pemulihan Rahasia, saya dapat kehilangan akun saya dan semua aset yang ada."
},
"smartTransaction": {
"message": "Transaksi pintar"
},
"snapContent": {
"message": "Konten ini berasal dari $1",
"description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap."
@ -3425,9 +3412,6 @@
"strong": {
"message": "Kuat"
},
"stxAreHere": {
"message": "Transaksi Pintar hadir di sini!"
},
"stxBenefit1": {
"message": "Meminimalkan biaya transaksi"
},
@ -3449,15 +3433,6 @@
"stxCancelledSubDescription": {
"message": "Cobalah untuk menukar lagi. Kami akan selalu hadir untuk melindungi Anda dari risiko serupa di lain waktu."
},
"stxDescription": {
"message": "Pertukaran MetaMask menjadi semakin pintar! Mengaktifkan Transaksi Pintar akan memungkinkan MetaMask mengoptimalkan Pertukaran Anda secara terprogram untuk membantu:"
},
"stxErrorNotEnoughFunds": {
"message": "Dana tidak cukup untuk mengaktifkan transaksi pintar."
},
"stxErrorUnavailable": {
"message": "Transaksi Pintar tidak tersedia untuk sementara waktu."
},
"stxFailure": {
"message": "Pertukaran gagal"
},
@ -3471,9 +3446,6 @@
"stxPendingPubliclySubmittingSwap": {
"message": "Kirimkan Swap Anda secara publik..."
},
"stxSubDescription": {
"message": "* Transaksi Pintar akan mencoba mengirimkan transaksi Anda secara pribadi, beberapa kali. Jika semua upaya gagal, transaksi akan disiarkan secara publik untuk memastikan Pertukaran telah berhasil dilakukan."
},
"stxSuccess": {
"message": "Pertukaran selesai!"
},

View File

@ -158,10 +158,6 @@
"addCustomToken": {
"message": "Aggiungi token personalizzato"
},
"addCustomTokenByContractAddress": {
"message": "Non trovi un token? Puoi aggiungere qualsiasi token incollando il suo indirizzo. L'indirizzo del contratto del Token può essere trovato su $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "Ciò consentirà a questa rete di essere utilizzata all'interno di MetaMask."
},
@ -204,6 +200,10 @@
"addToken": {
"message": "Aggiungi Token"
},
"addTokenByContractAddress": {
"message": "Non trovi un token? Puoi aggiungere qualsiasi token incollando il suo indirizzo. L'indirizzo del contratto del Token può essere trovato su $1",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"address": {
"message": "Indirizzo"
},
@ -419,13 +419,6 @@
"message": "Per $1 una transazione la commissione di gas deve crescere almeno del 10% per essere riconosciuto dalla rete.",
"description": "$1 is string 'cancel' or 'speed up'"
},
"cancelSwapForFee": {
"message": "Annulla scambio per ~$1",
"description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
},
"cancelSwapForFree": {
"message": "Annulla scambio gratuitamente"
},
"cancelled": {
"message": "Annullata"
},

View File

@ -195,10 +195,6 @@
"addCustomToken": {
"message": "カスタムトークンを追加"
},
"addCustomTokenByContractAddress": {
"message": "トークンが見つからない場合、アドレスをペーストして手動でトークンを追加できます。トークンコントラクトアドレスは$1にあります。",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "これにより、このネットワークはMetaMask内で使用できるようになります。"
},
@ -263,6 +259,10 @@
"addToken": {
"message": "トークンを追加"
},
"addTokenByContractAddress": {
"message": "トークンが見つからない場合、アドレスをペーストして手動でトークンを追加できます。トークンコントラクトアドレスは$1にあります",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"address": {
"message": "アドレス"
},
@ -548,13 +548,6 @@
"message": "トランザクションを$1するには、ネットワークに認識されるようにガス代を 10% 以上増額する必要があります。",
"description": "$1 is string 'cancel' or 'speed up'"
},
"cancelSwapForFee": {
"message": "$1 以下でスワップをキャンセル",
"description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
},
"cancelSwapForFree": {
"message": "無料でスワップをキャンセル"
},
"cancelled": {
"message": "キャンセル済み"
},
@ -1200,9 +1193,6 @@
"enableOpenSeaAPIDescription": {
"message": "OpenSea APIを使用してNFTデータを取得します。NFT自動検出はOpenSea APIを使用するため、この設定をオフにすると利用できなくなります。"
},
"enableSmartTransactions": {
"message": "スマートトランザクションを有効にする"
},
"enableToken": {
"message": "$1を有効にする",
"description": "$1 is a token symbol, e.g. ETH"
@ -3239,9 +3229,6 @@
"skipAccountSecurityDetails": {
"message": "私は、シークレットリカバリーフレーズをバックアップするまで、アカウントとそのアセットのすべてを失う可能性があることを理解しています。"
},
"smartTransaction": {
"message": "スマートトランザクション"
},
"snapContent": {
"message": "このコンテンツは $1 からのものです",
"description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap."
@ -3425,9 +3412,6 @@
"strong": {
"message": "強"
},
"stxAreHere": {
"message": "スマートトランザクションが利用可能になりました!"
},
"stxBenefit1": {
"message": "トランザクションコストを最小化"
},
@ -3449,15 +3433,6 @@
"stxCancelledSubDescription": {
"message": "もう一度スワップをお試しください。次回は同様のリスクを避けられるようサポートします。"
},
"stxDescription": {
"message": "MetaMask Swaps がはるかに賢くなりましたスマートトランザクションを有効にすると、MetaMask がプログラムに従ってスワップを最適化できるようになるため、以下のようなメリットがあります。"
},
"stxErrorNotEnoughFunds": {
"message": "スマートトランザクションに十分な資金がありません。"
},
"stxErrorUnavailable": {
"message": "スマートトランザクションは一時的に利用できません。"
},
"stxFailure": {
"message": "スワップに失敗しました"
},
@ -3471,9 +3446,6 @@
"stxPendingPubliclySubmittingSwap": {
"message": "スワップを公開で送信中..."
},
"stxSubDescription": {
"message": "* スマートトランザクションは、非公開でトランザクションのの送信を数回試みます。すべての試みが失敗した場合、スワップが成功するようトランザクションが公開されます。"
},
"stxSuccess": {
"message": "スワップ完了!"
},

View File

@ -195,10 +195,6 @@
"addCustomToken": {
"message": "커스텀 토큰 추가"
},
"addCustomTokenByContractAddress": {
"message": "이 토큰을 찾을 수 없으신가요? 토큰 주소를 붙여넣으면 토큰을 직접 추가할 수 있습니다. 토큰의 계약 주소는 $1에서 찾을 수 있습니다.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "이렇게 하면 MetaMask 내에서 이 네트워크를 사용할 수 있습니다."
},
@ -263,6 +259,10 @@
"addToken": {
"message": "토큰 추가"
},
"addTokenByContractAddress": {
"message": "이 토큰을 찾을 수 없으신가요? 토큰 주소를 붙여넣으면 토큰을 직접 추가할 수 있습니다. 토큰의 계약 주소는 $1에서 찾을 수 있습니다",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"address": {
"message": "주소"
},
@ -548,13 +548,6 @@
"message": "거래를 $1하려면 가스비를 최소 10%를 인상해야 네트워크에서 인식될 수 있습니다.",
"description": "$1 is string 'cancel' or 'speed up'"
},
"cancelSwapForFee": {
"message": "~$1 비용으로 스왑 취소",
"description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
},
"cancelSwapForFree": {
"message": "무료로 스왑 취소"
},
"cancelled": {
"message": "취소됨"
},
@ -1200,9 +1193,6 @@
"enableOpenSeaAPIDescription": {
"message": "OpenSea의 API를 사용하여 NFT 데이터를 가져옵니다. NFT 자동 감지는 OpenSea의 API에 의존하며 이 API가 꺼져 있으면 사용할 수 없습니다."
},
"enableSmartTransactions": {
"message": "스마트 트랜잭션 활성화"
},
"enableToken": {
"message": "$1 활성화",
"description": "$1 is a token symbol, e.g. ETH"
@ -3239,9 +3229,6 @@
"skipAccountSecurityDetails": {
"message": "본인은 본인의 비밀 복구 구문을 백업하지 않는 한 본인의 계정과 모든 자산을 잃을 수 있다는 사실을 이해합니다."
},
"smartTransaction": {
"message": "스마트 트랜잭션"
},
"snapContent": {
"message": "콘텐츠 출처: $1",
"description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap."
@ -3425,9 +3412,6 @@
"strong": {
"message": "강함"
},
"stxAreHere": {
"message": "스마트 거래가 가능합니다!"
},
"stxBenefit1": {
"message": "거래 비용 최소화하기"
},
@ -3449,15 +3433,6 @@
"stxCancelledSubDescription": {
"message": "스왑을 다시 진행하세요. 다음에도 유사한 위험이 발생한다면 보호해 드리겠습니다."
},
"stxDescription": {
"message": "MetaMask 스왑이 더욱 스마트해졌습니다! 스마트 거래를 활성화하면 MetaMask가 프로그램을 통해 스왑을 최적화하여 다음을 도울 수 있습니다."
},
"stxErrorNotEnoughFunds": {
"message": "스마트 거래 자금 부족"
},
"stxErrorUnavailable": {
"message": "스마트 거래를 잠시 사용할 수 없습니다."
},
"stxFailure": {
"message": "스왑 실패"
},
@ -3471,9 +3446,6 @@
"stxPendingPubliclySubmittingSwap": {
"message": "스왑을 공개로 제출하는 중..."
},
"stxSubDescription": {
"message": "*스마트 거래는 비공개로 거래를 제출하기 위해 여러 번 시도할 것입니다. 모든 시도가 실패하면 성공적인 스왑을 위해 거래는 공개적으로 브로드캐스트될 것입니다."
},
"stxSuccess": {
"message": "스왑 완료!"
},

View File

@ -42,10 +42,6 @@
"addContact": {
"message": "Magdagdag ng contact"
},
"addCustomTokenByContractAddress": {
"message": "Walang makitang token? Puwede kang manual na magdagdag ng anumang token sa pamamagitan ng pag-paste ng address nito. Makikita ang mga address ng kontrata ng token sa $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "Bibigyang-daan nito na magamit ang network na ito sa MetaMask."
},
@ -75,6 +71,10 @@
"addToken": {
"message": "Magdagdag ng Token"
},
"addTokenByContractAddress": {
"message": "Walang makitang token? Puwede kang manual na magdagdag ng anumang token sa pamamagitan ng pag-paste ng address nito. Makikita ang mga address ng kontrata ng token sa $1",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"advanced": {
"message": "Advanced"
},

View File

@ -195,10 +195,6 @@
"addCustomToken": {
"message": "Adicionar token personalizado"
},
"addCustomTokenByContractAddress": {
"message": "Não consegue encontrar um token? Cole o endereço para adicionar manualmente qualquer token. Os endereços de contrato do token se encontram em $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "Isso permitirá que essa rede seja usada dentro da MetaMask."
},
@ -263,6 +259,10 @@
"addToken": {
"message": "Adicionar token"
},
"addTokenByContractAddress": {
"message": "Não consegue encontrar um token? Cole o endereço para adicionar manualmente qualquer token. Os endereços de contrato do token se encontram em $1",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"address": {
"message": "Endereço"
},
@ -548,13 +548,6 @@
"message": "Para $1 uma transação, a taxa de gás deve ser aumentada em pelo menos 10% para que seja reconhecida pela rede.",
"description": "$1 is string 'cancel' or 'speed up'"
},
"cancelSwapForFee": {
"message": "Cancelar swap por ~$1",
"description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
},
"cancelSwapForFree": {
"message": "Cancelar swap gratuitamente"
},
"cancelled": {
"message": "Cancelada"
},
@ -1200,9 +1193,6 @@
"enableOpenSeaAPIDescription": {
"message": "Use a API OpenSea para recuperar dados de NFTs. A detecção automática de NFTs depende da API OpenSea e não estará disponível quando essa opção estiver desativada."
},
"enableSmartTransactions": {
"message": "Ativar transações inteligentes"
},
"enableToken": {
"message": "ativar $1",
"description": "$1 is a token symbol, e.g. ETH"
@ -3239,9 +3229,6 @@
"skipAccountSecurityDetails": {
"message": "Compreendo que, até fazer o backup da minha Frase de Recuperação Secreta, poderei perder minhas contas e todos os ativos contidos nela."
},
"smartTransaction": {
"message": "Transação inteligente"
},
"snapContent": {
"message": "Esse conteúdo vem de $1",
"description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap."
@ -3425,9 +3412,6 @@
"strong": {
"message": "Forte"
},
"stxAreHere": {
"message": "As transações inteligentes chegaram!"
},
"stxBenefit1": {
"message": "Minimize os custos das transações"
},
@ -3449,15 +3433,6 @@
"stxCancelledSubDescription": {
"message": "Tente fazer sua swap novamente. Estaremos aqui para te proteger contra riscos semelhantes no futuro."
},
"stxDescription": {
"message": "As swaps na MetaMask ficaram muito mais inteligentes! A ativação de Transações Inteligentes permite que o MetaMask otimize programaticamente suas swaps para evitar:"
},
"stxErrorNotEnoughFunds": {
"message": "Insuficiência de fundos para fazer uma transação inteligente."
},
"stxErrorUnavailable": {
"message": "Indisponibilidade temporária de Transações Inteligentes."
},
"stxFailure": {
"message": "Falha na troca"
},
@ -3471,9 +3446,6 @@
"stxPendingPubliclySubmittingSwap": {
"message": "Enviando seu swap de forma pública..."
},
"stxSubDescription": {
"message": "* A função de Transações Inteligentes tentará enviar a sua transação várias vezes de forma privada. Se todas as tentativas falharem, a transação será transmitida publicamente para garantir que sua Swap seja realizada com sucesso."
},
"stxSuccess": {
"message": "Swap concluído!"
},

View File

@ -103,10 +103,6 @@
"addCustomToken": {
"message": "Adicionar token personalizado"
},
"addCustomTokenByContractAddress": {
"message": "Não consegue encontrar um token? Cole o endereço para adicionar manualmente qualquer token. Os endereços de contrato do token se encontram em $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "Isso permitirá que essa rede seja usada dentro da MetaMask."
},
@ -139,6 +135,10 @@
"addToken": {
"message": "Adicionar token"
},
"addTokenByContractAddress": {
"message": "Não consegue encontrar um token? Cole o endereço para adicionar manualmente qualquer token. Os endereços de contrato do token se encontram em $1",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"address": {
"message": "Endereço"
},

View File

@ -195,10 +195,6 @@
"addCustomToken": {
"message": "Добавить пользовательский токен"
},
"addCustomTokenByContractAddress": {
"message": "Не можете найти токен? Можно вручную добавить любой токен, вставив его адрес. Адреса контракта токена можно найти на $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "Это позволит использовать эту сеть в MetaMask."
},
@ -263,6 +259,10 @@
"addToken": {
"message": "Добавить токен"
},
"addTokenByContractAddress": {
"message": "Не можете найти токен? Можно вручную добавить любой токен, вставив его адрес. Адреса контракта токена можно найти на $1",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"address": {
"message": "Адрес"
},
@ -548,13 +548,6 @@
"message": "Чтобы $1 транзакции плата за газ должна быть увеличена как минимум на 10%. Это позволит обеспечить прием транзакции сетью.",
"description": "$1 is string 'cancel' or 'speed up'"
},
"cancelSwapForFee": {
"message": "Отменить обмен на ~$1",
"description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
},
"cancelSwapForFree": {
"message": "Отменить обмен бесплатно"
},
"cancelled": {
"message": "Отменено"
},
@ -1200,9 +1193,6 @@
"enableOpenSeaAPIDescription": {
"message": "Используйте API OpenSea для получения данных NFT. Для автоматического обнаружения NFT используется API OpenSea, и такое обнаружение будет недоступно, если этот API отключен."
},
"enableSmartTransactions": {
"message": "Включить смарт-транзакции"
},
"enableToken": {
"message": "активирует для $1",
"description": "$1 is a token symbol, e.g. ETH"
@ -3239,9 +3229,6 @@
"skipAccountSecurityDetails": {
"message": "Я понимаю, что, если я не создам резервную копию своей секретной фразы для восстановления, я могу потерять доступ ко всем своим счетам и всем средствам на них."
},
"smartTransaction": {
"message": "Смарт-транзакция"
},
"snapContent": {
"message": "Этот контент поступает от $1",
"description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap."
@ -3425,9 +3412,6 @@
"strong": {
"message": "Сильный"
},
"stxAreHere": {
"message": "Появились смарт-транзакции!"
},
"stxBenefit1": {
"message": "Минимизируйте транзакционные издержки"
},
@ -3449,15 +3433,6 @@
"stxCancelledSubDescription": {
"message": "Попробуйте обменять еще раз. Мы готовы защитить вас от подобных рисков в следующий раз."
},
"stxDescription": {
"message": "Функция обмена в MetaMask стала намного умнее! Включение смарт-транзакций позволит MetaMask программно оптимизировать ваш обмен, чтобы помочь:"
},
"stxErrorNotEnoughFunds": {
"message": "Недостаточно средств для смарт-транзакции."
},
"stxErrorUnavailable": {
"message": "Смарт-транзакции временно недоступны."
},
"stxFailure": {
"message": "Обмен не удался"
},
@ -3471,9 +3446,6 @@
"stxPendingPubliclySubmittingSwap": {
"message": "Публичная отправка вашей операции обмена..."
},
"stxSubDescription": {
"message": "* Смарт-транзакции попытаются несколько раз отправить вашу транзакцию в конфиденциальном порядке. Если все попытки не увенчаются успехом, транзакция будет показана публично, чтобы гарантировать успешное завершение вашего обмена."
},
"stxSuccess": {
"message": "Обмен завершен!"
},

View File

@ -195,10 +195,6 @@
"addCustomToken": {
"message": "Magdagdag ng Custom na Token"
},
"addCustomTokenByContractAddress": {
"message": "Hindi makahanap ng token? Maaari kang manu-manong magdagdag ng anumang token sa pamamagitan ng pag-paste ng address nito. Ang mga address ng token contract ay matatagpuan sa $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "Magpapahintulot ito sa network na ito na gamitin sa loob ng MetaMask."
},
@ -263,6 +259,10 @@
"addToken": {
"message": "Magdagdag ng Token"
},
"addTokenByContractAddress": {
"message": "Hindi makahanap ng token? Maaari kang manu-manong magdagdag ng anumang token sa pamamagitan ng pag-paste ng address nito. Ang mga address ng token contract ay matatagpuan sa $1",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"address": {
"message": "Address"
},
@ -548,13 +548,6 @@
"message": "Sa $1 na transaksyon ang singil sa gas ay dapat tumaas nang hindi bababa sa 10% para ito ay makilala ng network.",
"description": "$1 is string 'cancel' or 'speed up'"
},
"cancelSwapForFee": {
"message": "Kanselahin ang swap sa halagang ~$1",
"description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
},
"cancelSwapForFree": {
"message": "Kanselahin ang swap nang libre"
},
"cancelled": {
"message": "Nakansela"
},
@ -1200,9 +1193,6 @@
"enableOpenSeaAPIDescription": {
"message": "Gamitin ang API ng Opensea upang kunin ang NFT data. ang NFT auto-detection ay umaasa sa API ng OpenSea, at hindi magiging available kapag ito ay isinara."
},
"enableSmartTransactions": {
"message": "Payagan ang mga smart transaction"
},
"enableToken": {
"message": "paganahin ang $1",
"description": "$1 is a token symbol, e.g. ETH"
@ -3239,9 +3229,6 @@
"skipAccountSecurityDetails": {
"message": "Nauunawaan ko na hanggang sa i-back up ko ang aking Secret Recovery Phrase, maaari kong maiwala ang aking mga account at lahat ng kanilang mga asset."
},
"smartTransaction": {
"message": "Smart Transaction"
},
"snapContent": {
"message": "Ang nilalamang ito ay nagmumula sa $1",
"description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap."
@ -3425,9 +3412,6 @@
"strong": {
"message": "Mahirap"
},
"stxAreHere": {
"message": "Narito na ang mga Smart Transaction!"
},
"stxBenefit1": {
"message": "Bawasan ang mga gastos sa transaksyon"
},
@ -3449,15 +3433,6 @@
"stxCancelledSubDescription": {
"message": "Subukan muli ang iyong pagpapalit. Narito kami para protektahan ka sa mga katulad na panganib sa susunod."
},
"stxDescription": {
"message": "Mas humusay pa ang mga Pagpapalit sa MetaMask! Papayagan ng Pagpapagana sa mga Smart Transaction ang MetaMask na pahusayin ang iyong Pagpapalit gamit ang program para makatulong sa: "
},
"stxErrorNotEnoughFunds": {
"message": "Hindi sapat na pondo para sa smart transaction."
},
"stxErrorUnavailable": {
"message": "Pansamantalang hindi available ang mga Smart Transaction."
},
"stxFailure": {
"message": "Hindi matagumpay ang pag-swap"
},
@ -3471,9 +3446,6 @@
"stxPendingPubliclySubmittingSwap": {
"message": "Pampublikong isinusumite ang iyong Swap..."
},
"stxSubDescription": {
"message": "* Susubukan ng mga Smart Transaction na isumite nang pribado ang iyong transaksyon, maraming beses. Kapag nabigo ang lahat ng pagsubok, ipapakita sa publiko ang transaksyon upang matiyak na ang Pagpapalit ay naging matagupay."
},
"stxSuccess": {
"message": "Nakumpleto ang pagpapalit!"
},

View File

@ -195,10 +195,6 @@
"addCustomToken": {
"message": "Özel token ekle"
},
"addCustomTokenByContractAddress": {
"message": "Bir tokeni bulamadınız mı? Adresini yapıştırarak dilediğiniz tokeni manuel olarak ekleyebilirsiniz. Token sözleşme adreslerini $1 alanında bulabilirsiniz.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "Bu, bu ağın MetaMas dahilinde kullanılmasına olanak tanıyacaktır."
},
@ -263,6 +259,10 @@
"addToken": {
"message": "Token ekle"
},
"addTokenByContractAddress": {
"message": "Bir tokeni bulamadınız mı? Adresini yapıştırarak dilediğiniz tokeni manuel olarak ekleyebilirsiniz. Token sözleşme adreslerini $1 alanında bulabilirsiniz",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"address": {
"message": "Adres"
},
@ -548,13 +548,6 @@
"message": "İşlemi $1 için, gaz ücretinin ağ tarafından tanınması amacıyla en az %10 oranında artırılması gerekir.",
"description": "$1 is string 'cancel' or 'speed up'"
},
"cancelSwapForFee": {
"message": "~$1 için swap işlemini iptal edin",
"description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
},
"cancelSwapForFree": {
"message": "Swap işlemini ücretsiz iptal edin"
},
"cancelled": {
"message": "İptal edildi"
},
@ -1200,9 +1193,6 @@
"enableOpenSeaAPIDescription": {
"message": "NFT verilerini almak için OpenSea API'sini kullanın. NFT otomatik algılama OpenSea API'ye dayalıdır ve bu kapatılırsa mevcut olmayacaktır."
},
"enableSmartTransactions": {
"message": "Akıllı işlemleri etkinleştir"
},
"enableToken": {
"message": "şunu etkinleştir: $1",
"description": "$1 is a token symbol, e.g. ETH"
@ -3239,9 +3229,6 @@
"skipAccountSecurityDetails": {
"message": "Gizli Kurtarma İfademi yedekleyene kadar hesaplarımı ve tüm varlıkları kaybedebileceğimi anlıyorum."
},
"smartTransaction": {
"message": "Akıllı işlem"
},
"snapContent": {
"message": "Bu içerik $1 kaynaklıdır",
"description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap."
@ -3425,9 +3412,6 @@
"strong": {
"message": "Güçlü"
},
"stxAreHere": {
"message": "Akıllı İşlemler burada!"
},
"stxBenefit1": {
"message": "İşlem maliyetlerini en aza indir"
},
@ -3449,15 +3433,6 @@
"stxCancelledSubDescription": {
"message": "Takasını tekrar dene. Bir dahaki sefere seni benzer risklere karşı korumak için burada olacağız."
},
"stxDescription": {
"message": "MetaMask Swapları artık çok daha akıllı! Akıllı İşlemleri, MetaMask'in renklerine yardımcı olmak için Swap'ını programlı olarak optimize etme bölümünden yararlanmak:"
},
"stxErrorNotEnoughFunds": {
"message": "Akıllı işlem için yeterli para yok."
},
"stxErrorUnavailable": {
"message": "Akıllı İşlemler geçici olarak kullanılamıyor."
},
"stxFailure": {
"message": "Takas başarısız oldu"
},
@ -3471,9 +3446,6 @@
"stxPendingPubliclySubmittingSwap": {
"message": "Swap işlemin herkese açık olarak gönderiliyor..."
},
"stxSubDescription": {
"message": "* Akıllı İşlemler, işlemini birden çok kez özel olarak göndermeye çalışır. Tüm denemeler başarısız olursa Takas'ının başarılı bir şekilde gerçekleşmesini sağlamak için işlem herkese açık olarak yayınlanacaktır."
},
"stxSuccess": {
"message": "Takas tamamlandı!"
},

View File

@ -195,10 +195,6 @@
"addCustomToken": {
"message": "Thêm token tùy chỉnh"
},
"addCustomTokenByContractAddress": {
"message": "Bạn không tìm thấy token? Bạn có thể dán địa chỉ của bất kỳ token nào để thêm token đó theo cách thủ công. Bạn có thể tìm thấy địa chỉ hợp đồng token trên $1.",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "Thao tác này sẽ cho phép sử dụng mạng này trong MetaMask."
},
@ -263,6 +259,10 @@
"addToken": {
"message": "Thêm token"
},
"addTokenByContractAddress": {
"message": "Bạn không tìm thấy token? Bạn có thể dán địa chỉ của bất kỳ token nào để thêm token đó theo cách thủ công. Bạn có thể tìm thấy địa chỉ hợp đồng token trên $1",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"address": {
"message": "Địa chỉ"
},
@ -548,13 +548,6 @@
"message": "Để $1 một giao dịch, phí gas phải tăng tối thiểu 10% để mạng nhận ra giao dịch này.",
"description": "$1 is string 'cancel' or 'speed up'"
},
"cancelSwapForFee": {
"message": "Hủy hoán đổi với giá ~$1",
"description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
},
"cancelSwapForFree": {
"message": "Hủy hoán đổi miễn phí"
},
"cancelled": {
"message": "Đã hủy"
},
@ -1200,9 +1193,6 @@
"enableOpenSeaAPIDescription": {
"message": "Sử dụng API của OpenSea để tìm nạp dữ liệu NFT. Tính năng tự động phát hiện NFT dựa vào API của OpenSea và sẽ không khả dụng nếu tính năng này bị tắt."
},
"enableSmartTransactions": {
"message": "Bật giao dịch thông minh"
},
"enableToken": {
"message": "bật $1",
"description": "$1 is a token symbol, e.g. ETH"
@ -3239,9 +3229,6 @@
"skipAccountSecurityDetails": {
"message": "Tôi hiểu rằng nếu chưa sao lưu Cụm Mật Khẩu Khôi Phục Bí Mật của mình, tôi có thể bị mất tài khoản và toàn bộ tài sản bên trong."
},
"smartTransaction": {
"message": "Giao dịch thông minh"
},
"snapContent": {
"message": "Nội dung này đến từ $1",
"description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap."
@ -3425,9 +3412,6 @@
"strong": {
"message": "Mạnh"
},
"stxAreHere": {
"message": "Giao dịch thông minh đã ra mắt!"
},
"stxBenefit1": {
"message": "Giảm thiểu chi phí giao dịch"
},
@ -3449,15 +3433,6 @@
"stxCancelledSubDescription": {
"message": "Hãy thử hoán đổi lại. Chúng tôi ở đây để bảo vệ bạn trước những rủi ro tương tự trong lần tới."
},
"stxDescription": {
"message": "Tính năng Hoán đổi của MetaMask nay đã thông minh hơn rất nhiều! Kích hoạt Giao dịch thông minh sẽ cho phép MetaMask tối ưu quy trình Hoán đổi để giúp bạn:"
},
"stxErrorNotEnoughFunds": {
"message": "Không có đủ tiền để thực hiện giao dịch thông minh."
},
"stxErrorUnavailable": {
"message": "Giao dịch thông minh tạm thời không khả dụng."
},
"stxFailure": {
"message": "Hoán đổi không thành công"
},
@ -3471,9 +3446,6 @@
"stxPendingPubliclySubmittingSwap": {
"message": "Đang công khai gửi yêu cầu Hoán đổi của bạn..."
},
"stxSubDescription": {
"message": "* Giao dịch thông minh sẽ cố gắng gửi giao dịch của bạn nhiều lần theo cách riêng tư. Nếu tất cả các lần thử đều không thành công, giao dịch sẽ được phát công khai để đảm bảo Hoán đổi của bạn được thực hiện thành công."
},
"stxSuccess": {
"message": "Hoán đổi hoàn tất!"
},

View File

@ -195,10 +195,6 @@
"addCustomToken": {
"message": "添加自定义代币"
},
"addCustomTokenByContractAddress": {
"message": "找不到代币?您可以通过粘贴其地址手动添加任何代币。代币合约地址可以在 $1 上找到。",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"addEthereumChainConfirmationDescription": {
"message": "这将允许在 MetaMask 中使用此网络。"
},
@ -263,6 +259,10 @@
"addToken": {
"message": "添加代币"
},
"addTokenByContractAddress": {
"message": "找不到代币?您可以通过粘贴其地址手动添加任何代币。代币合约地址可以在 $1 上找到",
"description": "$1 is a blockchain explorer for a specific network, e.g. Etherscan for Ethereum"
},
"address": {
"message": "地址"
},
@ -548,13 +548,6 @@
"message": "若要$1交易燃料费用必须增加至少10%才能被网络认可。",
"description": "$1 is string 'cancel' or 'speed up'"
},
"cancelSwapForFee": {
"message": "以~$1取消兑换",
"description": "$1 could be e.g. $2.98, it is a cost for cancelling a Smart Transaction"
},
"cancelSwapForFree": {
"message": "免费取消兑换"
},
"cancelled": {
"message": "已取消"
},
@ -1200,9 +1193,6 @@
"enableOpenSeaAPIDescription": {
"message": "使用 OpenSea 的 API 获取 NFT 数据。NFT 自动检测依赖于 OpenSea 的 API在后者关闭时自动检测将不可用。"
},
"enableSmartTransactions": {
"message": "启用智能交易"
},
"enableToken": {
"message": "启用 $1",
"description": "$1 is a token symbol, e.g. ETH"
@ -3239,9 +3229,6 @@
"skipAccountSecurityDetails": {
"message": "我明白,在我备份我的账户助记词之前,我可能会丢失我的账户及其所有资产。"
},
"smartTransaction": {
"message": "智能交易"
},
"snapContent": {
"message": "此内容来自$1",
"description": "This is shown when a snap shows transaction insight information in the confirmation UI. $1 is a link to the snap's settings page with the link text being the name of the snap."
@ -3425,9 +3412,6 @@
"strong": {
"message": "强"
},
"stxAreHere": {
"message": "智能交易已推出!"
},
"stxBenefit1": {
"message": "将交易成本减至最低"
},
@ -3449,15 +3433,6 @@
"stxCancelledSubDescription": {
"message": "再次尝试进行交换。下次我们会在这里保护您免受类似风险。 "
},
"stxDescription": {
"message": "MetaMask Swap变得更加智能启用智能交易将允许MetaMask从编程上优化您的交换以帮助"
},
"stxErrorNotEnoughFunds": {
"message": "没有足够的资金进行智能交易。"
},
"stxErrorUnavailable": {
"message": "智能交易暂时不可用。"
},
"stxFailure": {
"message": "交换失败"
},
@ -3471,9 +3446,6 @@
"stxPendingPubliclySubmittingSwap": {
"message": "正在公开提交您的Swap..."
},
"stxSubDescription": {
"message": "*智能交易将尝试多次隐秘提交您的交易。如果所有尝试都失败,交易将会公开广播,以确保您的交换能成功进行。"
},
"stxSuccess": {
"message": "交换完成!"
},

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 239 KiB

View File

@ -104,7 +104,8 @@ const initialState = {
};
export default class SwapsController {
constructor({
constructor(
{
getBufferedGasLimit,
networkController,
provider,
@ -114,13 +115,23 @@ export default class SwapsController {
getCurrentChainId,
getEIP1559GasFeeEstimates,
onNetworkStateChange,
}) {
},
state,
) {
this.store = new ObservableStore({
swapsState: { ...initialState.swapsState },
swapsState: {
...initialState.swapsState,
swapsFeatureFlags: state?.swapsState?.swapsFeatureFlags || {},
},
});
this.resetState = () => {
this.store.updateState({ swapsState: { ...initialState.swapsState } });
this.store.updateState({
swapsState: {
...initialState.swapsState,
swapsFeatureFlags: state?.swapsState?.swapsFeatureFlags,
},
});
};
this._fetchTradesInfo = fetchTradesInfo;
@ -662,6 +673,7 @@ export default class SwapsController {
swapsQuoteRefreshTime: swapsState.swapsQuoteRefreshTime,
swapsQuotePrefetchingRefreshTime:
swapsState.swapsQuotePrefetchingRefreshTime,
swapsFeatureFlags: swapsState.swapsFeatureFlags,
},
});
clearTimeout(this.pollingTimeout);

View File

@ -1282,8 +1282,10 @@ export default class MetamaskController extends EventEmitter {
},
);
this.swapsController = new SwapsController({
getBufferedGasLimit: this.txController.txGasUtil.getBufferedGasLimit.bind(
this.swapsController = new SwapsController(
{
getBufferedGasLimit:
this.txController.txGasUtil.getBufferedGasLimit.bind(
this.txController.txGasUtil,
),
networkController: this.networkController,
@ -1296,8 +1298,12 @@ export default class MetamaskController extends EventEmitter {
getCurrentChainId: () =>
this.networkController.store.getState().providerConfig.chainId,
getEIP1559GasFeeEstimates:
this.gasFeeController.fetchGasFeeEstimates.bind(this.gasFeeController),
});
this.gasFeeController.fetchGasFeeEstimates.bind(
this.gasFeeController,
),
},
initState.SwapsController,
);
this.smartTransactionsController = new SmartTransactionsController(
{
onNetworkStateChange: (cb) => {

View File

@ -15,6 +15,10 @@ export const QUOTES_NOT_AVAILABLE_ERROR = 'quotes-not-avilable';
export const CONTRACT_DATA_DISABLED_ERROR = 'contract-data-disabled';
export const OFFLINE_FOR_MAINTENANCE = 'offline-for-maintenance';
export const SWAPS_FETCH_ORDER_CONFLICT = 'swaps-fetch-order-conflict';
export const SLIPPAGE_OVER_LIMIT_ERROR = 'slippage-over-limit';
export const SLIPPAGE_VERY_HIGH_ERROR = 'slippage-very-high';
export const SLIPPAGE_TOO_LOW_ERROR = 'slippage-too-low';
export const SLIPPAGE_NEGATIVE_ERROR = 'slippage-negative';
// An address that the metaswap-api recognizes as the default token for the current network,
// in place of the token address that ERC-20 tokens have

View File

@ -106,6 +106,14 @@ export const UI_NOTIFICATIONS = {
id: 20,
date: null,
},
21: {
id: 21,
date: null,
image: {
src: 'images/swaps-redesign.svg',
width: '100%',
},
},
};
export const getTranslatedUINotifications = (t, locale) => {
@ -294,5 +302,16 @@ export const getTranslatedUINotifications = (t, locale) => {
)
: '',
},
21: {
...UI_NOTIFICATIONS[21],
title: t('notifications21Title'),
description: t('notifications21Description'),
actionText: t('notifications21ActionText'),
date: UI_NOTIFICATIONS[21].date
? new Intl.DateTimeFormat(formattedLocale).format(
new Date(UI_NOTIFICATIONS[21].date),
)
: '',
},
};
};

View File

@ -136,6 +136,11 @@ function defaultFixture() {
id: 19,
isShown: true,
},
21: {
date: null,
id: 21,
isShown: true,
},
},
},
AppStateController: {

View File

@ -24,24 +24,25 @@ const loadExtension = async (driver) => {
};
const buildQuote = async (driver, options) => {
await driver.clickElement(
'.wallet-overview__buttons .icon-button:nth-child(3)',
);
await driver.fill('input[placeholder*="0"]', options.amount);
await driver.delay(veryLargeDelayMs); // Need an extra delay after typing an amount.
await driver.waitForSelector(
'[class="dropdown-input-pair dropdown-input-pair__to"]',
);
await driver.clickElement('.dropdown-input-pair__to');
await driver.clickElement('[data-testid="token-overview-button-swap"]');
await driver.fill(
'input[data-testid="search-list-items"]',
'input[data-testid="prepare-swap-page-from-token-amount"]',
options.amount,
);
await driver.delay(veryLargeDelayMs); // Need an extra delay after typing an amount.
await driver.clickElement('[data-testid="prepare-swap-page-swap-to"]');
await driver.waitForSelector('[id="list-with-search__text-search"]');
await driver.fill(
'input[id="list-with-search__text-search"]',
options.swapTo || options.swapToContractAddress,
);
await driver.delay(veryLargeDelayMs); // Need an extra delay after typing an amount.
if (options.swapTo) {
await driver.wait(async () => {
const tokenNames = await driver.findElements(
'.searchable-item-list__primary-label',
'[data-testid="searchable-item-list-primary-label"]',
);
if (tokenNames.length === 0) {
return false;
@ -51,118 +52,140 @@ const buildQuote = async (driver, options) => {
});
}
if (options.swapToContractAddress) {
await driver.waitForSelector({
css: '.searchable-item-list__item button.btn-primary',
text: 'Import',
});
await driver.waitForSelector(
'[data-testid="searchable-item-list-import-button"]',
);
}
await driver.clickElement('.searchable-item-list__primary-label');
await driver.clickElement(
'[data-testid="searchable-item-list-primary-label"]',
);
};
const reviewQuote = async (driver, options) => {
await driver.clickElement({ text: 'Review swap', tag: 'button' });
await driver.waitForSelector({ text: 'Swap', tag: 'button' });
const summary = await driver.waitForSelector(
'[data-testid="exchange-rate-display-quote-rate"]',
);
const summaryText = await summary.getText();
assert.equal(summaryText.includes(options.swapFrom), true);
assert.equal(summaryText.includes(options.swapTo), true);
const quote = summaryText.split(`\n`);
const elementSwapToAmount = await driver.findElement(
'[data-testid="prepare-swap-page-receive-amount"]',
);
const swapToAmount = await elementSwapToAmount.getText();
const expectedAmount = parseFloat(quote[3]) * options.amount;
const dotIndex = swapToAmount.indexOf('.');
const decimals = dotIndex === -1 ? 0 : swapToAmount.length - dotIndex - 1;
assert.equal(
swapToAmount,
expectedAmount.toFixed(decimals),
`Expecting ${expectedAmount.toFixed(
decimals,
)} but got ${swapToAmount} instead`,
);
await driver.findElement('[data-testid="review-quote-gas-fee-in-fiat"]');
await driver.findElement('[data-testid="info-tooltip"]');
if (!options.skipCounter) {
await driver.waitForSelector({
css: '[class*="box--align-items-center"]',
text: 'Estimated gas fee',
});
const sourceValue = await driver.waitForSelector(
'.main-quote-summary__source-row-value',
);
assert.equal(
await sourceValue.getText(),
options.amount,
'Error: Quote has wrong amount',
);
const sourceSymbol = await driver.waitForSelector(
'.main-quote-summary__source-row-symbol',
);
assert.equal(
await sourceSymbol.getText(),
options.swapFrom,
'Error: SwapFrom has wrong symbol',
);
const swapToSymbol = await driver.waitForSelector(
'.main-quote-summary__destination-row > span',
);
assert.equal(
await swapToSymbol.getText(),
options.swapTo,
'Error: SwapTo has wrong symbol',
);
await driver.waitForSelector(
'[class="exchange-rate-display main-quote-summary__exchange-rate-display"]',
);
await driver.waitForSelector('[class="fee-card__info-tooltip-container"]');
await driver.waitForSelector({
css: '[class="countdown-timer__time"]',
text: '0:23',
css: '[data-testid="countdown-timer__timer-container"]',
text: '0:25',
});
}
};
const waitForTransactionToComplete = async (driver, tokenName) => {
const sucessfulTransactionMessage = await driver.waitForSelector(
const waitForTransactionToComplete = async (driver, options) => {
await driver.waitForSelector({
css: '[data-testid="awaiting-swap-header"]',
text: 'Processing',
});
await driver.waitForSelector(
{
css: '[class="awaiting-swap__header"]',
css: '[data-testid="awaiting-swap-header"]',
text: 'Transaction complete',
},
{ timeout: 30000 },
);
assert.equal(
await sucessfulTransactionMessage.getText(),
'Transaction complete',
'Incorrect transaction message',
);
const sucessfulTransactionToken = await driver.waitForSelector({
css: '[class="awaiting-swap__amount-and-symbol"]',
text: tokenName,
await driver.findElement({
css: '[data-testid="awaiting-swap-main-description"]',
text: `${options.tokenName}`,
});
assert.equal(
await sucessfulTransactionToken.getText(),
tokenName,
'Incorrect token name',
);
await driver.clickElement({ text: 'Close', tag: 'button' });
await driver.waitForSelector('[data-testid="home__asset-tab"]');
};
const checkActivityTransaction = async (driver, options) => {
await driver.clickElement('[data-testid="home__activity-tab"]');
const itemsText = await driver.findElements('.list-item__title');
await driver.waitForSelector('[data-testid="list-item-title"]');
const transactionList = await driver.findElements(
'[data-testid="list-item-title"]',
);
const transactionText = await transactionList[options.index].getText();
assert.equal(
await itemsText[options.index].getText(),
transactionText,
`Swap ${options.swapFrom} to ${options.swapTo}`,
'Title is incorrect',
'Transaction not found',
);
const amountValues = await driver.findElements(
'.transaction-list-item__primary-currency',
);
assert.equal(
await amountValues[options.index].getText(),
`-${options.amount} ${options.swapFrom}`,
'Transaction amount is incorrect',
);
await itemsText[options.index].click();
await driver.findElement({
css: '[data-testid="list-item-right-content"]',
text: `-${options.amount} ${options.swapFrom}`,
});
await transactionList[options.index].click();
await driver.delay(regularDelayMs);
const txStatus = await driver.findElement(
'.transaction-list-item-details__tx-status >div > div:last-child',
);
assert.equal(
await txStatus.getText(),
`Confirmed`,
`Transaction status is not 'Confirmed'`,
);
const txAmount = await driver.findElement(
'.transaction-breakdown__value--amount',
);
assert.equal(
await txAmount.getText(),
`-${options.amount} ${options.swapFrom}`,
'Transaction breakdown is incorrect',
);
await driver.findElement({
css: '[data-testid="transaction-list-item-details-tx-status"]',
text: `Confirmed`,
});
await driver.findElement({
css: '[data-testid="transaction-breakdown-value-amount"]',
text: `-${options.amount} ${options.swapFrom}`,
});
await driver.clickElement('[data-testid="popover-close"]');
};
const checkNotification = async (driver, options) => {
const boxTitle = await driver.findElement(
'[data-testid="mm-banner-base-title"]',
);
assert.equal(await boxTitle.getText(), options.title, 'Invalid box title');
const boxContent = await driver.findElement(
'[data-testid="mm-banner-alert-notification-text"]',
);
const bodyText = await boxContent.getText();
console.log(`test: ${bodyText}`);
assert.equal(
bodyText.includes(options.text),
true,
'Invalid box text content',
);
};
const changeExchangeRate = async (driver) => {
await driver.clickElement(
'[data-testid="exchange-rate-display-base-symbol"]',
);
await driver.waitForSelector({ text: 'Quote details', tag: 'h2' });
const networkFees = await driver.findElements(
'[data-testid*="select-quote-popover-row"]',
);
const random = Math.floor(Math.random() * networkFees.length);
await networkFees[random].click();
await driver.clickElement({ text: 'Select', tag: 'button' });
};
module.exports = {
withFixturesOptions,
loadExtension,
@ -170,4 +193,6 @@ module.exports = {
reviewQuote,
waitForTransactionToComplete,
checkActivityTransaction,
checkNotification,
changeExchangeRate,
};

View File

@ -6,6 +6,7 @@ const {
reviewQuote,
waitForTransactionToComplete,
checkActivityTransaction,
changeExchangeRate,
} = require('./shared');
describe('Swap Eth for another Token', function () {
@ -25,7 +26,7 @@ describe('Swap Eth for another Token', function () {
swapTo: 'USDC',
});
await reviewQuote(driver, {
amount: '0.001',
amount: 0.001,
swapFrom: 'TESTETH',
swapTo: 'USDC',
});
@ -36,12 +37,12 @@ describe('Swap Eth for another Token', function () {
swapTo: 'DAI',
});
await reviewQuote(driver, {
amount: '0.003',
amount: 0.003,
swapFrom: 'TESTETH',
swapTo: 'DAI',
});
await driver.clickElement({ text: 'Swap', tag: 'button' });
await waitForTransactionToComplete(driver, 'DAI');
await waitForTransactionToComplete(driver, { tokenName: 'DAI' });
await checkActivityTransaction(driver, {
index: 0,
amount: '0.003',
@ -57,7 +58,7 @@ describe('Swap Eth for another Token', function () {
},
);
});
it('Completes a Swap between Eth and Dai', async function () {
it('Completes a Swap between ETH and DAI after changing initial rate', async function () {
await withFixtures(
{
...withFixturesOptions,
@ -70,12 +71,19 @@ describe('Swap Eth for another Token', function () {
swapTo: 'DAI',
});
await reviewQuote(driver, {
amount: '2',
amount: 2,
swapFrom: 'TESTETH',
swapTo: 'DAI',
});
await changeExchangeRate(driver);
await reviewQuote(driver, {
amount: 2,
swapFrom: 'TESTETH',
swapTo: 'DAI',
skipCounter: true,
});
await driver.clickElement({ text: 'Swap', tag: 'button' });
await waitForTransactionToComplete(driver, 'DAI');
await waitForTransactionToComplete(driver, { tokenName: 'DAI' });
await checkActivityTransaction(driver, {
index: 0,
amount: '2',

View File

@ -1,8 +1,12 @@
const { strict: assert } = require('assert');
const { withFixtures } = require('../helpers');
const { withFixturesOptions, loadExtension, buildQuote } = require('./shared');
const {
withFixturesOptions,
loadExtension,
buildQuote,
reviewQuote,
checkNotification,
} = require('./shared');
describe('Swaps - notifications', function () {
async function mockTradesApiPriceSlippageError(mockServer) {
@ -65,33 +69,34 @@ describe('Swaps - notifications', function () {
amount: 2,
swapTo: 'INUINU',
});
const reviewSwapButton = await driver.findElement(
'[data-testid="page-container-footer-next"]',
);
assert.equal(await reviewSwapButton.getText(), 'Review swap');
assert.equal(await reviewSwapButton.isEnabled(), false);
const continueButton = await driver.findClickableElement(
'.actionable-message__action-warning',
);
assert.equal(await continueButton.getText(), 'Continue');
await continueButton.click();
assert.equal(await reviewSwapButton.isEnabled(), true);
await reviewSwapButton.click();
await driver.waitForSelector({
css: '[class*="box--align-items-center"]',
text: 'Estimated gas fee',
await checkNotification(driver, {
title: 'Potentially inauthentic token',
text: 'INUINU is only verified on 1 source. Consider verifying it on Etherscan before proceeding.',
});
await driver.clickElement({ text: 'Continue swapping', tag: 'button' });
await driver.waitForSelector({
text: 'Swap',
tag: 'button',
});
await checkNotification(driver, {
title: 'Check your rate before proceeding',
text: 'Price impact could not be determined due to lack of market price data.',
});
await driver.clickElement({ text: 'Swap anyway', tag: 'button' });
await reviewQuote(driver, {
amount: 2,
swapFrom: 'TESTETH',
swapTo: 'INUINU',
skipCounter: true,
});
const swapButton = await driver.findElement({
text: 'Swap',
tag: 'button',
});
const swapButton = await driver.findElement(
'[data-testid="page-container-footer-next"]',
);
assert.equal(await swapButton.isEnabled(), false);
await driver.clickElement({ text: 'I understand', tag: 'button' });
assert.equal(await swapButton.getText(), 'Swap');
assert.equal(await swapButton.isEnabled(), true);
},
);
});
it('tests a notification for not enough balance', async function () {
await withFixtures(
{
@ -104,30 +109,26 @@ describe('Swaps - notifications', function () {
amount: 50,
swapTo: 'USDC',
});
const reviewSwapButton = await driver.findElement(
'[data-testid="page-container-footer-next"]',
);
assert.equal(await reviewSwapButton.getText(), 'Review swap');
assert.equal(await reviewSwapButton.isEnabled(), true);
await reviewSwapButton.click();
await driver.waitForSelector({
css: '[class*="box--align-items-center"]',
text: 'Estimated gas fee',
await checkNotification(driver, {
title: 'Insufficient balance',
text: 'You need 50 more TESTETH to complete this swap',
});
await driver.waitForSelector({
css: '[class*="actionable-message__message"]',
text: 'You need 43.4467 more TESTETH to complete this swap',
await reviewQuote(driver, {
swapFrom: 'TESTETH',
swapTo: 'USDC',
amount: 50,
skipCounter: true,
});
const swapButton = await driver.waitForSelector({
text: 'Swap',
tag: 'button',
});
const swapButton = await driver.findElement(
'[data-testid="page-container-footer-next"]',
);
assert.equal(await swapButton.getText(), 'Swap');
assert.equal(await swapButton.isEnabled(), false);
},
);
});
it('tests notifications for verified token on 0 sources and high slippage', async function () {
it('tests notifications for token import', async function () {
await withFixtures(
{
...withFixturesOptions,
@ -139,39 +140,51 @@ describe('Swaps - notifications', function () {
amount: 2,
swapToContractAddress: '0x72c9Fb7ED19D3ce51cea5C56B3e023cd918baaDf',
});
await driver.waitForSelector({
css: '.popover-header',
text: 'Import token?',
});
await driver.clickElement(
'[data-testid="page-container__import-button"]',
);
const reviewSwapButton = await driver.findElement(
'[data-testid="page-container-footer-next"]',
);
assert.equal(await reviewSwapButton.isEnabled(), false);
const continueButton = await driver.findClickableElement(
'.actionable-message__action-danger',
);
assert.equal(await continueButton.getText(), 'Continue');
await continueButton.click();
assert.equal(await reviewSwapButton.isEnabled(), true);
await driver.clickElement('[class="slippage-buttons__header-text"]');
await driver.clickElement({ text: 'custom', tag: 'button' });
await driver.fill(
'input[data-testid="slippage-buttons__custom-slippage"]',
'20',
);
await driver.waitForSelector({
css: '[class*="slippage-buttons__error-text"]',
text: 'Slippage amount is too high and will result in a bad rate. Please reduce your slippage tolerance to a value below 15%.',
await checkNotification(driver, {
title: 'Token added manually',
text: 'Verify this token on Etherscan and make sure it is the token you want to trade.',
});
assert.equal(await reviewSwapButton.isEnabled(), false);
await driver.fill(
'input[data-testid="slippage-buttons__custom-slippage"]',
'4',
},
);
assert.equal(await reviewSwapButton.isEnabled(), true);
});
it('tests notifications for slippage', async function () {
await withFixtures(
{
...withFixturesOptions,
title: this.test.title,
},
async ({ driver }) => {
await loadExtension(driver);
await buildQuote(driver, {
amount: '.0001',
swapTo: 'DAI',
});
await driver.clickElement('[title="Transaction settings"]');
await driver.clickElement({ text: 'custom', tag: 'button' });
await driver.fill('input[data-testid*="slippage"]', '0');
await checkNotification(driver, {
title: 'Sourcing zero-slippage providers',
text: 'There are fewer zero-slippage quote providers which will result in a less competitive quote.',
});
await driver.fill('input[data-testid*="slippage"]', '1');
await checkNotification(driver, {
title: 'Increase slippage to avoid transaction failure',
text: 'Max slippage is too low which may cause your transaction to fail.',
});
await driver.fill('input[data-testid*="slippage"]', '15');
await checkNotification(driver, {
title: 'Very high slippage',
text: 'The slippage entered is considered very high and may result in a bad rate',
});
await driver.fill('input[data-testid*="slippage"]', '20');
await checkNotification(driver, {
title: 'Reduce slippage to continue',
text: 'Slippage tolerance must be 15% or less. Anything higher will result in a bad rate.',
});
await driver.fill('input[data-testid*="slippage"]', '4');
},
);
});

View File

@ -252,6 +252,7 @@ export const createSwapsMockStore = () => {
},
},
selectedAddress: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
currentLocale: 'en',
keyringTypes: [KeyringType.imported, KeyringType.hdKeyTree],
keyrings: [
{
@ -288,6 +289,10 @@ export const createSwapsMockStore = () => {
mobileActive: true,
extensionActive: true,
},
swapRedesign: {
mobileActive: true,
extensionActive: true,
},
},
quotes: {
TEST_AGG_1: {

View File

@ -28,6 +28,7 @@ exports[`ConfirmGasDisplay should match snapshot 1`] = `
</span>
<div
class="info-tooltip"
data-testid="info-tooltip"
>
<div>
<div

View File

@ -14,6 +14,7 @@ exports[`ConfirmLegacyGasDisplay should match snapshot 1`] = `
Estimated gas fee
<div
class="info-tooltip"
data-testid="info-tooltip"
>
<div>
<div

View File

@ -72,7 +72,10 @@ export default class TransactionBreakdown extends PureComponent {
<TransactionBreakdownRow
title={isTokenApprove ? t('spendingCap') : t('amount')}
>
<span className="transaction-breakdown__value transaction-breakdown__value--amount">
<span
className="transaction-breakdown__value transaction-breakdown__value--amount"
data-testid="transaction-breakdown-value-amount"
>
{primaryCurrency}
</span>
</TransactionBreakdownRow>

View File

@ -253,7 +253,10 @@ export default class TransactionListItemDetails extends PureComponent {
</div>
</div>
<div className="transaction-list-item-details__header">
<div className="transaction-list-item-details__tx-status">
<div
className="transaction-list-item-details__tx-status"
data-testid="transaction-list-item-details-tx-status"
>
<div>{t('status')}</div>
<div>
<TransactionStatus />

View File

@ -302,6 +302,7 @@ const EthOverview = ({ className }) => {
}
}}
label={t('swap')}
data-testid="token-overview-button-swap"
tooltipRender={
isSwapsChain
? null

View File

@ -15,6 +15,7 @@ import { getTranslatedUINotifications } from '../../../../shared/notifications';
import { getSortedAnnouncementsToShow } from '../../../selectors';
import {
BUILD_QUOTE_ROUTE,
PREPARE_SWAP_ROUTE,
ADVANCED_ROUTE,
EXPERIMENTAL_ROUTE,
SECURITY_ROUTE,
@ -85,6 +86,10 @@ function getActionFunctionById(id, history) {
url: ZENDESK_URLS.LEDGER_FIREFOX_U2F_GUIDE,
});
},
21: () => {
updateViewedNotifications({ 21: true });
history.push(PREPARE_SWAP_ROUTE);
},
};
return actionFunctions[id];
@ -318,7 +323,7 @@ export default function WhatsNewPopup({ onClose }) {
const isLast = index === notifications.length - 1;
// Display the swaps notification with full image
// Displays the NFTs & OpenSea notifications 18,19 with full image
return index === 0 || id === 1 || id === 18 || id === 19
return index === 0 || id === 1 || id === 18 || id === 19 || id === 21
? renderFirstNotification(
notification,
idRefMap,

View File

@ -13,6 +13,7 @@ exports[`BannerAlert should render BannerAlert element correctly 1`] = `
<div>
<h5
class="box mm-text mm-banner-base__title mm-text--body-lg-medium box--flex-direction-row box--color-text-default"
data-testid="mm-banner-base-title"
>
BannerAlert test
</h5>

View File

@ -9,6 +9,7 @@ exports[`BannerBase should render bannerbase element correctly 1`] = `
<div>
<h5
class="box mm-text mm-banner-base__title mm-text--body-lg-medium box--flex-direction-row box--color-text-default"
data-testid="mm-banner-base-title"
>
Bannerbase test
</h5>

View File

@ -46,6 +46,7 @@ export const BannerBase = ({
<Text
className="mm-banner-base__title"
variant={TextVariant.bodyLgMedium}
data-testid="mm-banner-base-title"
as="h5"
{...titleProps}
>

View File

@ -18,6 +18,7 @@ exports[`BannerTip should render BannerTip element correctly 1`] = `
<div>
<h5
class="box mm-text mm-banner-base__title mm-text--body-lg-medium box--flex-direction-row box--color-text-default"
data-testid="mm-banner-base-title"
>
BannerTip test
</h5>

View File

@ -38,6 +38,7 @@ export const TextField = ({
readOnly,
required,
size = Size.MD,
testId,
type = 'text',
truncate = true,
value,
@ -116,6 +117,7 @@ export const TextField = ({
autoComplete={autoComplete}
autoFocus={autoFocus}
backgroundColor={BackgroundColor.transparent}
data-testid={testId}
defaultValue={defaultValue}
disabled={disabled}
focused={focused.toString()}
@ -249,6 +251,10 @@ TextField.propTypes = {
* The input value, required for a controlled component.
*/
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/**
* Data test ID for the InputComponent component
*/
testId: PropTypes.string,
/**
* TextField accepts all the props from Box
*/

View File

@ -32,6 +32,7 @@ exports[`ComplianceDetails should render correctly 1`] = `
</p>
<div
class="info-tooltip"
data-testid="info-tooltip"
>
<div>
<div
@ -78,6 +79,7 @@ exports[`ComplianceDetails should render correctly 1`] = `
</p>
<div
class="info-tooltip"
data-testid="info-tooltip"
>
<div>
<div

View File

@ -19,7 +19,7 @@ export default function InfoTooltip({
iconFillColor = 'var(--color-icon-alternative)',
}) {
return (
<div className="info-tooltip">
<div className="info-tooltip" data-testid="info-tooltip">
<Tooltip
interactive
position={position}

View File

@ -12,6 +12,7 @@ exports[`ListItem should match snapshot with no props 1`] = `
>
<h2
class="list-item__title"
data-testid="list-item-title"
/>
</div>
</div>
@ -55,6 +56,7 @@ exports[`ListItem should match snapshot with props 1`] = `
>
<h2
class="list-item__title"
data-testid="list-item-title"
>
Hello World
</h2>
@ -125,6 +127,7 @@ exports[`ListItem should match snapshot with props 1`] = `
</div>
<div
class="list-item__right-content"
data-testid="list-item-right-content"
>
<p>
Content rendered to the right

View File

@ -38,7 +38,9 @@ export default function ListItem({
{React.isValidElement(title) ? (
title
) : (
<h2 className="list-item__title">{title}</h2>
<h2 className="list-item__title" data-testid="list-item-title">
{title}
</h2>
)}
{titleIcon && (
<div className="list-item__heading-wrap">{titleIcon}</div>
@ -52,7 +54,12 @@ export default function ListItem({
<div className="list-item__mid-content">{midContent}</div>
) : null}
{rightContent ? (
<div className="list-item__right-content">{rightContent}</div>
<div
className="list-item__right-content"
data-testid="list-item-right-content"
>
{rightContent}
</div>
) : null}
</div>
);

View File

@ -80,6 +80,7 @@ export default function Typography({
marginLeft,
boxProps = {},
className,
testId,
children,
}) {
let Tag = as ?? variant;
@ -123,6 +124,7 @@ export default function Typography({
<Tag
className={classnames(boxClassName, computedClassName)}
title={title}
data-testid={testId}
>
{children}
</Tag>
@ -188,6 +190,10 @@ Typography.propTypes = {
* Title attribute to include on the element. Will show as tooltip on hover.
*/
title: PropTypes.string,
/**
* Data test ID for the Tag component
*/
testId: PropTypes.string,
/**
* The text content of the Typography component
*/

View File

@ -120,6 +120,7 @@ const initialState = {
},
currentSmartTransactionsError: '',
swapsSTXLoading: false,
transactionSettingsOpened: false,
};
const slice = createSlice({
@ -208,6 +209,9 @@ const slice = createSlice({
setSwapsSTXSubmitLoading: (state, action) => {
state.swapsSTXLoading = action.payload || false;
},
setTransactionSettingsOpened: (state, action) => {
state.transactionSettingsOpened = Boolean(action.payload);
},
},
});
@ -268,6 +272,9 @@ export const getSwapsFallbackGasPrice = (state) =>
export const getCurrentSmartTransactionsError = (state) =>
state.swaps.currentSmartTransactionsError;
export const getTransactionSettingsOpened = (state) =>
state.swaps.transactionSettingsOpened;
export function shouldShowCustomPriceTooLowWarning(state) {
const { average } = getSwapGasPriceEstimateData(state);
@ -325,6 +332,15 @@ export const getCurrentSmartTransactionsEnabled = (state) => {
return smartTransactionsEnabled && !currentSmartTransactionsError;
};
export const getSwapRedesignEnabled = (state) => {
const swapRedesign =
state.metamask.swapsState?.swapsFeatureFlags?.swapRedesign;
if (swapRedesign === undefined) {
return true; // By default show the redesign if we don't have feature flags returned yet.
}
return swapRedesign.extensionActive;
};
export const getSwapsQuoteRefreshTime = (state) =>
state.metamask.swapsState.swapsQuoteRefreshTime;
@ -478,6 +494,7 @@ const {
swapCustomGasModalClosed,
setCurrentSmartTransactionsError,
setSwapsSTXSubmitLoading,
setTransactionSettingsOpened,
} = actions;
export {
@ -497,6 +514,7 @@ export {
swapCustomGasModalPriceEdited,
swapCustomGasModalLimitEdited,
swapCustomGasModalClosed,
setTransactionSettingsOpened,
};
export const navigateBackToBuildQuote = (history) => {
@ -647,12 +665,7 @@ export const fetchQuotesAndSetQuoteState = (
iconUrl: fromTokenIconUrl,
balance: fromTokenBalance,
} = selectedFromToken;
const {
address: toTokenAddress,
symbol: toTokenSymbol,
decimals: toTokenDecimals,
iconUrl: toTokenIconUrl,
} = selectedToToken;
const { address: toTokenAddress, symbol: toTokenSymbol } = 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) {
@ -663,24 +676,6 @@ export const fetchQuotesAndSetQuoteState = (
const contractExchangeRates = getTokenExchangeRates(state);
let destinationTokenAddedForSwap = false;
if (
toTokenAddress &&
toTokenSymbol !== swapsDefaultToken.symbol &&
contractExchangeRates[toTokenAddress] === undefined &&
!isTokenAlreadyAdded(toTokenAddress, getTokens(state))
) {
destinationTokenAddedForSwap = true;
await dispatch(
addToken(
toTokenAddress,
toTokenSymbol,
toTokenDecimals,
toTokenIconUrl,
true,
),
);
}
if (
fromTokenAddress &&
fromTokenSymbol !== swapsDefaultToken.symbol &&
@ -749,7 +744,6 @@ export const fetchQuotesAndSetQuoteState = (
destinationToken: toTokenAddress,
value: inputValue,
fromAddress: selectedAccount.address,
destinationTokenAddedForSwap,
balanceError,
sourceDecimals: fromTokenDecimals,
},
@ -837,6 +831,36 @@ export const fetchQuotesAndSetQuoteState = (
};
};
const addTokenTo = (dispatch, state) => {
const fetchParams = getFetchParams(state);
const swapsDefaultToken = getSwapsDefaultToken(state);
const contractExchangeRates = getTokenExchangeRates(state);
const selectedToToken =
getToToken(state) || fetchParams?.metaData?.destinationTokenInfo || {};
const {
address: toTokenAddress,
symbol: toTokenSymbol,
decimals: toTokenDecimals,
iconUrl: toTokenIconUrl,
} = selectedToToken;
if (
toTokenAddress &&
toTokenSymbol !== swapsDefaultToken.symbol &&
contractExchangeRates[toTokenAddress] === undefined &&
!isTokenAlreadyAdded(toTokenAddress, getTokens(state))
) {
dispatch(
addToken(
toTokenAddress,
toTokenSymbol,
toTokenDecimals,
toTokenIconUrl,
true,
),
);
}
};
export const signAndSendSwapsSmartTransaction = ({
unsignedTransaction,
trackEvent,
@ -936,6 +960,7 @@ export const signAndSendSwapsSmartTransaction = ({
dispatch(setCurrentSmartTransactionsError(StxErrorTypes.unavailable));
return;
}
addTokenTo(dispatch, state);
if (approveTxParams) {
updatedApproveTxParams.gas = `0x${decimalToHex(
fees.approvalTxFees?.gasLimit || 0,
@ -1179,6 +1204,7 @@ export const signAndSendTransactions = (
history.push(AWAITING_SIGNATURES_ROUTE);
}
addTokenTo(dispatch, state);
if (approveTxParams) {
if (networkAndAccountSupports1559) {
approveTxParams.maxFeePerGas = maxFeePerGas;

View File

@ -956,5 +956,24 @@ describe('Ducks - Swaps', () => {
expect(newState.customGas.limit).toBe(null);
});
});
describe('getSwapRedesignEnabled', () => {
it('returns true if feature flags are not returned from backend yet', () => {
const state = createSwapsMockStore();
delete state.metamask.swapsState.swapsFeatureFlags.swapRedesign;
expect(swaps.getSwapRedesignEnabled(state)).toBe(true);
});
it('returns false if the extension feature flag for swaps redesign is false', () => {
const state = createSwapsMockStore();
state.metamask.swapsState.swapsFeatureFlags.swapRedesign.extensionActive = false;
expect(swaps.getSwapRedesignEnabled(state)).toBe(false);
});
it('returns true if the extension feature flag for swaps redesign is true', () => {
const state = createSwapsMockStore();
expect(swaps.getSwapRedesignEnabled(state)).toBe(true);
});
});
});
});

View File

@ -52,6 +52,8 @@ const NOTIFICATIONS_ROUTE = '/notifications';
const CONNECTED_ROUTE = '/connected';
const CONNECTED_ACCOUNTS_ROUTE = '/connected/accounts';
const SWAPS_ROUTE = '/swaps';
const PREPARE_SWAP_ROUTE = '/swaps/prepare-swap-page';
const SWAPS_NOTIFICATION_ROUTE = '/swaps/notification-page';
const BUILD_QUOTE_ROUTE = '/swaps/build-quote';
const VIEW_QUOTE_ROUTE = '/swaps/view-quote';
const LOADING_QUOTES_ROUTE = '/swaps/loading-quotes';
@ -174,6 +176,8 @@ const PATH_NAME_MAP = {
[`${CONFIRM_TRANSACTION_ROUTE}/:id${ENCRYPTION_PUBLIC_KEY_REQUEST_PATH}`]:
'Encryption Public Key Request Page',
[BUILD_QUOTE_ROUTE]: 'Swaps Build Quote Page',
[PREPARE_SWAP_ROUTE]: 'Prepare Swap Page',
[SWAPS_NOTIFICATION_ROUTE]: 'Swaps Notification Page',
[VIEW_QUOTE_ROUTE]: 'Swaps View Quotes Page',
[LOADING_QUOTES_ROUTE]: 'Swaps Loading Quotes Page',
[AWAITING_SWAP_ROUTE]: 'Swaps Awaiting Swaps Page',
@ -246,6 +250,8 @@ export {
CONNECTED_ACCOUNTS_ROUTE,
PATH_NAME_MAP,
SWAPS_ROUTE,
PREPARE_SWAP_ROUTE,
SWAPS_NOTIFICATION_ROUTE,
BUILD_QUOTE_ROUTE,
VIEW_QUOTE_ROUTE,
LOADING_QUOTES_ROUTE,

View File

@ -358,6 +358,7 @@ exports[`ConfirmSendEther should render correct information for for confirm send
</button>
<div
class="info-tooltip"
data-testid="info-tooltip"
>
<div>
<div
@ -411,6 +412,7 @@ exports[`ConfirmSendEther should render correct information for for confirm send
</span>
<div
class="info-tooltip"
data-testid="info-tooltip"
>
<div>
<div

View File

@ -345,6 +345,7 @@ exports[`Confirm Transaction Base should match snapshot 1`] = `
Estimated gas fee
<div
class="info-tooltip"
data-testid="info-tooltip"
>
<div>
<div

View File

@ -119,6 +119,7 @@ exports[`Add Network Modal should render 1`] = `
<div
class="info-tooltip"
data-testid="info-tooltip"
>
<div>
<div

View File

@ -205,6 +205,7 @@ exports[`SendContent Component render should match snapshot 1`] = `
</span>
<div
class="info-tooltip"
data-testid="info-tooltip"
>
<div>
<div

View File

@ -3,12 +3,14 @@
exports[`AwaitingSwap renders the component with initial props 1`] = `
<div
class="awaiting-swap__main-description"
data-testid="awaiting-swap-main-description"
>
<span>
Your
<span
class="awaiting-swap__amount-and-symbol"
data-testid="awaiting-swap-amount-and-symbol"
>
USDC
</span>

View File

@ -230,6 +230,7 @@ export default function AwaitingSwap({
<span
key="swapOnceTransactionHasProcess-1"
className="awaiting-swap__amount-and-symbol"
data-testid="awaiting-swap-amount-and-symbol"
>
{swapMetaData?.token_to}
</span>,
@ -278,8 +279,18 @@ export default function AwaitingSwap({
/>
)}
<div className="awaiting-swap__status-image">{statusImage}</div>
<div className="awaiting-swap__header">{headerText}</div>
<div className="awaiting-swap__main-description">{descriptionText}</div>
<div
className="awaiting-swap__header"
data-testid="awaiting-swap-header"
>
{headerText}
</div>
<div
className="awaiting-swap__main-description"
data-testid="awaiting-swap-main-description"
>
{descriptionText}
</div>
{content}
</div>
{!errorKey && swapComplete ? (

View File

@ -18,17 +18,7 @@ import DropdownSearchList from '../dropdown-search-list';
import SlippageButtons from '../slippage-buttons';
import { getTokens, getConversionRate } from '../../../ducks/metamask/metamask';
import InfoTooltip from '../../../components/ui/info-tooltip';
import Popover from '../../../components/ui/popover';
import Button from '../../../components/ui/button';
import ActionableMessage from '../../../components/ui/actionable-message/actionable-message';
import Box from '../../../components/ui/box';
import {
TextVariant,
DISPLAY,
FLEX_DIRECTION,
FontWeight,
TextColor,
} from '../../../helpers/constants/design-system';
import {
VIEW_QUOTE_ROUTE,
LOADING_QUOTES_ROUTE,
@ -94,7 +84,6 @@ import {
import {
resetSwapsPostFetchState,
ignoreTokens,
setBackgroundSwapRouteState,
clearSwapsQuotes,
stopPollingForQuotes,
@ -111,7 +100,7 @@ import {
getValueFromWeiHex,
hexToDecimal,
} from '../../../../shared/modules/conversion.utils';
import { Text } from '../../../components/component-library';
import SmartTransactionsPopover from '../prepare-swap-page/smart-transactions-popover';
const fuseSearchKeys = [
{ name: 'name', weight: 0.499 },
@ -358,22 +347,12 @@ export default function BuildQuote({
? getURLHostName(blockExplorerTokenLink)
: t('etherscan');
const { destinationTokenAddedForSwap } = fetchParams || {};
const { address: toAddress } = toToken || {};
const onToSelect = useCallback(
(token) => {
if (destinationTokenAddedForSwap && token.address !== toAddress) {
dispatch(
ignoreTokens({
tokensToIgnore: toAddress,
dontShowLoadingIndicator: true,
}),
);
}
dispatch(setSwapToToken(token));
setVerificationClicked(false);
},
[dispatch, destinationTokenAddedForSwap, toAddress],
[dispatch],
);
const hideDropdownItemIf = useCallback(
@ -585,82 +564,12 @@ export default function BuildQuote({
<div className="build-quote">
<div className="build-quote__content">
{showSmartTransactionsOptInPopover && (
<Popover
title={t('stxAreHere')}
footer={
<>
<Button type="primary" onClick={onEnableSmartTransactionsClick}>
{t('enableSmartTransactions')}
</Button>
<Box marginTop={1}>
<Text variant={TextVariant.bodySm} as="h6">
<Button
type="link"
onClick={onCloseSmartTransactionsOptInPopover}
className="smart-transactions-popover__no-thanks-link"
>
{t('noThanksVariant2')}
</Button>
</Text>
</Box>
</>
<SmartTransactionsPopover
onEnableSmartTransactionsClick={onEnableSmartTransactionsClick}
onCloseSmartTransactionsOptInPopover={
onCloseSmartTransactionsOptInPopover
}
footerClassName="smart-transactions-popover__footer"
className="smart-transactions-popover"
>
<Box
paddingRight={6}
paddingLeft={6}
paddingTop={0}
paddingBottom={0}
display={DISPLAY.FLEX}
className="smart-transactions-popover__content"
>
<Box
marginTop={0}
marginBottom={4}
display={DISPLAY.FLEX}
flexDirection={FLEX_DIRECTION.COLUMN}
>
<img
src="./images/logo/smart-transactions-header.png"
alt={t('swapSwapSwitch')}
/>
</Box>
<Text variant={TextVariant.bodySm} as="h6" marginTop={0}>
{t('stxDescription')}
</Text>
<Text
as="ul"
variant={TextVariant.bodySm}
fontWeight={FontWeight.Bold}
marginTop={3}
>
<li>{t('stxBenefit1')}</li>
<li>{t('stxBenefit2')}</li>
<li>{t('stxBenefit3')}</li>
<li>
{t('stxBenefit4')}
<Text
as="span"
fontWeight={FontWeight.Normal}
variant={TextVariant.bodySm}
>
{' *'}
</Text>
</li>
</Text>
<Text
variant={TextVariant.bodyXs}
as="h6"
color={TextColor.textAlternative}
marginTop={3}
>
{t('stxSubDescription')}&nbsp;
<strong>{t('stxYouCanOptOut')}&nbsp;</strong>
</Text>
</Box>
</Popover>
)}
<div className="build-quote__dropdown-input-pair-header">
<div className="build-quote__input-label">{t('swapSwapFrom')}</div>

View File

@ -29,7 +29,6 @@ const createProps = (customProps = {}) => {
setBackgroundConnection({
resetPostFetchState: jest.fn(),
ignoreTokens: jest.fn(),
setBackgroundSwapRouteState: jest.fn(),
clearSwapsQuotes: jest.fn(),
stopPollingForQuotes: jest.fn(),

View File

@ -10,7 +10,6 @@ import {
getSwapsQuotePrefetchingRefreshTime,
} from '../../../ducks/swaps/swaps';
import { SECOND } from '../../../../shared/constants/time';
import TimerIcon from './timer-icon';
// Return the mm:ss start time of the countdown timer.
// If time has elapsed between `timeStarted` the time current time,
@ -111,7 +110,6 @@ export default function CountdownTimer({
warningTime && timeBelowWarningTime(timer, warningTime),
})}
>
<TimerIcon />
{time}
</div>
{!timeOnly && infoTooltipLabelKey ? (

View File

@ -11,6 +11,7 @@
&__timer-container {
display: flex;
padding-right: 3px;
color: var(--color-text-alternative);
> span {
display: flex;

View File

@ -36,8 +36,9 @@ exports[`DropdownSearchList renders the component with initial props 1`] = `
</div>
</div>
</div>
<i
class="fa fa-caret-down fa-lg dropdown-search-list__caret"
<span
class="box mm-icon mm-icon--size-xs box--margin-right-3 box--display-inline-block box--flex-direction-row box--color-inherit"
style="mask-image: url('./images/icons/arrow-down.svg');"
/>
</div>
</div>

View File

@ -13,6 +13,11 @@ import { I18nContext } from '../../../contexts/i18n';
import SearchableItemList from '../searchable-item-list';
import PulseLoader from '../../../components/ui/pulse-loader';
import UrlIcon from '../../../components/ui/url-icon';
import {
Icon,
IconName,
IconSize,
} from '../../../components/component-library';
import ActionableMessage from '../../../components/ui/actionable-message/actionable-message';
import ImportToken from '../import-token';
import {
@ -210,7 +215,7 @@ export default function DropdownSearchList({
</div>
</div>
</div>
<i className="fa fa-caret-down fa-lg dropdown-search-list__caret" />
<Icon name={IconName.ArrowDown} size={IconSize.Xs} marginRight={3} />
</div>
)}
{isOpen && (
@ -238,7 +243,7 @@ export default function DropdownSearchList({
key="searchable-item-list-item-last"
>
<ActionableMessage
message={t('addCustomTokenByContractAddress', [
message={t('addTokenByContractAddress', [
<a
key="dropdown-search-list__etherscan-link"
onClick={() => {

View File

@ -51,7 +51,7 @@
&__caret {
position: absolute;
right: 16px;
color: var(--color-icon-muted);
color: var(--color-icon-default);
}
&__selector-closed {
@ -71,7 +71,7 @@
}
.dropdown-search-list__item-labels {
width: 56%;
width: 100%;
}
}

View File

@ -4,13 +4,17 @@ exports[`ExchangeRateDisplay renders the component with initial props 1`] = `
<div>
<div
class="exchange-rate-display"
>
<div
class="box exchange-rate-display__quote-rate box--display-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-primary-default"
data-testid="exchange-rate-display-quote-rate"
>
<span>
1
</span>
<span
class="exchange-rate-display__bold"
data-testid="exchange-rate-display__base-symbol"
data-testid="exchange-rate-display-base-symbol"
>
ETH
</span>
@ -25,23 +29,13 @@ exports[`ExchangeRateDisplay renders the component with initial props 1`] = `
>
BAT
</span>
<div
class="exchange-rate-display__switch-arrows"
data-testid="exchange-rate-display__switch-arrows"
>
<svg
fill="none"
height="13"
viewBox="0 0 13 13"
width="13"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.15294 4.38514H9.99223L8.50853 5.86884C8.30421 6.07297 8.30421 6.40418 8.50853 6.60869C8.61069 6.71085 8.74443 6.76203 8.87836 6.76203C9.01229 6.76203 9.14603 6.71085 9.24819 6.60869L11.6249 4.23219C11.649 4.20803 11.6707 4.1814 11.6899 4.15305C11.6947 4.14563 11.6981 4.13726 11.7025 4.12965C11.7154 4.10815 11.7282 4.08646 11.7381 4.06325C11.7426 4.05222 11.7447 4.04043 11.7487 4.0292C11.7558 4.00827 11.7636 3.98754 11.7681 3.96547C11.775 3.93161 11.7786 3.89717 11.7786 3.86198C11.7786 3.82678 11.775 3.79235 11.7681 3.75849C11.7638 3.73642 11.756 3.71568 11.7487 3.69476C11.7447 3.68353 11.7428 3.67174 11.7381 3.6607C11.7282 3.63749 11.7156 3.616 11.7025 3.59431C11.6981 3.5867 11.6947 3.57833 11.6899 3.57091C11.6707 3.54256 11.649 3.51593 11.6249 3.49177L9.24876 1.11564C9.04444 0.911322 8.71342 0.911322 8.50891 1.11564C8.30459 1.31977 8.30459 1.65098 8.50891 1.85549L9.99223 3.339H4.15294C2.22978 3.339 0.665039 4.90374 0.665039 6.8269C0.665039 7.11588 0.899227 7.35007 1.1882 7.35007C1.47718 7.35007 1.71137 7.11588 1.71137 6.8269C1.71137 5.48037 2.80659 4.38514 4.15294 4.38514ZM12.2066 6.57445C11.9177 6.57445 11.6835 6.80864 11.6835 7.09762C11.6835 8.44396 10.5883 9.53919 9.24191 9.53919H3.40262L4.88632 8.05549C5.09064 7.85136 5.09064 7.52014 4.88632 7.31563C4.682 7.11112 4.35098 7.11131 4.14647 7.31563L1.76977 9.69233C1.74561 9.71649 1.72393 9.74312 1.70471 9.77147C1.70015 9.7787 1.69691 9.78669 1.69273 9.79392C1.6796 9.81561 1.66647 9.83748 1.65677 9.86126C1.6524 9.87211 1.6503 9.88371 1.64631 9.89475C1.63927 9.91586 1.63128 9.93679 1.62671 9.95905C1.61986 9.99291 1.61625 10.0273 1.61625 10.0625C1.61625 10.0977 1.61986 10.1322 1.62671 10.166C1.63109 10.1883 1.63908 10.2092 1.64631 10.2303C1.6503 10.2414 1.65221 10.253 1.65677 10.2638C1.66666 10.2874 1.6796 10.3093 1.69273 10.3312C1.69691 10.3384 1.70015 10.3464 1.70471 10.3536C1.72393 10.382 1.74561 10.4086 1.76977 10.4328L4.14609 12.8091C4.24825 12.9112 4.38199 12.9624 4.51592 12.9624C4.64985 12.9624 4.78359 12.9112 4.88575 12.8091C5.09007 12.6049 5.09007 12.2737 4.88575 12.0692L3.40243 10.5857H9.24172C11.1649 10.5857 12.7296 9.02097 12.7296 7.09781C12.7298 6.80864 12.4956 6.57445 12.2066 6.57445Z"
fill="var(--color-primary-default)"
/>
</svg>
</div>
<span
class="box mm-icon mm-icon--size-md box--display-inline-block box--flex-direction-row box--color-icon-alternative"
data-testid="exchange-rate-display-switch"
style="mask-image: url('./images/icons/swap-horizontal.svg'); cursor: pointer;"
title="Switch"
/>
</div>
</div>
`;

View File

@ -1,9 +1,19 @@
import React, { useState } from 'react';
import React, { useState, useContext } from 'react';
import PropTypes from 'prop-types';
import BigNumber from 'bignumber.js';
import classnames from 'classnames';
import { formatSwapsValueForDisplay } from '../swaps.util';
import { calcTokenAmount } from '../../../../shared/lib/transactions-controller-utils';
import Box from '../../../components/ui/box';
import {
JustifyContent,
DISPLAY,
AlignItems,
IconColor,
TextColor,
} from '../../../helpers/constants/design-system';
import { Icon, IconName } from '../../../components/component-library';
import { I18nContext } from '../../../contexts/i18n';
export default function ExchangeRateDisplay({
primaryTokenValue,
@ -12,12 +22,13 @@ export default function ExchangeRateDisplay({
secondaryTokenValue,
secondaryTokenDecimals = 18,
secondaryTokenSymbol,
arrowColor = 'var(--color-primary-default)',
boldSymbols = true,
showIconForSwappingTokens = true,
className,
onQuotesClick,
}) {
const [showPrimaryToSecondary, setShowPrimaryToSecondary] = useState(true);
const [rotating, setRotating] = useState(false);
const t = useContext(I18nContext);
const primaryTokenAmount = calcTokenAmount(
primaryTokenValue,
@ -63,10 +74,19 @@ export default function ExchangeRateDisplay({
return (
<div className={classnames('exchange-rate-display', className)}>
<Box
display={DISPLAY.FLEX}
justifyContent={JustifyContent.center}
alignItems={AlignItems.center}
onClick={onQuotesClick}
color={TextColor.primaryDefault}
className="exchange-rate-display__quote-rate"
data-testid="exchange-rate-display-quote-rate"
>
<span>1</span>
<span
className={classnames({ 'exchange-rate-display__bold': boldSymbols })}
data-testid="exchange-rate-display__base-symbol"
data-testid="exchange-rate-display-base-symbol"
>
{baseSymbol}
</span>
@ -77,30 +97,19 @@ export default function ExchangeRateDisplay({
>
{ratiodSymbol}
</span>
<div
className={classnames('exchange-rate-display__switch-arrows', {
'exchange-rate-display__switch-arrows-rotate': rotating,
})}
data-testid="exchange-rate-display__switch-arrows"
</Box>
{showIconForSwappingTokens && (
<Icon
name={IconName.SwapHorizontal}
onClick={() => {
setShowPrimaryToSecondary(!showPrimaryToSecondary);
setRotating(true);
}}
onAnimationEnd={() => setRotating(false)}
>
<svg
width="13"
height="13"
viewBox="0 0 13 13"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.15294 4.38514H9.99223L8.50853 5.86884C8.30421 6.07297 8.30421 6.40418 8.50853 6.60869C8.61069 6.71085 8.74443 6.76203 8.87836 6.76203C9.01229 6.76203 9.14603 6.71085 9.24819 6.60869L11.6249 4.23219C11.649 4.20803 11.6707 4.1814 11.6899 4.15305C11.6947 4.14563 11.6981 4.13726 11.7025 4.12965C11.7154 4.10815 11.7282 4.08646 11.7381 4.06325C11.7426 4.05222 11.7447 4.04043 11.7487 4.0292C11.7558 4.00827 11.7636 3.98754 11.7681 3.96547C11.775 3.93161 11.7786 3.89717 11.7786 3.86198C11.7786 3.82678 11.775 3.79235 11.7681 3.75849C11.7638 3.73642 11.756 3.71568 11.7487 3.69476C11.7447 3.68353 11.7428 3.67174 11.7381 3.6607C11.7282 3.63749 11.7156 3.616 11.7025 3.59431C11.6981 3.5867 11.6947 3.57833 11.6899 3.57091C11.6707 3.54256 11.649 3.51593 11.6249 3.49177L9.24876 1.11564C9.04444 0.911322 8.71342 0.911322 8.50891 1.11564C8.30459 1.31977 8.30459 1.65098 8.50891 1.85549L9.99223 3.339H4.15294C2.22978 3.339 0.665039 4.90374 0.665039 6.8269C0.665039 7.11588 0.899227 7.35007 1.1882 7.35007C1.47718 7.35007 1.71137 7.11588 1.71137 6.8269C1.71137 5.48037 2.80659 4.38514 4.15294 4.38514ZM12.2066 6.57445C11.9177 6.57445 11.6835 6.80864 11.6835 7.09762C11.6835 8.44396 10.5883 9.53919 9.24191 9.53919H3.40262L4.88632 8.05549C5.09064 7.85136 5.09064 7.52014 4.88632 7.31563C4.682 7.11112 4.35098 7.11131 4.14647 7.31563L1.76977 9.69233C1.74561 9.71649 1.72393 9.74312 1.70471 9.77147C1.70015 9.7787 1.69691 9.78669 1.69273 9.79392C1.6796 9.81561 1.66647 9.83748 1.65677 9.86126C1.6524 9.87211 1.6503 9.88371 1.64631 9.89475C1.63927 9.91586 1.63128 9.93679 1.62671 9.95905C1.61986 9.99291 1.61625 10.0273 1.61625 10.0625C1.61625 10.0977 1.61986 10.1322 1.62671 10.166C1.63109 10.1883 1.63908 10.2092 1.64631 10.2303C1.6503 10.2414 1.65221 10.253 1.65677 10.2638C1.66666 10.2874 1.6796 10.3093 1.69273 10.3312C1.69691 10.3384 1.70015 10.3464 1.70471 10.3536C1.72393 10.382 1.74561 10.4086 1.76977 10.4328L4.14609 12.8091C4.24825 12.9112 4.38199 12.9624 4.51592 12.9624C4.64985 12.9624 4.78359 12.9112 4.88575 12.8091C5.09007 12.6049 5.09007 12.2737 4.88575 12.0692L3.40243 10.5857H9.24172C11.1649 10.5857 12.7296 9.02097 12.7296 7.09781C12.7298 6.80864 12.4956 6.57445 12.2066 6.57445Z"
fill={arrowColor}
color={IconColor.iconAlternative}
style={{ cursor: 'pointer' }}
title={t('switch')}
data-testid="exchange-rate-display-switch"
/>
</svg>
</div>
)}
</div>
);
}
@ -125,6 +134,7 @@ ExchangeRateDisplay.propTypes = {
]),
secondaryTokenSymbol: PropTypes.string.isRequired,
className: PropTypes.string,
arrowColor: PropTypes.string,
boldSymbols: PropTypes.bool,
showIconForSwappingTokens: PropTypes.bool,
onQuotesClick: PropTypes.func,
};

View File

@ -28,14 +28,15 @@ describe('ExchangeRateDisplay', () => {
it('clicks on the switch link', () => {
const props = createProps();
props.showIconForSwappingTokens = true;
const { getByTestId } = renderWithProvider(
<ExchangeRateDisplay {...props} />,
);
expect(getByTestId('exchange-rate-display__base-symbol')).toHaveTextContent(
expect(getByTestId('exchange-rate-display-base-symbol')).toHaveTextContent(
'ETH',
);
fireEvent.click(getByTestId('exchange-rate-display__switch-arrows'));
expect(getByTestId('exchange-rate-display__base-symbol')).toHaveTextContent(
fireEvent.click(getByTestId('exchange-rate-display-switch'));
expect(getByTestId('exchange-rate-display-base-symbol')).toHaveTextContent(
'BAT',
);
});

View File

@ -16,34 +16,7 @@
font-weight: bold;
}
&__switch-arrows {
&__quote-rate {
cursor: pointer;
> svg {
margin-top: 4px;
}
}
&__switch-arrows-rotate {
-webkit-animation-name: rotate-toggle;
-webkit-animation-duration: 1s;
-webkit-transition: all 500ms cubic-bezier(0.86, 0, 0.07, 1);
-moz-transition: all 500ms cubic-bezier(0.86, 0, 0.07, 1);
-o-transition: all 500ms cubic-bezier(0.86, 0, 0.07, 1);
transition: all 500ms cubic-bezier(0.86, 0, 0.07, 1);
-webkit-transition-timing-function: cubic-bezier(0.86, 0, 0.07, 1);
-moz-transition-timing-function: cubic-bezier(0.86, 0, 0.07, 1);
-o-transition-timing-function: cubic-bezier(0.86, 0, 0.07, 1);
transition-timing-function: cubic-bezier(0.86, 0, 0.07, 1);
}
@-webkit-keyframes rotate-toggle {
0% {
-webkit-transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(180deg);
}
}
}

View File

@ -7,6 +7,7 @@ exports[`FeeCard renders the component with initial props 1`] = `null`;
exports[`FeeCard renders the component with initial props 2`] = `
<div
class="info-tooltip"
data-testid="info-tooltip"
>
<div
class="fee-card__row-label fee-card__info-tooltip-container"

View File

@ -14,7 +14,9 @@ import {
Redirect,
} from 'react-router-dom';
import { shuffle, isEqual } from 'lodash';
import classnames from 'classnames';
import { I18nContext } from '../../contexts/i18n';
import {
getSelectedAccount,
getCurrentChainId,
@ -46,6 +48,8 @@ import {
getCurrentSmartTransactionsEnabled,
getCurrentSmartTransactionsError,
navigateBackToBuildQuote,
getSwapRedesignEnabled,
setTransactionSettingsOpened,
} from '../../ducks/swaps/swaps';
import {
checkNetworkAndAccountSupports1559,
@ -61,6 +65,8 @@ import {
SWAPS_ERROR_ROUTE,
DEFAULT_ROUTE,
SWAPS_MAINTENANCE_ROUTE,
PREPARE_SWAP_ROUTE,
SWAPS_NOTIFICATION_ROUTE,
} from '../../helpers/constants/routes';
import {
ERROR_FETCHING_QUOTES,
@ -73,7 +79,6 @@ import {
import {
resetBackgroundSwapsState,
setSwapsTokens,
ignoreTokens,
setBackgroundSwapRouteState,
setSwapsErrorKey,
} from '../../store/actions';
@ -84,6 +89,14 @@ import { MetaMetricsEventCategory } from '../../../shared/constants/metametrics'
import { TransactionStatus } from '../../../shared/constants/transaction';
import { MetaMetricsContext } from '../../contexts/metametrics';
import { getSwapsTokensReceivedFromTxMeta } from '../../../shared/lib/transactions-controller-utils';
import { Icon, IconName, IconSize } from '../../components/component-library';
import Box from '../../components/ui/box';
import {
DISPLAY,
JustifyContent,
IconColor,
FRACTIONS,
} from '../../helpers/constants/design-system';
import {
fetchTokens,
fetchTopAssets,
@ -94,6 +107,8 @@ import SmartTransactionStatus from './smart-transaction-status';
import AwaitingSwap from './awaiting-swap';
import LoadingQuote from './loading-swaps-quotes';
import BuildQuote from './build-quote';
import PrepareSwapPage from './prepare-swap-page/prepare-swap-page';
import NotificationPage from './notification-page/notification-page';
import ViewQuote from './view-quote';
export default function Swap() {
@ -110,6 +125,7 @@ export default function Swap() {
const isSmartTransactionStatusRoute =
pathname === SMART_TRANSACTION_STATUS_ROUTE;
const isViewQuoteRoute = pathname === VIEW_QUOTE_ROUTE;
const isPrepareSwapRoute = pathname === PREPARE_SWAP_ROUTE;
const [currentStxErrorTracked, setCurrentStxErrorTracked] = useState(false);
const fetchParams = useSelector(getFetchParams, isEqual);
@ -142,6 +158,7 @@ export default function Swap() {
const currentSmartTransactionsEnabled = useSelector(
getCurrentSmartTransactionsEnabled,
);
const swapRedesignEnabled = useSelector(getSwapRedesignEnabled);
const currentSmartTransactionsError = useSelector(
getCurrentSmartTransactionsError,
);
@ -165,8 +182,6 @@ export default function Swap() {
const { balance: ethBalance, address: selectedAccountAddress } =
selectedAccount;
const { destinationTokenAddedForSwap } = fetchParams || {};
const approveTxData =
approveTxId && txList.find(({ id }) => approveTxId === id);
const tradeTxData = tradeTxId && txList.find(({ id }) => tradeTxId === id);
@ -194,35 +209,6 @@ export default function Swap() {
swapsErrorKey = SWAP_FAILED_ERROR;
}
const clearTemporaryTokenRef = useRef();
useEffect(() => {
clearTemporaryTokenRef.current = () => {
if (
destinationTokenAddedForSwap &&
(!isAwaitingSwapRoute || conversionError)
) {
dispatch(
ignoreTokens({
tokensToIgnore: destinationTokenInfo?.address,
dontShowLoadingIndicator: true,
}),
);
}
};
}, [
conversionError,
dispatch,
destinationTokenAddedForSwap,
destinationTokenInfo,
fetchParams,
isAwaitingSwapRoute,
]);
useEffect(() => {
return () => {
clearTemporaryTokenRef.current();
};
}, []);
// eslint-disable-next-line
useEffect(() => {
if (!isSwapsChain) {
@ -297,7 +283,6 @@ export default function Swap() {
const beforeUnloadEventAddedRef = useRef();
useEffect(() => {
const fn = () => {
clearTemporaryTokenRef.current();
if (isLoadingQuotesRoute) {
dispatch(prepareToLeaveSwaps());
}
@ -363,10 +348,17 @@ export default function Swap() {
return <></>;
}
const redirectToDefaultRoute = async () => {
dispatch(clearSwapsState());
await dispatch(resetBackgroundSwapsState());
history.push(DEFAULT_ROUTE);
};
return (
<div className="swaps">
<div className="swaps__container">
<div className="swaps__header">
{!swapRedesignEnabled && (
<div
className="swaps__header-edit"
onClick={async () => {
@ -375,11 +367,39 @@ export default function Swap() {
>
{isViewQuoteRoute && t('edit')}
</div>
)}
{swapRedesignEnabled && (
<Box
display={DISPLAY.FLEX}
justifyContent={JustifyContent.center}
marginLeft={4}
width={FRACTIONS.ONE_TWELFTH}
tabIndex="0"
onKeyUp={(e) => {
if (e.key === 'Enter') {
redirectToDefaultRoute();
}
}}
>
{!isAwaitingSwapRoute &&
!isAwaitingSignaturesRoute &&
!isSmartTransactionStatusRoute && (
<Icon
name={IconName.Arrow2Left}
size={IconSize.Lg}
color={IconColor.iconAlternative}
onClick={redirectToDefaultRoute}
style={{ cursor: 'pointer' }}
title={t('cancel')}
/>
)}
</Box>
)}
<div className="swaps__title">{t('swap')}</div>
{!swapRedesignEnabled && (
<div
className="swaps__header-cancel"
onClick={async () => {
clearTemporaryTokenRef.current();
dispatch(clearSwapsState());
await dispatch(resetBackgroundSwapsState());
history.push(DEFAULT_ROUTE);
@ -390,8 +410,40 @@ export default function Swap() {
!isSmartTransactionStatusRoute &&
t('cancel')}
</div>
)}
{swapRedesignEnabled && (
<Box
display={DISPLAY.FLEX}
justifyContent={JustifyContent.center}
marginRight={4}
width={FRACTIONS.ONE_TWELFTH}
tabIndex="0"
onKeyUp={(e) => {
if (e.key === 'Enter') {
dispatch(setTransactionSettingsOpened(true));
}
}}
>
{isPrepareSwapRoute && (
<Icon
name={IconName.Setting}
size={IconSize.Lg}
color={IconColor.iconAlternative}
onClick={() => {
dispatch(setTransactionSettingsOpened(true));
}}
style={{ cursor: 'pointer' }}
title={t('transactionSettings')}
/>
)}
</Box>
)}
</div>
<div className="swaps__content">
<div
className={classnames('swaps__content', {
'swaps__content--redesign-enabled': swapRedesignEnabled,
})}
>
<Switch>
<FeatureToggledRoute
redirectRoute={SWAPS_MAINTENANCE_ROUTE}
@ -399,6 +451,9 @@ export default function Swap() {
path={BUILD_QUOTE_ROUTE}
exact
render={() => {
if (swapRedesignEnabled) {
return <Redirect to={{ pathname: PREPARE_SWAP_ROUTE }} />;
}
if (tradeTxData && !conversionError) {
return <Redirect to={{ pathname: AWAITING_SWAP_ROUTE }} />;
} else if (tradeTxData && routeState) {
@ -416,6 +471,25 @@ export default function Swap() {
);
}}
/>
<FeatureToggledRoute
redirectRoute={SWAPS_MAINTENANCE_ROUTE}
flag={swapsEnabled}
path={PREPARE_SWAP_ROUTE}
exact
render={() => {
if (!swapRedesignEnabled) {
return <Redirect to={{ pathname: BUILD_QUOTE_ROUTE }} />;
}
return (
<PrepareSwapPage
ethBalance={ethBalance}
selectedAccountAddress={selectedAccountAddress}
shuffledTokensList={shuffledTokensList}
/>
);
}}
/>
<FeatureToggledRoute
redirectRoute={SWAPS_MAINTENANCE_ROUTE}
flag={swapsEnabled}
@ -432,6 +506,9 @@ export default function Swap() {
/>
);
}
if (swapRedesignEnabled) {
return <Redirect to={{ pathname: PREPARE_SWAP_ROUTE }} />;
}
if (Object.values(quotes).length) {
return (
<ViewQuote numberOfQuotes={Object.values(quotes).length} />
@ -460,6 +537,16 @@ export default function Swap() {
return <Redirect to={{ pathname: BUILD_QUOTE_ROUTE }} />;
}}
/>
<Route
path={SWAPS_NOTIFICATION_ROUTE}
exact
render={() => {
if (!swapsErrorKey) {
return <Redirect to={{ pathname: PREPARE_SWAP_ROUTE }} />;
}
return <NotificationPage notificationKey={swapsErrorKey} />;
}}
/>
<FeatureToggledRoute
redirectRoute={SWAPS_MAINTENANCE_ROUTE}
flag={swapsEnabled}

View File

@ -2,6 +2,8 @@
@import 'awaiting-signatures/index';
@import 'smart-transaction-status/index';
@import 'build-quote/index';
@import 'prepare-swap-page/index';
@import 'notification-page/index';
@import 'countdown-timer/index';
@import 'dropdown-input-pair/index';
@import 'dropdown-search-list/index';
@ -17,6 +19,10 @@
@import 'import-token/index';
@import 'create-new-swap/index';
@import 'view-on-block-explorer/index';
@import 'transaction-settings/index';
@import 'list-with-search/index';
@import 'popover-custom-background/index';
@import 'mascot-background-animation/index';
.swaps {
display: flex;
@ -49,9 +55,10 @@
@include screen-sm-min {
width: 460px;
background: var(--color-background-default);
border: 1px solid var(--color-border-muted);
box-shadow: var(--shadow-size-xs) var(--color-shadow-default);
border-radius: 8px;
border: 1px solid var(--color-border-muted);
border-top: 0;
border-radius: 0 0 8px 8px;
height: 620px;
}
}
@ -73,6 +80,12 @@
@include screen-sm-min {
width: 348px;
}
&--redesign-enabled {
@include screen-sm-min {
width: 100%;
}
}
}
&__title {
@ -90,16 +103,18 @@
flex-flow: column;
justify-content: center;
align-items: center;
padding-top: 0;
padding-bottom: 16px;
padding-top: 12px;
padding-bottom: 12px;
background: var(--color-background-alternative);
width: 100%;
position: relative;
flex-direction: row;
position: sticky;
top: 0;
z-index: 1;
@include screen-sm-min {
padding-top: 8px;
padding-bottom: 8px;
padding-top: 12px;
padding-bottom: 12px;
height: 66px;
}
}
@ -147,3 +162,17 @@
margin-right: 14px;
}
}
.mm-modal {
&__custom-scrollbar {
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-thumb {
-webkit-border-radius: 10px;
border-radius: 10px;
background: var(--color-icon-muted);
}
}
}

View File

@ -59,7 +59,9 @@ describe('Swap', () => {
});
it('renders the component with initial props', async () => {
const store = configureMockStore(middleware)(createSwapsMockStore());
const swapsMockStore = createSwapsMockStore();
swapsMockStore.metamask.swapsState.swapsFeatureFlags.swapRedesign.extensionActive = false;
const store = configureMockStore(middleware)(swapsMockStore);
const { container, getByText } = renderWithProvider(<Swap />, store);
await waitFor(() => expect(featureFlagsNock.isDone()).toBe(true));
expect(getByText('Swap')).toBeInTheDocument();

View File

@ -0,0 +1,22 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ListWithSearch renders the component with initial props 1`] = `
<div
class="box mm-text-field mm-text-field--size-md mm-text-field--focused mm-text-field--truncate mm-text-field-search list-with-search__text-search box--margin-bottom-4 box--padding-left-4 box--display-inline-flex box--flex-direction-row box--align-items-center box--background-color-background-default box--rounded-sm box--border-width-1 box--border-style-solid"
tabindex="0"
>
<span
class="box mm-icon mm-icon--size-sm box--display-inline-block box--flex-direction-row box--color-inherit"
style="mask-image: url('./images/icons/search.svg');"
/>
<input
autocomplete="off"
class="box mm-text mm-input mm-input--disable-state-styles mm-text-field__input mm-text--body-md box--padding-right-4 box--padding-left-2 box--flex-direction-row box--color-text-default box--background-color-transparent box--border-style-none"
focused="true"
id="list-with-search__text-search"
placeholder="Enter token name or paste address"
type="search"
value=""
/>
</div>
`;

View File

@ -0,0 +1,23 @@
.list-with-search {
.searchable-item-list {
&__list-container {
height: 320px;
overflow-y: auto;
padding-right: 8px;
@include screen-sm-min {
height: 600px;
}
}
}
&__text-search {
outline: none;
}
.mm-button-icon {
.mm-icon--size-lg {
--size: 20px;
}
}
}

View File

@ -0,0 +1,179 @@
import React, { useState, useEffect, useRef, useContext } from 'react';
import { useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { filter } from 'lodash';
import log from 'loglevel';
import Box from '../../../components/ui/box';
import {
DISPLAY,
FLEX_DIRECTION,
JustifyContent,
AlignItems,
TextVariant,
BLOCK_SIZES,
} from '../../../helpers/constants/design-system';
import { TextFieldSearch, Text } from '../../../components/component-library';
import ItemList from '../searchable-item-list/item-list';
import { isValidHexAddress } from '../../../../shared/modules/hexstring-utils';
import { I18nContext } from '../../../contexts/i18n';
import { fetchToken } from '../swaps.util';
import { getCurrentChainId } from '../../../selectors/selectors';
let timeoutIdForSearch;
export default function ListWithSearch({
itemsToSearch = [],
listTitle,
maxListItems,
onClickItem,
onOpenImportTokenModalClick,
shouldSearchForImports,
Placeholder,
hideRightLabels,
hideItemIf,
listContainerClassName,
searchQuery,
setSearchQuery,
}) {
const itemListRef = useRef();
const t = useContext(I18nContext);
const [items, setItems] = useState(itemsToSearch);
const chainId = useSelector(getCurrentChainId);
/**
* Search a custom token for import based on a contract address.
*
* @param {string} contractAddress
*/
const handleSearchTokenForImport = async (contractAddress) => {
try {
const token = await fetchToken(contractAddress, chainId);
if (token) {
token.primaryLabel = token.symbol;
token.secondaryLabel = token.name;
token.notImported = true;
setItems([token]);
return;
}
} catch (e) {
log.error('Token not found, show 0 results.', e);
}
setItems([]); // No token for import found.
};
const handleSearch = async (newSearchQuery) => {
setSearchQuery(newSearchQuery);
if (timeoutIdForSearch) {
clearTimeout(timeoutIdForSearch);
}
timeoutIdForSearch = setTimeout(async () => {
timeoutIdForSearch = null;
const trimmedNewSearchQuery = newSearchQuery.trim();
const trimmedNewSearchQueryUpperCase =
trimmedNewSearchQuery.toUpperCase();
const trimmedNewSearchQueryLowerCase =
trimmedNewSearchQuery.toLowerCase();
if (!trimmedNewSearchQuery) {
setItems(itemsToSearch);
return;
}
const validHexAddress = isValidHexAddress(trimmedNewSearchQuery);
let filteredItems = [];
if (validHexAddress) {
// E.g. DAI token: 0x6b175474e89094c44da98b954eedeac495271D0f
const foundItem = itemsToSearch.find((item) => {
return item.address === trimmedNewSearchQueryLowerCase;
});
if (foundItem) {
filteredItems.push(foundItem);
}
} else {
filteredItems = filter(itemsToSearch, function (item) {
return item.symbol.includes(trimmedNewSearchQueryUpperCase);
});
}
const results = newSearchQuery === '' ? itemsToSearch : filteredItems;
if (shouldSearchForImports && results.length === 0 && validHexAddress) {
await handleSearchTokenForImport(trimmedNewSearchQuery);
return;
}
setItems(results);
}, 350);
};
useEffect(() => {
handleSearch(searchQuery);
}, [searchQuery]);
const handleOnClear = () => {
setSearchQuery('');
};
return (
<Box className="list-with-search" width={BLOCK_SIZES.FULL} tabIndex="0">
<Box
style={{ gridColumnStart: 1, gridColumnEnd: 3 }}
display={DISPLAY.FLEX}
flexDirection={FLEX_DIRECTION.COLUMN}
>
<TextFieldSearch
id="list-with-search__text-search"
marginBottom={4}
onChange={(e) => handleSearch(e.target.value)}
clearButtonOnClick={handleOnClear}
value={searchQuery}
placeholder={t('enterTokenNameOrAddress')}
inputProps={{ marginRight: 0 }}
className="list-with-search__text-search"
autoFocus
tabIndex="0"
/>
</Box>
{items?.length > 0 && (
<ItemList
searchQuery={searchQuery}
results={items}
onClickItem={onClickItem}
onOpenImportTokenModalClick={onOpenImportTokenModalClick}
Placeholder={Placeholder}
listTitle={listTitle}
maxListItems={maxListItems}
containerRef={itemListRef}
hideRightLabels={hideRightLabels}
hideItemIf={hideItemIf}
listContainerClassName={listContainerClassName}
/>
)}
{items?.length === 0 && (
<Box
marginTop={1}
marginBottom={5}
display={DISPLAY.FLEX}
justifyContent={JustifyContent.center}
alignItems={AlignItems.center}
>
<Text variant={TextVariant.bodyMd} as="h6">
{t('swapNoTokensAvailable', [searchQuery])}
</Text>
</Box>
)}
</Box>
);
}
ListWithSearch.propTypes = {
itemsToSearch: PropTypes.array,
onClickItem: PropTypes.func,
onOpenImportTokenModalClick: PropTypes.func,
Placeholder: PropTypes.func,
listTitle: PropTypes.string,
maxListItems: PropTypes.number,
hideRightLabels: PropTypes.bool,
shouldSearchForImports: PropTypes.bool,
hideItemIf: PropTypes.func,
listContainerClassName: PropTypes.string,
searchQuery: PropTypes.string,
setSearchQuery: PropTypes.func,
};

View File

@ -0,0 +1,68 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import {
renderWithProvider,
createSwapsMockStore,
} from '../../../../test/jest';
import ListWithSearch from './list-with-search';
const createProps = (customProps = {}) => {
return {
itemsToSearch: [
{
iconUrl: 'iconUrl',
selected: true,
primaryLabel: 'primaryLabel',
secondaryLabel: 'secondaryLabel',
rightPrimaryLabel: 'rightPrimaryLabel',
rightSecondaryLabel: 'rightSecondaryLabel',
},
],
onClickItem: jest.fn(),
onOpenImportTokenModalClick: jest.fn(),
setSearchQuery: jest.fn(),
Placeholder: <></>,
listTitle: 'listTitle',
...customProps,
};
};
describe('ListWithSearch', () => {
it('renders the component with initial props', () => {
const store = configureMockStore()(createSwapsMockStore());
const props = createProps();
const { getByText, getByPlaceholderText } = renderWithProvider(
<ListWithSearch {...props} />,
store,
);
expect(getByText(props.listTitle)).toBeInTheDocument();
expect(getByText(props.itemsToSearch[0].primaryLabel)).toBeInTheDocument();
expect(
getByText(props.itemsToSearch[0].secondaryLabel),
).toBeInTheDocument();
expect(
getByText(props.itemsToSearch[0].rightPrimaryLabel),
).toBeInTheDocument();
expect(
getByText(props.itemsToSearch[0].rightSecondaryLabel),
).toBeInTheDocument();
expect(
getByPlaceholderText('Enter token name or paste address'),
).toBeInTheDocument();
expect(
document.querySelector('.list-with-search__text-search'),
).toMatchSnapshot();
});
it('renders the component with an empty list', () => {
const store = configureMockStore()(createSwapsMockStore());
const props = createProps();
props.itemsToSearch = [];
const { getByText } = renderWithProvider(
<ListWithSearch {...props} />,
store,
);
expect(getByText('No tokens available matching')).toBeInTheDocument();
});
});

View File

@ -3,7 +3,10 @@ import React from 'react';
export default function BackgroundAnimation() {
return (
<>
<div className="loading-swaps-quotes__background-1">
<div
className="loading-swaps-quotes__background-1"
data-testid="loading-swaps-quotes-background-1"
>
<svg
width="193"
height="190"
@ -143,7 +146,10 @@ export default function BackgroundAnimation() {
</defs>
</svg>
</div>
<div className="loading-swaps-quotes__background-2">
<div
className="loading-swaps-quotes__background-2"
data-testid="loading-swaps-quotes-background-2"
>
<svg
width="195"
height="205"

View File

@ -5,8 +5,15 @@ import BackgroundAnimation from './background-animation';
describe('BackgroundAnimation', () => {
it('renders the component', () => {
const { container } = renderWithProvider(<BackgroundAnimation />);
expect(container.firstChild.nodeName).toBe('DIV');
const { container, getByTestId } = renderWithProvider(
<BackgroundAnimation />,
);
expect(
getByTestId('loading-swaps-quotes-background-1'),
).toBeInTheDocument();
expect(
getByTestId('loading-swaps-quotes-background-2'),
).toBeInTheDocument();
expect(container.firstChild.firstChild.nodeName).toBe('svg');
});
});

View File

@ -79,13 +79,17 @@ exports[`MainQuoteSummary renders the component with initial props 4`] = `
>
<div
class="exchange-rate-display main-quote-summary__exchange-rate-display"
>
<div
class="box exchange-rate-display__quote-rate box--display-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-primary-default"
data-testid="exchange-rate-display-quote-rate"
>
<span>
1
</span>
<span
class=""
data-testid="exchange-rate-display__base-symbol"
data-testid="exchange-rate-display-base-symbol"
>
ETH
</span>
@ -100,23 +104,13 @@ exports[`MainQuoteSummary renders the component with initial props 4`] = `
>
BAT
</span>
<div
class="exchange-rate-display__switch-arrows"
data-testid="exchange-rate-display__switch-arrows"
>
<svg
fill="none"
height="13"
viewBox="0 0 13 13"
width="13"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.15294 4.38514H9.99223L8.50853 5.86884C8.30421 6.07297 8.30421 6.40418 8.50853 6.60869C8.61069 6.71085 8.74443 6.76203 8.87836 6.76203C9.01229 6.76203 9.14603 6.71085 9.24819 6.60869L11.6249 4.23219C11.649 4.20803 11.6707 4.1814 11.6899 4.15305C11.6947 4.14563 11.6981 4.13726 11.7025 4.12965C11.7154 4.10815 11.7282 4.08646 11.7381 4.06325C11.7426 4.05222 11.7447 4.04043 11.7487 4.0292C11.7558 4.00827 11.7636 3.98754 11.7681 3.96547C11.775 3.93161 11.7786 3.89717 11.7786 3.86198C11.7786 3.82678 11.775 3.79235 11.7681 3.75849C11.7638 3.73642 11.756 3.71568 11.7487 3.69476C11.7447 3.68353 11.7428 3.67174 11.7381 3.6607C11.7282 3.63749 11.7156 3.616 11.7025 3.59431C11.6981 3.5867 11.6947 3.57833 11.6899 3.57091C11.6707 3.54256 11.649 3.51593 11.6249 3.49177L9.24876 1.11564C9.04444 0.911322 8.71342 0.911322 8.50891 1.11564C8.30459 1.31977 8.30459 1.65098 8.50891 1.85549L9.99223 3.339H4.15294C2.22978 3.339 0.665039 4.90374 0.665039 6.8269C0.665039 7.11588 0.899227 7.35007 1.1882 7.35007C1.47718 7.35007 1.71137 7.11588 1.71137 6.8269C1.71137 5.48037 2.80659 4.38514 4.15294 4.38514ZM12.2066 6.57445C11.9177 6.57445 11.6835 6.80864 11.6835 7.09762C11.6835 8.44396 10.5883 9.53919 9.24191 9.53919H3.40262L4.88632 8.05549C5.09064 7.85136 5.09064 7.52014 4.88632 7.31563C4.682 7.11112 4.35098 7.11131 4.14647 7.31563L1.76977 9.69233C1.74561 9.71649 1.72393 9.74312 1.70471 9.77147C1.70015 9.7787 1.69691 9.78669 1.69273 9.79392C1.6796 9.81561 1.66647 9.83748 1.65677 9.86126C1.6524 9.87211 1.6503 9.88371 1.64631 9.89475C1.63927 9.91586 1.63128 9.93679 1.62671 9.95905C1.61986 9.99291 1.61625 10.0273 1.61625 10.0625C1.61625 10.0977 1.61986 10.1322 1.62671 10.166C1.63109 10.1883 1.63908 10.2092 1.64631 10.2303C1.6503 10.2414 1.65221 10.253 1.65677 10.2638C1.66666 10.2874 1.6796 10.3093 1.69273 10.3312C1.69691 10.3384 1.70015 10.3464 1.70471 10.3536C1.72393 10.382 1.74561 10.4086 1.76977 10.4328L4.14609 12.8091C4.24825 12.9112 4.38199 12.9624 4.51592 12.9624C4.64985 12.9624 4.78359 12.9112 4.88575 12.8091C5.09007 12.6049 5.09007 12.2737 4.88575 12.0692L3.40243 10.5857H9.24172C11.1649 10.5857 12.7296 9.02097 12.7296 7.09781C12.7298 6.80864 12.4956 6.57445 12.2066 6.57445Z"
fill="var(--color-primary-default)"
/>
</svg>
</div>
<span
class="box mm-icon mm-icon--size-md box--display-inline-block box--flex-direction-row box--color-icon-alternative"
data-testid="exchange-rate-display-switch"
style="mask-image: url('./images/icons/swap-horizontal.svg'); cursor: pointer;"
title="Switch"
/>
</div>
</div>
`;

View File

@ -0,0 +1,60 @@
.mascot-background-animation {
display: flex;
flex-flow: column;
align-items: center;
flex: 1;
width: 100%;
&__background-1,
&__background-2 {
width: 120px;
height: 100px;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
-webkit-animation: spin 38s linear infinite;
-moz-animation: spin 38s linear infinite;
animation: spin 38s linear infinite;
@-moz-keyframes spin {
100% {
-moz-transform: rotate(360deg);
}
}
@-webkit-keyframes spin {
100% {
-webkit-transform: rotate(360deg);
}
}
@keyframes spin {
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
}
&__background-2 {
width: 120px;
height: 128px;
-webkit-animation: spin 42s linear infinite;
-moz-animation: spin 42s linear infinite;
animation: spin 42s linear infinite;
}
&__mascot-container {
position: relative;
}
&__animation {
display: flex;
justify-content: center;
align-items: center;
position: relative;
margin-top: 40px;
margin-bottom: 40px;
}
}

View File

@ -0,0 +1,229 @@
import EventEmitter from 'events';
import React, { useRef } from 'react';
import Mascot from '../../../components/ui/mascot';
export default function MascotBackgroundAnimation() {
const animationEventEmitter = useRef(new EventEmitter());
return (
<div className="mascot-background-animation__animation">
<div
className="mascot-background-animation__background-1"
data-testid="mascot-background-animation-background-1"
>
<svg
width="193"
height="190"
viewBox="0 0 193 190"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M153.753 53.876C153.595 53.9493 153.419 54.0161 153.25 54.0651C151.081 54.7451 148.777 53.538 148.086 51.3768C147.763 50.3298 147.851 49.2109 148.361 48.2363C148.87 47.2618 149.732 46.5374 150.78 46.2144C151.828 45.8913 152.948 45.9781 153.923 46.4865C154.898 46.9949 155.622 47.8557 155.944 48.9027C156.567 50.918 155.592 53.0243 153.753 53.876ZM150.817 47.5708C150.245 47.8359 149.782 48.2721 149.495 48.8334C149.144 49.5127 149.073 50.2843 149.303 51.0047C149.774 52.4998 151.375 53.3384 152.877 52.8787C154.374 52.4069 155.215 50.8063 154.756 49.3056C154.526 48.5852 154.027 47.9888 153.36 47.6334C152.68 47.2836 151.908 47.213 151.187 47.4437C151.049 47.4636 150.927 47.52 150.817 47.5708Z"
fill="#86E29B"
/>
<path
d="M18.3624 73.9241C18.3015 73.9523 18.2407 73.9805 18.1798 74.0087C17.0495 74.4733 15.8073 74.4728 14.6751 74.0078C12.3517 73.0461 11.2387 70.3567 12.2031 68.0341C13.1676 65.7115 15.8598 64.5971 18.1833 65.5589C19.3155 66.0239 20.1951 66.9013 20.6591 68.0304C21.123 69.1595 21.1215 70.4008 20.6552 71.5326C20.2207 72.6053 19.4093 73.4391 18.3624 73.9241ZM15.0433 66.7921C14.3129 67.1305 13.713 67.7186 13.3766 68.5243C12.6776 70.207 13.4834 72.1377 15.1666 72.8348C15.9849 73.1647 16.8786 73.1789 17.6933 72.831C18.5136 72.4952 19.1388 71.8659 19.4818 71.0424C19.8126 70.2245 19.8276 69.3313 19.4801 68.5175C19.1448 67.6981 18.5155 67.0739 17.6917 66.7319C16.8144 66.3703 15.8589 66.4142 15.0433 66.7921Z"
fill="#FFB0EB"
/>
<path
d="M116.617 37.3839C117.397 37.0226 117.736 36.0982 117.375 35.3192C117.015 34.5402 116.09 34.2016 115.31 34.5629C114.53 34.9243 114.19 35.8487 114.551 36.6277C114.912 37.4067 115.837 37.7453 116.617 37.3839Z"
fill="url(#paint0_linear)"
/>
<path
d="M55.1317 91.7213C55.9116 91.36 56.2512 90.4356 55.8903 89.6566C55.5294 88.8776 54.6046 88.539 53.8247 88.9003C53.0448 89.2617 52.7052 90.1861 53.0661 90.9651C53.427 91.7441 54.3518 92.0827 55.1317 91.7213Z"
fill="url(#paint1_linear)"
/>
<path
d="M31.9932 126.235C32.7731 125.874 33.1127 124.95 32.7518 124.171C32.3909 123.392 31.4661 123.053 30.6863 123.414C29.9064 123.776 29.5667 124.7 29.9277 125.479C30.2886 126.258 31.2134 126.597 31.9932 126.235Z"
fill="url(#paint2_linear)"
/>
<path
d="M119.43 132.589C120.21 132.228 120.55 131.304 120.189 130.525C119.828 129.746 118.903 129.407 118.123 129.768C117.344 130.13 117.004 131.054 117.365 131.833C117.726 132.612 118.651 132.951 119.43 132.589Z"
fill="url(#paint3_linear)"
/>
<path
d="M44.7469 47.3835C46.0108 46.7979 46.5612 45.2997 45.9763 44.0372C45.3914 42.7747 43.8926 42.2259 42.6286 42.8115C41.3647 43.3971 40.8143 44.8953 41.3992 46.1578C41.9841 47.4203 43.4829 47.9691 44.7469 47.3835Z"
fill="url(#paint4_linear)"
/>
<path
d="M105.107 90.7857C106.371 90.2001 106.922 88.702 106.337 87.4394C105.752 86.1769 104.253 85.6282 102.989 86.2137C101.725 86.7993 101.175 88.2975 101.76 89.56C102.344 90.8226 103.843 91.3713 105.107 90.7857Z"
fill="url(#paint5_linear)"
/>
<path
d="M95.5179 172.376C96.7818 171.791 97.3322 170.293 96.7473 169.03C96.1624 167.767 94.6636 167.219 93.3996 167.804C92.1357 168.39 91.5853 169.888 92.1702 171.151C92.7551 172.413 94.2539 172.962 95.5179 172.376Z"
fill="url(#paint6_linear)"
/>
<path
d="M165.098 102.367C166.362 101.781 166.912 100.283 166.327 99.0205C165.742 97.758 164.244 97.2092 162.98 97.7948C161.716 98.3804 161.165 99.8786 161.75 101.141C162.335 102.404 163.834 102.952 165.098 102.367Z"
fill="url(#paint7_linear)"
/>
<defs>
<linearGradient
id="paint0_linear"
x1="114.554"
y1="36.6326"
x2="117.379"
y2="35.3237"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#75C3FC" />
<stop offset="1" stopColor="#75C3FC" />
</linearGradient>
<linearGradient
id="paint1_linear"
x1="53.0688"
y1="90.97"
x2="55.8937"
y2="89.6611"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#75C3FC" />
<stop offset="1" stopColor="#75C3FC" />
</linearGradient>
<linearGradient
id="paint2_linear"
x1="29.9283"
y1="125.483"
x2="32.7532"
y2="124.174"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#75C3FC" />
<stop offset="1" stopColor="#75C3FC" />
</linearGradient>
<linearGradient
id="paint3_linear"
x1="117.365"
y1="131.837"
x2="120.19"
y2="130.528"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#75C3FC" />
<stop offset="1" stopColor="#75C3FC" />
</linearGradient>
<linearGradient
id="paint4_linear"
x1="41.4394"
y1="46.2402"
x2="45.947"
y2="43.9537"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#75C3FC" />
<stop offset="1" stopColor="#75C3FC" />
</linearGradient>
<linearGradient
id="paint5_linear"
x1="101.8"
y1="89.6425"
x2="106.307"
y2="87.356"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#75C3FC" />
<stop offset="1" stopColor="#75C3FC" />
</linearGradient>
<linearGradient
id="paint6_linear"
x1="92.2104"
y1="171.233"
x2="96.718"
y2="168.947"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#75C3FC" />
<stop offset="1" stopColor="#75C3FC" />
</linearGradient>
<linearGradient
id="paint7_linear"
x1="161.79"
y1="101.224"
x2="166.298"
y2="98.937"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#75C3FC" />
<stop offset="1" stopColor="#75C3FC" />
</linearGradient>
</defs>
</svg>
</div>
<div
className="mascot-background-animation__background-2"
data-testid="mascot-background-animation-background-2"
>
<svg
width="195"
height="205"
viewBox="0 0 195 205"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M29.84 121.982C30.0408 121.969 30.245 122.01 30.4258 122.106L46.4233 130.275C46.8125 130.478 47.0536 130.893 47.0402 131.324C47.0143 131.768 46.7423 132.148 46.3356 132.308L24.5484 140.937C24.1417 141.097 23.6789 141.005 23.3642 140.702C23.0495 140.4 22.9398 139.937 23.0753 139.525L28.8651 122.727C28.9671 122.426 29.2065 122.169 29.5086 122.056C29.614 122.023 29.7194 121.989 29.84 121.982ZM43.1981 131.148L30.5607 124.689L25.9884 137.96L43.1981 131.148Z"
fill="#75C4FD"
/>
<path
d="M168.214 54.3381C168.442 54.3238 168.674 54.3764 168.869 54.485C169.217 54.6781 169.44 55.0266 169.465 55.4145L170.59 68.4358C170.631 68.8763 170.416 69.3061 170.041 69.5444C169.666 69.7827 169.182 69.7862 168.805 69.5681L156.14 62.2246C155.763 62.0065 155.535 61.5911 155.548 61.1472C155.56 60.7033 155.818 60.3112 156.209 60.1122L167.75 54.4343C167.908 54.3841 168.067 54.3473 168.214 54.3381ZM168.027 66.3674L167.248 57.3661L159.267 61.2902L168.027 66.3674Z"
fill="#FFB0EB"
/>
<path
d="M88.6283 16.6885C88.8694 16.6734 89.1154 16.7385 89.3255 16.873L100.21 24.1133C100.561 24.3464 100.762 24.7635 100.708 25.1832C100.653 25.6028 100.381 25.969 99.9864 26.1146L86.3391 31.4276C85.9449 31.5731 85.5106 31.5064 85.1842 31.2314C84.8712 30.9556 84.7239 30.5352 84.8192 30.1264L87.5815 17.5731C87.666 17.2053 87.9162 16.9076 88.2702 16.7646C88.3882 16.7169 88.5078 16.696 88.6283 16.6885ZM97.1342 24.7894L89.4471 19.6718L87.5021 28.5349L97.1342 24.7894Z"
fill="url(#paint0_linear)"
/>
<path
d="M117.145 183.156C116.944 183.289 116.698 183.356 116.449 183.344L103.402 182.517C102.982 182.49 102.6 182.229 102.437 181.839C102.274 181.448 102.327 180.995 102.596 180.671L111.758 169.247C112.027 168.923 112.436 168.764 112.856 168.839C113.265 168.921 113.603 169.212 113.725 169.614L117.609 181.866C117.72 182.227 117.652 182.61 117.417 182.911C117.339 183.011 117.246 183.089 117.145 183.156ZM105.728 180.393L114.944 180.981L112.197 172.333L105.728 180.393Z"
fill="url(#paint1_linear)"
/>
<path
d="M38.0816 74.0208C38.1217 74.0183 38.1485 74.0166 38.1887 74.0141C42.3831 73.805 45.9744 77.0577 46.1831 81.2474C46.3919 85.4371 43.1484 89.0241 38.9407 89.234C34.7463 89.4431 31.1549 86.1904 30.9462 82.0007C30.7391 77.8377 33.9307 74.2809 38.0816 74.0208ZM38.9104 87.2486C41.9767 87.0565 44.3523 84.4236 44.1997 81.3448C44.0455 78.2393 41.3949 75.8407 38.2859 75.9952C35.1761 76.1364 32.7753 78.7977 32.9296 81.9033C33.0838 85.0088 35.7344 87.4073 38.8434 87.2528C38.8568 87.252 38.8836 87.2503 38.9104 87.2486Z"
fill="#86E29B"
/>
<path
d="M162.178 97.8401C162.218 97.8376 162.245 97.8359 162.285 97.8334C166.48 97.6243 170.071 100.877 170.28 105.067C170.489 109.256 167.245 112.843 163.037 113.053C158.843 113.262 155.252 110.01 155.043 105.82C154.836 101.657 158.027 98.1002 162.178 97.8401ZM163.007 111.068C166.073 110.876 168.449 108.243 168.296 105.164C168.142 102.059 165.492 99.6601 162.383 99.8146C159.273 99.9557 156.872 102.617 157.026 105.723C157.181 108.828 159.831 111.227 162.94 111.072C162.953 111.071 162.98 111.07 163.007 111.068Z"
fill="#86E29B"
/>
<defs>
<linearGradient
id="paint0_linear"
x1="100.609"
y1="23.2611"
x2="84.4152"
y2="24.2757"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#FFE466" />
<stop offset="1" stopColor="#FFAFEA" />
</linearGradient>
<linearGradient
id="paint1_linear"
x1="103.812"
y1="183.939"
x2="116.959"
y2="174.66"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#75C3FC" />
<stop offset="0.0928503" stopColor="#81C2F6" />
<stop offset="1" stopColor="#F0B8BD" />
</linearGradient>
</defs>
</svg>
</div>
<div
className="mascot-background-animation__mascot-container"
data-testid="mascot-background-animation-mascot-container"
>
<Mascot
animationEventEmitter={animationEventEmitter.current}
width="42"
height="42"
followMouse={false}
/>
</div>
</div>
);
}

View File

@ -0,0 +1,19 @@
import React from 'react';
import { renderWithProvider } from '../../../../test/jest';
import MascotBackgroundAnimation from './mascot-background-animation';
describe('MascotBackgroundAnimation', () => {
it('renders the component', () => {
const { getByTestId } = renderWithProvider(<MascotBackgroundAnimation />);
expect(
getByTestId('mascot-background-animation-background-1'),
).toBeInTheDocument();
expect(
getByTestId('mascot-background-animation-background-2'),
).toBeInTheDocument();
expect(
getByTestId('mascot-background-animation-mascot-container'),
).toBeInTheDocument();
});
});

View File

@ -0,0 +1,13 @@
.notification-page {
display: flex;
flex-flow: column;
height: 100%;
&__content {
flex: 1;
}
&__warning-icon {
font-size: 54px;
}
}

View File

@ -0,0 +1,79 @@
import React, { useContext } from 'react';
import { useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom';
import PropTypes from 'prop-types';
import { I18nContext } from '../../../contexts/i18n';
import { setSwapsErrorKey } from '../../../store/actions';
import Box from '../../../components/ui/box';
import {
DISPLAY,
AlignItems,
TextVariant,
FLEX_DIRECTION,
TEXT_ALIGN,
IconColor,
} from '../../../helpers/constants/design-system';
import { Text, Icon, IconName } from '../../../components/component-library';
import { PREPARE_SWAP_ROUTE } from '../../../helpers/constants/routes';
import SwapsFooter from '../swaps-footer';
import { QUOTES_EXPIRED_ERROR } from '../../../../shared/constants/swaps';
export default function NotificationPage({ notificationKey }) {
const t = useContext(I18nContext);
const history = useHistory();
const dispatch = useDispatch();
// TODO: Either add default values or redirect a user out if a notificationKey value is not supported.
let title = '';
let description = '';
let buttonText = '';
if (notificationKey === QUOTES_EXPIRED_ERROR) {
title = t('swapAreYouStillThere');
description = t('swapAreYouStillThereDescription');
buttonText = t('swapShowLatestQuotes');
}
return (
<div className="notification-page">
<Box
alignItems={AlignItems.center}
display={DISPLAY.FLEX}
flexDirection={FLEX_DIRECTION.COLUMN}
marginTop={10}
marginLeft={4}
marginRight={4}
textAlign={TEXT_ALIGN.CENTER}
className="notification-page__content"
>
<Box marginTop={8} marginBottom={4}>
<Icon
name={IconName.Warning}
color={IconColor.iconMuted}
className="notification-page__warning-icon"
/>
</Box>
<Text variant={TextVariant.bodyLgMedium} as="h2">
{title}
</Text>
<Text variant={TextVariant.bodyMd} as="h6">
{description}
</Text>
</Box>
<SwapsFooter
onSubmit={async () => {
await dispatch(setSwapsErrorKey(''));
history.push(PREPARE_SWAP_ROUTE);
}}
submitText={buttonText}
hideCancel
showTermsOfService
/>
</div>
);
}
NotificationPage.propTypes = {
notificationKey: PropTypes.oneOf([QUOTES_EXPIRED_ERROR]),
};

View File

@ -0,0 +1,48 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import {
renderWithProvider,
createSwapsMockStore,
} from '../../../../test/jest';
import { QUOTES_EXPIRED_ERROR } from '../../../../shared/constants/swaps';
import NotificationPage from './notification-page';
const middleware = [thunk];
describe('NotificationPage', () => {
it('renders the component with the QUOTES_EXPIRED_ERROR', () => {
const mockStore = createSwapsMockStore();
const store = configureMockStore(middleware)(mockStore);
const { getByText } = renderWithProvider(
<NotificationPage notificationKey={QUOTES_EXPIRED_ERROR} />,
store,
);
expect(getByText('Are you still there?')).toBeInTheDocument();
expect(
getByText(
'Were ready to show you the latest quotes when you want to continue',
),
).toBeInTheDocument();
expect(getByText('Show latest quotes')).toBeInTheDocument();
expect(getByText('Terms of service')).toBeInTheDocument();
});
it('renders the component with an unsupported error key', () => {
const mockStore = createSwapsMockStore();
const store = configureMockStore(middleware)(mockStore);
const { getByText, queryByText } = renderWithProvider(
<NotificationPage notificationKey="unsupportedNotificationKey" />,
store,
);
expect(queryByText('Are you still there?')).not.toBeInTheDocument();
expect(
queryByText(
'Were ready to show you the latest quotes when you want to continue',
),
).not.toBeInTheDocument();
expect(queryByText('Show latest quotes')).not.toBeInTheDocument();
expect(getByText('Terms of service')).toBeInTheDocument();
});
});

View File

@ -0,0 +1,6 @@
.popover-custom-background {
height: 100%;
width: 100%;
background: var(--color-background-alternative);
opacity: 0.6;
}

View File

@ -0,0 +1,14 @@
import React from 'react';
import PropTypes from 'prop-types';
import Box from '../../../components/ui/box';
const PopoverCustomBackground = ({ onClose }) => {
return <Box className="popover-custom-background" onClick={onClose}></Box>;
};
export default PopoverCustomBackground;
PopoverCustomBackground.propTypes = {
onClose: PropTypes.func,
};

View File

@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`PrepareSwapPage renders the component with initial props 1`] = `null`;

View File

@ -0,0 +1,404 @@
.prepare-swap-page {
display: flex;
flex-flow: column;
flex: 1;
width: 100%;
&__content {
display: flex;
height: 100%;
flex-direction: column;
margin-left: 16px;
margin-right: 16px;
@include screen-sm-min {
margin-left: 24px;
margin-right: 24px;
}
}
&__swap-from-content {
padding: 24px 16px 20px 16px;
border-radius: 6px 6px 0 0;
box-shadow: none;
border: 1px solid var(--color-border-muted);
margin-top: 16px;
position: relative;
.dropdown-input-pair__input {
input {
text-align: right;
}
}
.MuiInputBase-root {
padding-right: 0;
}
}
&__swap-to-content {
padding: 28px 16px 20px 16px;
border-radius: 0 0 6px 6px;
box-shadow: none;
border: 1px solid var(--color-border-muted);
border-top: 0;
}
&__switch-tokens {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
padding: 7px;
background: var(--color-background-default);
border: 1px solid var(--color-border-muted);
transition: all 0.3s ease-in-out;
cursor: pointer;
.mm-icon {
color: var(--color-icon-alternative);
transition: all 0.3s ease-in-out;
}
&:hover {
background: var(--color-background-default-hover);
.mm-icon {
color: var(--color-icon-default);
}
}
&:active {
background: var(--color-background-default-pressed);
.mm-icon {
color: var(--color-icon-default);
}
}
&--rotate {
transform: rotate(360deg);
}
&--disabled {
cursor: not-allowed;
}
}
&__max-balance {
@include H7;
color: var(--color-primary-default);
cursor: pointer;
padding-left: 4px;
}
&__balance-message {
@include H7;
width: 100%;
color: var(--color-text-alternative);
margin-top: 4px;
display: flex;
flex-flow: row;
height: 18px;
&--error {
div:first-of-type {
font-weight: bold;
color: var(--color-text-default);
}
.prepare-swap-page__form-error:first-of-type {
font-weight: bold;
color: var(--color-error-default);
}
div:last-of-type {
font-weight: normal;
color: var(--color-text-alternative);
}
}
}
.dropdown-search-list {
background-color: var(--color-background-alternative);
border-radius: 100px;
&__select-default {
color: var(--color-text-default);
}
&__labels {
flex: auto;
max-width: 110px;
&--with-icon {
max-width: 95px;
}
}
&__closed-primary-label {
font-weight: 500;
}
&__selector-closed-container {
border: 0;
border-radius: 100px;
height: 32px;
max-height: 32px;
max-width: 165px;
width: auto;
}
&__selector-closed-icon {
width: 24px;
height: 24px;
margin-right: 8px;
}
&__selector-closed {
height: 32px;
max-width: 140px;
div {
display: flex;
}
&__item-labels {
width: 100%;
margin-left: 0;
}
}
}
.dropdown-input-pair {
height: 32px;
width: auto;
&__selector--closed {
height: 32px;
border-top-right-radius: 100px;
border-bottom-right-radius: 100px;
}
.searchable-item-list {
&__item--add-token {
display: none;
}
}
&__to {
display: flex;
justify-content: space-between;
align-items: center;
.searchable-item-list {
&__item--add-token {
display: flex;
}
}
}
&__input {
div {
border: 0;
}
}
&__two-line-input {
input {
padding-bottom: 0;
}
}
}
&__token-etherscan-link {
color: var(--color-primary-default);
cursor: pointer;
}
&__bold {
font-weight: bold;
}
&__underline {
text-decoration: underline;
}
&__from-token-amount {
border: 0;
outline: none;
input {
padding-right: 0;
text-align: right;
font-weight: var(--typography-s-heading-lg-font-weight);
font-size: var(--typography-s-heading-lg-font-size);
overflow: hidden;
text-overflow: ellipsis;
}
&--lg {
input {
font-weight: var(--typography-s-heading-lg-font-weight);
font-size: var(--typography-s-heading-lg-font-size);
}
}
&--md {
input {
font-weight: var(--typography-s-heading-md-font-weight);
font-size: var(--typography-s-heading-md-font-size);
}
}
&--sm {
input {
font-weight: var(--typography-s-heading-sm-font-weight);
font-size: var(--typography-s-heading-sm-font-size);
}
}
}
&__receive-amount-container {
overflow: hidden;
}
&__receive-amount {
font-weight: var(--typography-s-heading-lg-font-weight);
font-size: var(--typography-s-heading-lg-font-size);
overflow: hidden;
text-overflow: ellipsis;
&--lg {
font-weight: var(--typography-s-heading-lg-font-weight);
font-size: var(--typography-s-heading-lg-font-size);
}
&--md {
font-weight: var(--typography-s-heading-md-font-weight);
font-size: var(--typography-s-heading-md-font-size);
}
&--sm {
font-weight: var(--typography-s-heading-sm-font-weight);
font-size: var(--typography-s-heading-sm-font-size);
}
}
footer {
.btn-primary {
width: 100%;
}
}
}
.review-quote {
display: flex;
flex-flow: column;
height: 100%;
&__overview {
width: 100%;
}
.main-quote-summary {
&__exchange-rate-display {
width: auto;
}
}
&::after { // Hide preloaded images.
position: absolute;
width: 0;
height: 0;
overflow: hidden;
z-index: -1;
content: url('/images/transaction-background-top.svg') url('/images/transaction-background-bottom.svg'); // Preload images for the STX status page.
}
&__content {
display: flex;
flex-flow: column;
align-items: center;
width: 100%;
flex: 1;
@include screen-sm-max {
overflow-y: auto;
max-height: 420px;
}
}
&__bold {
font-weight: bold;
}
&__countdown-timer-container {
display: flex;
justify-content: center;
margin-top: 8px;
}
&__thin-swaps-footer {
max-height: 82px;
@include screen-sm-min {
height: 72px;
}
}
&__footer {
footer {
padding: 16px 0;
.btn-primary {
width: 100%;
}
}
}
&__edit-limit {
white-space: nowrap;
}
}
@keyframes slide-in {
100% { transform: translateY(0%); }
}
.smart-transactions-popover {
transform: translateY(-100%);
animation: slide-in 0.5s forwards;
&__content {
flex-direction: column;
ul {
list-style: inside;
}
a {
color: var(--color-primary-default);
cursor: pointer;
}
}
&__footer {
flex-direction: column;
flex: 1;
align-items: center;
border-top: 0;
button {
border-radius: 50px;
}
a {
font-size: inherit;
padding-bottom: 0;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,188 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import {
renderWithProvider,
createSwapsMockStore,
setBackgroundConnection,
fireEvent,
} from '../../../../test/jest';
import {
setSwapsFromToken,
setSwapToToken,
setFromTokenInputValue,
} from '../../../ducks/swaps/swaps';
import PrepareSwapPage from './prepare-swap-page';
const middleware = [thunk];
const createProps = (customProps = {}) => {
return {
ethBalance: '0x8',
selectedAccountAddress: 'selectedAccountAddress',
shuffledTokensList: [],
...customProps,
};
};
setBackgroundConnection({
resetPostFetchState: jest.fn(),
setBackgroundSwapRouteState: jest.fn(),
clearSwapsQuotes: jest.fn(),
stopPollingForQuotes: jest.fn(),
clearSmartTransactionFees: jest.fn(),
setSwapsFromToken: jest.fn(),
setSwapToToken: jest.fn(),
setFromTokenInputValue: jest.fn(),
});
jest.mock('../../../../shared/lib/token-util.ts', () => {
const actual = jest.requireActual('../../../../shared/lib/token-util.ts');
return {
...actual,
fetchTokenBalance: jest.fn(() => Promise.resolve()),
};
});
jest.mock('../../../ducks/swaps/swaps', () => {
const actual = jest.requireActual('../../../ducks/swaps/swaps');
return {
...actual,
setSwapsFromToken: jest.fn(),
setSwapToToken: jest.fn(),
setFromTokenInputValue: jest.fn(() => {
return {
type: 'MOCK_ACTION',
};
}),
};
});
jest.mock('../swaps.util', () => {
const actual = jest.requireActual('../swaps.util');
return {
...actual,
fetchTokenPrice: jest.fn(() => Promise.resolve()),
};
});
describe('PrepareSwapPage', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('renders the component with initial props', () => {
const store = configureMockStore(middleware)(createSwapsMockStore());
const props = createProps();
const { getByText } = renderWithProvider(
<PrepareSwapPage {...props} />,
store,
);
expect(getByText('Select token')).toBeInTheDocument();
expect(
document.querySelector('.slippage-buttons__button-group'),
).toMatchSnapshot();
});
it('switches swap from and to tokens', () => {
const setSwapFromTokenMock = jest.fn(() => {
return {
type: 'MOCK_ACTION',
};
});
setSwapsFromToken.mockImplementation(setSwapFromTokenMock);
const setSwapToTokenMock = jest.fn(() => {
return {
type: 'MOCK_ACTION',
};
});
setSwapToToken.mockImplementation(setSwapToTokenMock);
const mockStore = createSwapsMockStore();
const store = configureMockStore(middleware)(mockStore);
const props = createProps();
const { getByTestId } = renderWithProvider(
<PrepareSwapPage {...props} />,
store,
);
fireEvent.click(getByTestId('prepare-swap-page-switch-tokens'));
expect(setSwapsFromToken).toHaveBeenCalledWith(mockStore.swaps.toToken);
expect(setSwapToToken).toHaveBeenCalled();
});
it('renders the block explorer link, only 1 verified source', () => {
const mockStore = createSwapsMockStore();
mockStore.swaps.toToken.occurances = 1;
const store = configureMockStore(middleware)(mockStore);
const props = createProps();
const { getByText } = renderWithProvider(
<PrepareSwapPage {...props} />,
store,
);
expect(getByText('Potentially inauthentic token')).toBeInTheDocument();
expect(
getByText('USDC is only verified on 1 source', { exact: false }),
).toBeInTheDocument();
expect(getByText('Etherscan')).toBeInTheDocument();
expect(getByText('Continue swapping')).toBeInTheDocument();
});
it('renders the block explorer link, 0 verified sources', () => {
const mockStore = createSwapsMockStore();
mockStore.swaps.toToken.occurances = 0;
const store = configureMockStore(middleware)(mockStore);
const props = createProps();
const { getByText } = renderWithProvider(
<PrepareSwapPage {...props} />,
store,
);
expect(getByText('Token added manually')).toBeInTheDocument();
expect(
getByText('Verify this token on', { exact: false }),
).toBeInTheDocument();
expect(getByText('Etherscan')).toBeInTheDocument();
expect(getByText('Continue swapping')).toBeInTheDocument();
});
it('clicks on a block explorer link', () => {
global.platform = { openTab: jest.fn() };
const mockStore = createSwapsMockStore();
mockStore.swaps.toToken.occurances = 1;
const store = configureMockStore(middleware)(mockStore);
const props = createProps();
const { getByText } = renderWithProvider(
<PrepareSwapPage {...props} />,
store,
);
const blockExplorer = getByText('Etherscan');
expect(blockExplorer).toBeInTheDocument();
fireEvent.click(blockExplorer);
expect(global.platform.openTab).toHaveBeenCalledWith({
url: 'https://etherscan.io/token/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
});
});
it('clicks on the "max" link', () => {
const setFromTokenInputValueMock = jest.fn(() => {
return {
type: 'MOCK_ACTION',
};
});
setFromTokenInputValue.mockImplementation(setFromTokenInputValueMock);
const mockStore = createSwapsMockStore();
mockStore.swaps.fromToken = {
symbol: 'DAI',
balance: '0x8',
address: '0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f',
decimals: 6,
};
const store = configureMockStore(middleware)(mockStore);
const props = createProps();
const { getByText } = renderWithProvider(
<PrepareSwapPage {...props} />,
store,
);
const maxLink = getByText('Max');
fireEvent.click(maxLink);
expect(setFromTokenInputValue).toHaveBeenCalled();
});
});

View File

@ -0,0 +1,63 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { I18nContext } from '../../../contexts/i18n';
import Box from '../../../components/ui/box';
import {
DISPLAY,
FLEX_DIRECTION,
TextColor,
JustifyContent,
AlignItems,
TextVariant,
} from '../../../helpers/constants/design-system';
import { Text } from '../../../components/component-library';
import MascotBackgroundAnimation from '../mascot-background-animation/mascot-background-animation';
export default function QuotesLoadingAnimation(props) {
const { quoteCount, numberOfAggregators } = props;
const t = useContext(I18nContext);
return (
<Box
marginTop={4}
display={DISPLAY.FLEX}
justifyContent={JustifyContent.center}
alignItems={AlignItems.center}
flexDirection={FLEX_DIRECTION.COLUMN}
>
<Box
display={DISPLAY.FLEX}
justifyContent={JustifyContent.center}
alignItems={AlignItems.center}
>
<Text
variant={TextVariant.bodyMd}
as="h6"
color={TextColor.textAlternative}
marginLeft={1}
marginRight={1}
>
{t('swapFetchingQuote')}
</Text>
<Text
variant={TextVariant.bodyMdBold}
as="h6"
color={TextColor.textAlternative}
>
{t('swapQuoteNofM', [
Math.min(quoteCount + 1, numberOfAggregators),
numberOfAggregators,
])}
</Text>
</Box>
<MascotBackgroundAnimation />
</Box>
);
}
QuotesLoadingAnimation.propTypes = {
quoteCount: PropTypes.number.isRequired,
numberOfAggregators: PropTypes.number.isRequired,
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,104 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import {
renderWithProvider,
createSwapsMockStore,
setBackgroundConnection,
MOCKS,
} from '../../../../test/jest';
import ReviewQuote from './review-quote';
jest.mock(
'../../../components/ui/info-tooltip/info-tooltip-icon',
() => () => '<InfoTooltipIcon />',
);
jest.mock('../../../hooks/gasFeeInput/useGasFeeInputs', () => {
return {
useGasFeeInputs: () => {
return {
maxFeePerGas: 16,
maxPriorityFeePerGas: 3,
gasFeeEstimates: MOCKS.createGasFeeEstimatesForFeeMarket(),
};
},
};
});
const middleware = [thunk];
const createProps = (customProps = {}) => {
return {
setReceiveToAmount: jest.fn(),
...customProps,
};
};
setBackgroundConnection({
resetPostFetchState: jest.fn(),
safeRefetchQuotes: jest.fn(),
setSwapsErrorKey: jest.fn(),
getGasFeeEstimatesAndStartPolling: jest.fn(),
updateTransaction: jest.fn(),
getGasFeeTimeEstimate: jest.fn(),
setSwapsQuotesPollingLimitEnabled: jest.fn(),
});
describe('ReviewQuote', () => {
it('renders the component with initial props', () => {
const store = configureMockStore(middleware)(createSwapsMockStore());
const props = createProps();
const { getByText } = renderWithProvider(<ReviewQuote {...props} />, store);
expect(getByText('New quotes in')).toBeInTheDocument();
expect(getByText('Quote rate')).toBeInTheDocument();
expect(getByText('MetaMask fee')).toBeInTheDocument();
expect(getByText('Estimated gas fee')).toBeInTheDocument();
expect(getByText('0.00008 ETH')).toBeInTheDocument();
expect(getByText('Max fee:')).toBeInTheDocument();
expect(getByText('Swap')).toBeInTheDocument();
});
it('renders the component with EIP-1559 enabled', () => {
const state = createSwapsMockStore();
state.metamask.networkDetails = {
EIPS: {
1559: true,
},
};
const store = configureMockStore(middleware)(state);
const props = createProps();
const { getByText } = renderWithProvider(<ReviewQuote {...props} />, store);
expect(getByText('New quotes in')).toBeInTheDocument();
expect(getByText('Quote rate')).toBeInTheDocument();
expect(getByText('MetaMask fee')).toBeInTheDocument();
expect(getByText('Estimated gas fee')).toBeInTheDocument();
expect(getByText('0.00008 ETH')).toBeInTheDocument();
expect(getByText('Max fee:')).toBeInTheDocument();
expect(getByText('Swap')).toBeInTheDocument();
});
it('renders text for token approval', () => {
const state = createSwapsMockStore();
state.metamask.swapsState.quotes.TEST_AGG_2.approvalNeeded = {
data: '0x095ea7b300000000000000000000000095e6f48254609a6ee006f7d493c8e5fb97094cef0000000000000000000000000000000000000000004a817c7ffffffdabf41c00',
to: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
amount: '0',
from: '0x2369267687A84ac7B494daE2f1542C40E37f4455',
gas: '12',
gasPrice: '34',
};
const store = configureMockStore(middleware)(state);
const props = createProps();
const { getByText } = renderWithProvider(<ReviewQuote {...props} />, store);
expect(getByText('New quotes in')).toBeInTheDocument();
expect(getByText('Quote rate')).toBeInTheDocument();
expect(getByText('MetaMask fee')).toBeInTheDocument();
expect(getByText('Estimated gas fee')).toBeInTheDocument();
expect(getByText('0.00008 ETH')).toBeInTheDocument();
expect(getByText('Max fee:')).toBeInTheDocument();
expect(getByText('enable DAI')).toBeInTheDocument();
expect(getByText('Edit limit')).toBeInTheDocument();
expect(getByText('Swap')).toBeInTheDocument();
});
});

View File

@ -0,0 +1,121 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { I18nContext } from '../../../contexts/i18n';
import Button from '../../../components/ui/button';
import Box from '../../../components/ui/box';
import Popover from '../../../components/ui/popover';
import Typography from '../../../components/ui/typography';
import {
TypographyVariant,
DISPLAY,
TextVariant,
FLEX_DIRECTION,
FONT_WEIGHT,
TextColor,
} from '../../../helpers/constants/design-system';
import { Text } from '../../../components/component-library';
import PopoverCustomBackground from '../popover-custom-background/popover-custom-background';
export default function SmartTransactionsPopover({
onEnableSmartTransactionsClick,
onCloseSmartTransactionsOptInPopover,
}) {
const t = useContext(I18nContext);
return (
<Popover
title={t('smartSwapsAreHere')}
footer={
<>
<Button type="primary" onClick={onEnableSmartTransactionsClick}>
{t('enableSmartSwaps')}
</Button>
<Box marginTop={1}>
<Text variant={TextVariant.bodyMd} as="h6">
<Button
type="link"
onClick={onCloseSmartTransactionsOptInPopover}
className="smart-transactions-popover__no-thanks-link"
>
{t('noThanksVariant2')}
</Button>
</Text>
</Box>
</>
}
footerClassName="smart-transactions-popover__footer"
className="smart-transactions-popover"
CustomBackground={() => {
return (
<PopoverCustomBackground
onClose={onCloseSmartTransactionsOptInPopover}
/>
);
}}
>
<Box
paddingRight={6}
paddingLeft={6}
paddingTop={0}
paddingBottom={0}
display={DISPLAY.FLEX}
className="smart-transactions-popover__content"
>
<Box
marginTop={0}
marginBottom={4}
display={DISPLAY.FLEX}
flexDirection={FLEX_DIRECTION.COLUMN}
>
<img
src="./images/logo/smart-transactions-header.png"
alt={t('swapSwapSwitch')}
/>
</Box>
<Typography variant={TypographyVariant.H7} marginTop={0}>
{t('smartSwapsDescription')}
</Typography>
<Typography
as="ul"
variant={TypographyVariant.H7}
fontWeight={FONT_WEIGHT.BOLD}
marginTop={3}
>
<li>{t('stxBenefit1')}</li>
<li>{t('stxBenefit2')}</li>
<li>{t('stxBenefit3')}</li>
<li>
{t('stxBenefit4')}
<Typography
as="span"
fontWeight={FONT_WEIGHT.NORMAL}
variant={TypographyVariant.H7}
>
{' *'}
</Typography>
</li>
</Typography>
<Typography
variant={TypographyVariant.H8}
color={TextColor.textAlternative}
boxProps={{ marginTop: 3 }}
>
{t('smartSwapsSubDescription')}&nbsp;
<Typography
as="span"
fontWeight={FONT_WEIGHT.BOLD}
variant={TypographyVariant.H8}
color={TextColor.textAlternative}
>
{t('stxYouCanOptOut')}&nbsp;
</Typography>
</Typography>
</Box>
</Popover>
);
}
SmartTransactionsPopover.propTypes = {
onEnableSmartTransactionsClick: PropTypes.func.isRequired,
onCloseSmartTransactionsOptInPopover: PropTypes.func.isRequired,
};

View File

@ -0,0 +1,102 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';
import { I18nContext } from '../../../contexts/i18n';
import Box from '../../../components/ui/box';
import {
DISPLAY,
AlignItems,
SEVERITIES,
Size,
TextVariant,
BLOCK_SIZES,
} from '../../../helpers/constants/design-system';
import { GasRecommendations } from '../../../../shared/constants/gas';
import {
BannerAlert,
Text,
ButtonLink,
} from '../../../components/component-library';
export default function ViewQuotePriceDifference(props) {
const {
usedQuote,
sourceTokenValue,
destinationTokenValue,
onAcknowledgementClick,
acknowledged,
priceSlippageFromSource,
priceSlippageFromDestination,
priceDifferencePercentage,
priceSlippageUnknownFiatValue,
} = props;
const t = useContext(I18nContext);
let priceDifferenceTitle = t('swapPriceUnavailableTitle');
let priceDifferenceMessage = t('swapPriceUnavailableDescription');
let priceDifferenceClass = GasRecommendations.high;
if (!priceSlippageUnknownFiatValue) {
priceDifferenceTitle = t('swapPriceDifferenceTitle', [
priceDifferencePercentage,
]);
priceDifferenceMessage = t('swapPriceDifference', [
sourceTokenValue, // Number of source token to swap
usedQuote.sourceTokenInfo.symbol, // Source token symbol
priceSlippageFromSource, // Source tokens total value
destinationTokenValue, // Number of destination tokens in return
usedQuote.destinationTokenInfo.symbol, // Destination token symbol,
priceSlippageFromDestination, // Destination tokens total value
]);
priceDifferenceClass = usedQuote.priceSlippage.bucket;
}
const severity =
priceDifferenceClass === GasRecommendations.high
? SEVERITIES.DANGER
: SEVERITIES.WARNING;
return (
<Box display={DISPLAY.FLEX} marginTop={2}>
<BannerAlert
title={priceDifferenceTitle}
severity={severity}
width={BLOCK_SIZES.FULL}
data-testid="mm-banner-alert"
>
<Box>
<Text
variant={TextVariant.bodyMd}
as="h6"
data-testid="mm-banner-alert-notification-text"
>
{priceDifferenceMessage}
</Text>
{!acknowledged && (
<ButtonLink
size={Size.INHERIT}
textProps={{
variant: TextVariant.bodyMd,
alignItems: AlignItems.flexStart,
}}
onClick={onAcknowledgementClick}
>
{t('swapAnyway')}
</ButtonLink>
)}
</Box>
</BannerAlert>
</Box>
);
}
ViewQuotePriceDifference.propTypes = {
usedQuote: PropTypes.object,
sourceTokenValue: PropTypes.string,
destinationTokenValue: PropTypes.string,
onAcknowledgementClick: PropTypes.func,
acknowledged: PropTypes.bool,
priceSlippageFromSource: PropTypes.string,
priceSlippageFromDestination: PropTypes.string,
priceDifferencePercentage: PropTypes.number,
priceSlippageUnknownFiatValue: PropTypes.bool,
};

View File

@ -0,0 +1,150 @@
import React from 'react';
import configureMockStore from 'redux-mock-store';
import { renderWithProvider } from '../../../../test/lib/render-helpers';
import { NETWORK_TYPES } from '../../../../shared/constants/network';
import { GasRecommendations } from '../../../../shared/constants/gas';
import ViewQuotePriceDifference from './view-quote-price-difference';
describe('View Price Quote Difference', () => {
const mockState = {
metamask: {
tokens: [],
providerConfig: { type: NETWORK_TYPES.RPC, nickname: '', rpcUrl: '' },
preferences: { showFiatInTestnets: true },
currentCurrency: 'usd',
conversionRate: 600.0,
},
};
const mockStore = configureMockStore()(mockState);
// Sample transaction is 1 ETH to ~42.880915 LINK.
const DEFAULT_PROPS = {
usedQuote: {
trade: {
data: '0x5f575529000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000007756e69737761700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca0000000000000000000000000000000000000000000000000dc1a09f859b20000000000000000000000000000000000000000000000000024855454cb32d335f0000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000005fc7b7100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f161421c8e0000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca',
from: '0xd7440fdcb70a9fba55dfe06942ddbc17679c90ac',
value: '0xde0b6b3a7640000',
gas: '0xbbfd0',
to: '0x881D40237659C251811CEC9c364ef91dC08D300C',
},
sourceAmount: '1000000000000000000',
destinationAmount: '42947749216634160067',
error: null,
sourceToken: '0x0000000000000000000000000000000000000000',
destinationToken: '0x514910771af9ca656af840dff83e8264ecf986ca',
approvalNeeded: null,
maxGas: 770000,
averageGas: 210546,
estimatedRefund: 80000,
fetchTime: 647,
aggregator: 'uniswap',
aggType: 'DEX',
fee: 0.875,
gasMultiplier: 1.5,
priceSlippage: {
ratio: 1.007876641534847,
calculationError: '',
bucket: GasRecommendations.low,
sourceAmountInETH: 1,
destinationAmountInETH: 0.9921849150875727,
},
slippage: 2,
sourceTokenInfo: {
symbol: 'ETH',
name: 'Ether',
address: '0x0000000000000000000000000000000000000000',
decimals: 18,
iconUrl: 'images/black-eth-logo.svg',
},
destinationTokenInfo: {
address: '0x514910771af9ca656af840dff83e8264ecf986ca',
symbol: 'LINK',
decimals: 18,
occurances: 12,
iconUrl:
'https://cloudflare-ipfs.com/ipfs/QmQhZAdcZvW9T2tPm516yHqbGkfhyZwTZmLixW9MXJudTA',
},
ethFee: '0.011791',
ethValueOfTokens: '0.99220724791716534441',
overallValueOfQuote: '0.98041624791716534441',
metaMaskFeeInEth: '0.00875844985551091729',
isBestQuote: true,
savings: {
performance: '0.00207907025112527799',
fee: '0.005581',
metaMaskFee: '0.00875844985551091729',
total: '-0.0010983796043856393',
medianMetaMaskFee: '0.00874009740688812165',
},
},
sourceTokenValue: '1',
destinationTokenValue: '42.947749',
};
it('displays an error when in low bucket', () => {
const { getByText, getByTestId } = renderWithProvider(
<ViewQuotePriceDifference {...DEFAULT_PROPS} />,
mockStore,
);
expect(getByTestId('mm-banner-alert')).toHaveClass(
'mm-banner-alert--severity-warning',
);
expect(
getByText('You are about to swap 1 ETH (~) for 42.947749 LINK (~).'),
).toBeInTheDocument();
expect(getByText('Swap anyway')).toBeInTheDocument();
});
it('displays an error when in medium bucket', () => {
const props = { ...DEFAULT_PROPS };
props.usedQuote.priceSlippage.bucket = GasRecommendations.medium;
const { getByText, getByTestId } = renderWithProvider(
<ViewQuotePriceDifference {...props} />,
mockStore,
);
expect(getByTestId('mm-banner-alert')).toHaveClass(
'mm-banner-alert--severity-warning',
);
expect(
getByText('You are about to swap 1 ETH (~) for 42.947749 LINK (~).'),
).toBeInTheDocument();
expect(getByText('Swap anyway')).toBeInTheDocument();
});
it('displays an error when in high bucket', () => {
const props = { ...DEFAULT_PROPS };
props.usedQuote.priceSlippage.bucket = GasRecommendations.high;
const { getByText, getByTestId } = renderWithProvider(
<ViewQuotePriceDifference {...props} />,
mockStore,
);
expect(getByTestId('mm-banner-alert')).toHaveClass(
'mm-banner-alert--severity-danger',
);
expect(
getByText('You are about to swap 1 ETH (~) for 42.947749 LINK (~).'),
).toBeInTheDocument();
expect(getByText('Swap anyway')).toBeInTheDocument();
});
it('displays a fiat error when calculationError is present', () => {
const props = { ...DEFAULT_PROPS, priceSlippageUnknownFiatValue: true };
props.usedQuote.priceSlippage.calculationError =
'Could not determine price.';
const { getByText, getByTestId } = renderWithProvider(
<ViewQuotePriceDifference {...props} />,
mockStore,
);
expect(getByTestId('mm-banner-alert')).toHaveClass(
'mm-banner-alert--severity-danger',
);
expect(getByText('Check your rate before proceeding')).toBeInTheDocument();
expect(
getByText(
'Price impact could not be determined due to lack of market price data. Please confirm that you are comfortable with the amount of tokens you are about to receive before swapping.',
),
).toBeInTheDocument();
expect(getByText('Swap anyway')).toBeInTheDocument();
});
});

View File

@ -59,6 +59,7 @@ exports[`SearchableItemList renders the component with initial props 2`] = `
>
<span
class="searchable-item-list__primary-label"
data-testid="searchable-item-list-primary-label"
>
primaryLabel
</span>

View File

@ -142,7 +142,6 @@
&__labels {
display: flex;
justify-content: space-between;
max-width: 237px;
flex: 1;
-moz-animation: fadein 1s;
-webkit-animation: fadein 1s;

View File

@ -23,6 +23,7 @@ exports[`ItemList renders the component with initial props 1`] = `
>
<span
class="searchable-item-list__primary-label"
data-testid="searchable-item-list-primary-label"
>
primaryLabel
</span>

View File

@ -59,6 +59,7 @@ export default function ItemList({
listContainerClassName,
)}
ref={containerRef}
data-testid="searchable-item-list-list-container"
>
{results.slice(0, maxListItems).map((result, i) => {
if (hideItemIf?.(result)) {
@ -107,7 +108,10 @@ export default function ItemList({
<div className="searchable-item-list__labels">
<div className="searchable-item-list__item-labels">
{primaryLabel ? (
<span className="searchable-item-list__primary-label">
<span
className="searchable-item-list__primary-label"
data-testid="searchable-item-list-primary-label"
>
{primaryLabel}
</span>
) : null}
@ -134,7 +138,11 @@ export default function ItemList({
) : null}
</div>
{result.notImported && (
<Button type="primary" onClick={onClick}>
<Button
type="primary"
onClick={onClick}
data-testid="searchable-item-list-import-button"
>
{t('import')}
</Button>
)}
@ -148,7 +156,7 @@ export default function ItemList({
key="searchable-item-list-item-last"
>
<ActionableMessage
message={t('addCustomTokenByContractAddress', [
message={t('addTokenByContractAddress', [
<a
key="searchable-item-list__etherscan-link"
onClick={() => {

View File

@ -113,7 +113,7 @@
color: var(--color-primary-inverse);
&:hover {
color: var(--color-text-default);
color: var(--color-icon-alternative);
}
}

View File

@ -48,7 +48,7 @@
}
&__light-grey {
color: var(--color-border-muted);
color: var(--color-text-muted);
}
&__row {

Some files were not shown because too many files have changed in this diff Show More