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

Merge remote-tracking branch 'origin/develop' into master-sync

* origin/develop: (131 commits)
  Update `protobufjs` and remove obsolete advisory exclusion (#14841)
  Include snap version in pill (#14803)
  Update PULL_REQUEST_TEMPLATE.md (#14790)
  fix: keystone transaction qrcode has no white spacing (#14798)
  Snap notifications integration (#14605)
  Upgrade @metamask/eth-ledger-bridge-keyring (#14799)
  snaps-skunkworks@0.15.0 (#14772)
  Fix proptype errors in network dropdown, tx list item details, and account details modal tests (#14747)
  Ensure transaction type is correctly updated on edit (#14721)
  Add fiat onboarding for AVAX and MATIC through Wyre (#14683)
  Bump @metamask/contract-metadata from 1.33.0 to 1.35.0 (#14791)
  Slight cleanup of constants/transactions, useTransactionDisplayData, and TransactionIcon (#14784)
  Migrate the "estimateGas" API call to "getFees" for STX (#14767)
  Ignore advisory GHSA-wm7h-9275-46v2 (#14789)
  Adding flag for MV3 (#14762)
  Add types to send state (#14740)
  Remove site origin on snap install (#14752)
  Update design tokens library from 1.5 to 1.6 WIP (#14732)
  Enables the "Safe Transaction From" copy for safeTransferFrom transactions (#14769)
  remove draft transaction (#14701)
  ...
This commit is contained in:
Mark Stacey 2022-06-03 11:53:40 -02:30
commit e6d5af5f9a
483 changed files with 49758 additions and 22228 deletions

View File

@ -670,6 +670,13 @@ jobs:
- run:
name: test:coverage:jest
command: yarn test:coverage:jest
- run:
name: Validate coverage thresholds
command: |
if ! git diff --exit-code jest.config.js development/jest.config.js; then
echo "Detected changes in coverage thresholds"
exit 1
fi
- persist_to_workspace:
root: .
paths:

View File

@ -5,12 +5,12 @@ set -u
set -o pipefail
# To get the latest version, see <https://www.ubuntuupdates.org/ppa/google_chrome?dist=stable>
CHROME_VERSION='100.0.4896.60-1'
CHROME_VERSION='101.0.4951.54-1'
CHROME_BINARY="google-chrome-stable_${CHROME_VERSION}_amd64.deb"
CHROME_BINARY_URL="https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/${CHROME_BINARY}"
# To retrieve this checksum, run the `wget` and `shasum` commands below
CHROME_BINARY_SHA512SUM='d7a98777650e8218fef4acc8466d4ddf5e234b97fbc16c33f38f69f9ebfe7b6bb6827a90aad15ea729d7c1bacfa78488c5d5194b531f00f454302dd9c0957e4e'
CHROME_BINARY_SHA512SUM='46ebc53c806f179a5f9ab570c1097c2d35fc437dd4f5bd3fb34e758dc4f306771ceb05f8df7d450584355abf35bae34c0e70903e4f0312e2d3caa34491c8f622'
wget -O "${CHROME_BINARY}" -t 5 "${CHROME_BINARY_URL}"

View File

@ -6,13 +6,13 @@ set -x
set -o pipefail
# use `improved-yarn-audit` since that allows for exclude
# exclude 1002401 until we remove use of 3Box, 1002581 until we can find a better solution
yarn run improved-yarn-audit --ignore-dev-deps --min-severity moderate --exclude GHSA-93q8-gq69-wqmw,GHSA-257v-vj4p-3w2h,GHSA-fwr7-v2mv-hh25
audit_status="$?"
# exclusions are in .iyarc now
yarn run improved-yarn-audit \
--ignore-dev-deps \
--min-severity moderate \
--fail-on-missing-exclusions
# Use a bitmask to ignore INFO and LOW severity audit results
# See here: https://yarnpkg.com/lang/en/docs/cli/audit/
audit_status="$(( audit_status & 11100 ))"
audit_status="$?"
if [[ "$audit_status" != 0 ]]
then

View File

@ -44,14 +44,6 @@ module.exports = {
path.resolve(__dirname, '.eslintrc.babel.js'),
path.resolve(__dirname, '.eslintrc.typescript-compat.js'),
],
parserOptions: {
sourceType: 'module',
},
rules: {
// This rule does not work with CommonJS modules. We will just have to
// trust that all of the files specified above are indeed modules.
'import/unambiguous': 'off',
},
settings: {
'import/resolver': {
// When determining the location of a `require()` call, use Node's

View File

@ -1,7 +1,7 @@
name: Bug Report
description: Using MetaMask, but it's not working as you expect?
title: "[Bug]: "
labels: ["bug"]
labels: ["type-bug"]
body:
- type: markdown
attributes:

View File

@ -15,7 +15,7 @@ This is a problem because ...
In order to solve this problem, this pull request ...
-->
## More information
## More Information
<!--
Are there any issues, Slack conversations, Zendesk issues, user stories, etc. reviewers should consult to understand this pull request better? For instance:
@ -36,7 +36,7 @@ Are there any issues, Slack conversations, Zendesk issues, user stories, etc. re
<!-- How does it look now? Drag your file(s) below this line: -->
## Manual testing steps
## Manual Testing Steps
<!--
How should reviewers and QA manually test your changes? For instance:
@ -45,3 +45,15 @@ How should reviewers and QA manually test your changes? For instance:
- Do this
- Then do this
-->
## Pre-Merge Checklist
- [ ] PR template is filled out
- [ ] **IF** this PR fixes a bug, a test that _would have_ caught the bug has been added
- [ ] PR is linked to the appropriate GitHub issue
- [ ] PR has been added to the appropriate release Milestone
### + If there are functional changes:
- [ ] Manual testing complete & passed
- [ ] "Extension QA Board" label has been applied

View File

@ -21,7 +21,7 @@ jobs:
uses: actions/checkout@v2
- name: crowdin action
uses: crowdin/github-action@9237b4cb361788dfce63feb2e2f15c09e2fe7415
uses: crowdin/github-action@a3160b9e5a9e00739392c23da5e580c6cabe526d
with:
upload_translations: true
download_translations: true

4
.iyarc Normal file
View File

@ -0,0 +1,4 @@
# improved-yarn-audit advisory exclusions
GHSA-93q8-gq69-wqmw
GHSA-257v-vj4p-3w2h
GHSA-wm7h-9275-46v2

View File

@ -67,6 +67,7 @@ var(--color-text-muted)
/** Icons */
var(--color-icon-default)
var(--color-icon-alternative)
var(--color-icon-muted)
/** Borders */
@ -75,20 +76,20 @@ var(--color-border-muted)
/** Overlays */
var(--color-overlay-default)
var(--color-overlay-inverse)
var(--color-overlay-inverse) [DEPRECATED]
/** User Actions */
var(--color-primary-default)
var(--color-primary-alternative)
var(--color-primary-muted)
var(--color-primary-inverse)
var(--color-primary-disabled)
var(--color-primary-disabled) [DEPRECATED]
var(--color-secondary-default)
var(--color-secondary-alternative)
var(--color-secondary-muted)
var(--color-secondary-inverse)
var(--color-secondary-disabled)
var(--color-secondary-default) [DEPRECATED]
var(--color-secondary-alternative) [DEPRECATED]
var(--color-secondary-muted) [DEPRECATED]
var(--color-secondary-inverse) [DEPRECATED]
var(--color-secondary-disabled) [DEPRECATED]
/** States */
/** Error */
@ -96,28 +97,28 @@ var(--color-error-default)
var(--color-error-alternative)
var(--color-error-muted)
var(--color-error-inverse)
var(--color-error-disabled)
var(--color-error-disabled) [DEPRECATED]
/** Warning */
var(--color-warning-default)
var(--color-warning-alternative)
var(--color-warning-alternative) [DEPRECATED]
var(--color-warning-muted)
var(--color-warning-inverse)
var(--color-warning-disabled)
var(--color-warning-disabled) [DEPRECATED]
/** Success */
var(--color-success-default)
var(--color-success-alternative)
var(--color-success-alternative) [DEPRECATED]
var(--color-success-muted)
var(--color-success-inverse)
var(--color-success-disabled)
var(--color-success-disabled) [DEPRECATED]
/** Info */
var(--color-info-default)
var(--color-info-alternative)
var(--color-info-alternative) [DEPRECATED]
var(--color-info-muted)
var(--color-info-inverse)
var(--color-info-disabled)
var(--color-info-disabled) [DEPRECATED]
```
### **Component colors** (tier 3)

View File

@ -112,6 +112,65 @@ const state = {
],
metamask: {
tokenList: {
'0x514910771af9ca656af840dff83e8264ecf986ca': {
address: '0x514910771af9ca656af840dff83e8264ecf986ca',
symbol: 'LINK',
decimals: 18,
name: 'ChainLink Token',
iconUrl: 'https://crypto.com/price/coin-data/icon/LINK/color_icon.png',
aggregators: [
'Aave',
'Bancor',
'CMC',
'Crypto.com',
'CoinGecko',
'1inch',
'Paraswap',
'PMM',
'Zapper',
'Zerion',
'0x',
],
occurrences: 12,
unlisted: false
},
'0xc00e94cb662c3520282e6f5717214004a7f26888': {
address: '0xc00e94cb662c3520282e6f5717214004a7f26888',
symbol: 'COMP',
decimals: 18,
name: 'Compound',
iconUrl: 'https://crypto.com/price/coin-data/icon/COMP/color_icon.png',
aggregators: [
'Bancor',
'CMC',
'Crypto.com',
'CoinGecko',
'1inch',
'Paraswap',
'PMM',
'Zapper',
'Zerion',
'0x',
],
occurrences: 12,
unlisted: false
},
'0xfffffffff15abf397da76f1dcc1a1604f45126db': {
address: '0xfffffffff15abf397da76f1dcc1a1604f45126db',
symbol: 'FSW',
decimals: 18,
name: 'Falconswap',
iconUrl: 'https://assets.coingecko.com/coins/images/12256/thumb/falconswap.png?1598534184',
aggregators: [
'CoinGecko',
'1inch',
'Paraswap',
'Zapper',
'Zerion',
],
occurrences: 12,
unlisted: false
},
'0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f': {
address: '0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f',
symbol: 'SNX',
@ -425,6 +484,35 @@ const state = {
decimals: 18,
},
],
detectedTokens: [
{
address: "0x514910771AF9Ca656af840dff83E8264EcF986CA",
decimals: 18,
symbol: "LINK",
image: "https://crypto.com/price/coin-data/icon/LINK/color_icon.png",
aggregators:[
"coinGecko","oneInch","paraswap","zapper","zerion"
]
},
{
address: "0xc00e94Cb662C3520282E6f5717214004A7f26888",
decimals: 18,
symbol: "COMP",
image: "https://crypto.com/price/coin-data/icon/COMP/color_icon.png",
aggregators:[
"bancor","cmc","cryptocom","coinGecko","oneInch","paraswap","pmm","zapper","zerion","zeroEx"
]
},
{
address: "0xfffffffFf15AbF397dA76f1dcc1A1604F45126DB",
decimals: 18,
symbol: "FSW",
image: "https://assets.coingecko.com/coins/images/12256/thumb/falconswap.png?1598534184",
aggregators:[
"aave", "cmc","coinGecko","oneInch","paraswap","zapper","zerion"
]
}
],
pendingTokens: {},
customNonceValue: '',
send: {
@ -1248,20 +1336,7 @@ const state = {
method: 'eth_accounts',
methodType: 'restricted',
origin: 'https://metamask.io',
request: {
method: 'eth_accounts',
params: [],
jsonrpc: '2.0',
id: 522690215,
origin: 'https://metamask.io',
tabId: 5,
},
requestTime: 1602643170686,
response: {
id: 522690215,
jsonrpc: '2.0',
result: [],
},
responseTime: 1602643170688,
success: true,
},
@ -1270,20 +1345,7 @@ const state = {
method: 'eth_accounts',
methodType: 'restricted',
origin: 'https://widget.getacute.io',
request: {
method: 'eth_accounts',
params: [],
jsonrpc: '2.0',
id: 1620464600,
origin: 'https://widget.getacute.io',
tabId: 5,
},
requestTime: 1602643172935,
response: {
id: 1620464600,
jsonrpc: '2.0',
result: [],
},
responseTime: 1602643172935,
success: true,
},
@ -1292,19 +1354,7 @@ const state = {
method: 'eth_accounts',
methodType: 'restricted',
origin: 'https://app.uniswap.org',
request: {
method: 'eth_accounts',
jsonrpc: '2.0',
id: 4279100021,
origin: 'https://app.uniswap.org',
tabId: 5,
},
requestTime: 1620710669962,
response: {
id: 4279100021,
jsonrpc: '2.0',
result: [],
},
responseTime: 1620710669963,
success: true,
},
@ -1313,19 +1363,7 @@ const state = {
method: 'eth_requestAccounts',
methodType: 'restricted',
origin: 'https://app.uniswap.org',
request: {
method: 'eth_requestAccounts',
jsonrpc: '2.0',
id: 4279100022,
origin: 'https://app.uniswap.org',
tabId: 5,
},
requestTime: 1620710686872,
response: {
id: 4279100022,
jsonrpc: '2.0',
result: ['0x64a845a5b02460acf8a3d84503b0d68d028b4bb4'],
},
responseTime: 1620710693187,
success: true,
},
@ -1334,19 +1372,7 @@ const state = {
method: 'eth_requestAccounts',
methodType: 'restricted',
origin: 'https://app.uniswap.org',
request: {
method: 'eth_requestAccounts',
jsonrpc: '2.0',
id: 4279100023,
origin: 'https://app.uniswap.org',
tabId: 5,
},
requestTime: 1620710693204,
response: {
id: 4279100023,
jsonrpc: '2.0',
result: ['0x64a845a5b02460acf8a3d84503b0d68d028b4bb4'],
},
responseTime: 1620710693213,
success: true,
},
@ -1355,20 +1381,7 @@ const state = {
method: 'eth_accounts',
methodType: 'restricted',
origin: 'https://app.uniswap.org',
request: {
method: 'eth_accounts',
params: [],
jsonrpc: '2.0',
id: 4279100034,
origin: 'https://app.uniswap.org',
tabId: 5,
},
requestTime: 1620710712072,
response: {
id: 4279100034,
jsonrpc: '2.0',
result: ['0x64a845a5b02460acf8a3d84503b0d68d028b4bb4'],
},
responseTime: 1620710712075,
success: true,
},

View File

@ -60,9 +60,23 @@ You can run the linter by itself with `yarn lint`, and you can automatically fix
Our e2e test suite can be run on either Firefox or Chrome. In either case, start by creating a test build by running `yarn build:test`.
Firefox e2e tests can be run with `yarn test:e2e:firefox`.
- Firefox e2e tests can be run with `yarn test:e2e:firefox`.
Chrome e2e tests can be run with `yarn test:e2e:chrome`, but they will only work if you have Chrome v79 installed. Update the `chromedriver` package to a version matching your local Chrome installation to run e2e tests on newer Chrome versions.
- Chrome e2e tests can be run with `yarn test:e2e:chrome`. The `chromedriver` package major version must match the major version of your local Chrome installation. If they don't match, update whichever is behind before running Chrome e2e tests.
- Single e2e tests can be run with `yarn test:e2e:single test/e2e/tests/TEST_NAME.spec.js` along with the options below.
```console
--browser Set the browser used; either 'chrome' or 'firefox'.
--leave-running Leaves the browser running after a test fails, along with anything else
that the test used (ganache, the test dapp, etc.).
--retries Set how many times the test should be retried upon failure. Default is 0.
```
An example for running `account-details` testcase with chrome and leaving the browser open would be:
`yarn test:e2e:single test/e2e/tests/account-details.spec.js --browser=chrome --leave-running`
### Changing dependencies

View File

@ -131,10 +131,10 @@
"message": "ማሰሺያዎት አልተደገፈም..."
},
"buyWithWyre": {
"message": "ETH በ Wyre ይግዙ"
"message": "$1 በ Wyre ይግዙ"
},
"buyWithWyreDescription": {
"message": "Wyre ክሬዲት ካርድ ተጠቅመው ETH በቀጥታ በ MetaMask መለያዎ ላይ እንዲያስቀምጡ ያስችልዎታል።"
"message": "Wyre ክሬዲት ካርድ ተጠቅመው $1 በቀጥታ በ MetaMask መለያዎ ላይ እንዲያስቀምጡ ያስችልዎታል።"
},
"bytes": {
"message": "ባይት"

View File

@ -148,7 +148,7 @@
"message": "قم بشراء عملة إيثير بواسطة Wyre"
},
"buyWithWyreDescription": {
"message": "يتيح لك Wyre استخدام بطاقة ائتمان لإيداع ETH مباشرة في حساب MetaMask الخاص بك."
"message": "يتيح لك Wyre استخدام بطاقة ائتمان لإيداع 1$ مباشرة في حساب MetaMask الخاص بك."
},
"bytes": {
"message": "بايتات"

View File

@ -137,10 +137,10 @@
"message": "Браузърът ви не се поддържа ..."
},
"buyWithWyre": {
"message": "Купете ETH с Wyre"
"message": "Купете $1 с Wyre"
},
"buyWithWyreDescription": {
"message": "Wyre ви позволява да използвате кредитна карта, за да депозирате ETH право във вашата MetaMask сметка."
"message": "Wyre ви позволява да използвате кредитна карта, за да депозирате $1 право във вашата MetaMask сметка."
},
"bytes": {
"message": "Байта"

View File

@ -137,10 +137,10 @@
"message": "আপনার ব্রাউজার সমর্থিত নয়..."
},
"buyWithWyre": {
"message": "Wyre দিয়ে ETH ক্রয় করুন"
"message": "Wyre দিয়ে $1 ক্রয় করুন"
},
"buyWithWyreDescription": {
"message": "Wyre আপনার MetaMask অ্যাকাউন্টে সরাসরি ETH জমা করতে আপনাকে একটি ক্রেডিট কার্ড ব্যবহার করতে দেয়।"
"message": "Wyre আপনার MetaMask অ্যাকাউন্টে সরাসরি $1 জমা করতে আপনাকে একটি ক্রেডিট কার্ড ব্যবহার করতে দেয়।"
},
"bytes": {
"message": "বাইটস"

View File

@ -137,10 +137,10 @@
"message": "El teu navegador no és suportat..."
},
"buyWithWyre": {
"message": "Compra ETH amb Wyre"
"message": "Compra $1 amb Wyre"
},
"buyWithWyreDescription": {
"message": "Wyre et permet utilitzar una targeta de crèdit per dipositar ETH directe al teu compte MetaMask."
"message": "Wyre et permet utilitzar una targeta de crèdit per dipositar $1 directe al teu compte MetaMask."
},
"cancel": {
"message": "Cancel·la"

View File

@ -137,10 +137,10 @@
"message": "Din browser er ikke understøttet..."
},
"buyWithWyre": {
"message": "Køb ETH med Wyre"
"message": "Køb $1 med Wyre"
},
"buyWithWyreDescription": {
"message": "Wyre lader dig bruge et kreditkort til at indbetale ETH på din MetaMask-konto."
"message": "Wyre lader dig bruge et kreditkort til at indbetale $1 på din MetaMask-konto."
},
"bytes": {
"message": "Byte"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -160,6 +160,10 @@
"addNetwork": {
"message": "Add Network"
},
"addNetworkTooltipWarning": {
"message": "This network connection relies on third parties. This connection may be less reliable or enable third-parties to track activity. $1",
"description": "$1 is Learn more link"
},
"addSuggestedTokens": {
"message": "Add Suggested Tokens"
},
@ -199,9 +203,6 @@
"affirmAgree": {
"message": "I Agree"
},
"aggregatorFeeCost": {
"message": "Aggregator network fee"
},
"airgapVault": {
"message": "AirGap Vault"
},
@ -259,12 +260,6 @@
"message": "MetaMask Flask",
"description": "The name of the application (Flask)"
},
"approvalAndAggregatorTxFeeCost": {
"message": "Approval and aggregator network fee"
},
"approvalTxGasCost": {
"message": "Approval Tx Gas Cost"
},
"approve": {
"message": "Approve spend limit"
},
@ -416,6 +411,14 @@
"message": "Buy $1",
"description": "$1 is the ticker symbol of a an asset the user is being prompted to purchase"
},
"buyCryptoWithCoinbasePay": {
"message": "Buy $1 with Coinbase Pay",
"description": "$1 represents the crypto symbol to be purchased"
},
"buyCryptoWithCoinbasePayDescription": {
"message": "You can easily buy or transfer crypto with your Coinbase account.",
"description": "$1 represents the crypto symbol to be purchased"
},
"buyCryptoWithMoonPay": {
"message": "Buy $1 with MoonPay",
"description": "$1 represents the cypto symbol to be purchased"
@ -432,10 +435,10 @@
"description": "$1 represents the crypto symbol to be purchased"
},
"buyWithWyre": {
"message": "Buy ETH with Wyre"
"message": "Buy $1 with Wyre"
},
"buyWithWyreDescription": {
"message": "Wyre lets you use a debit card to deposit ETH right in to your MetaMask account."
"message": "Easy onboarding for purchases up to $ 1000. Fast interactive high limit purchase verification. Supports Debit/Credit Card, Apple Pay, Bank Transfers. Available in 100+ countries. Tokens deposit into your MetaMask Account"
},
"bytes": {
"message": "Bytes"
@ -622,6 +625,9 @@
"continue": {
"message": "Continue"
},
"continueToCoinbasePay": {
"message": "Continue to Coinbase Pay"
},
"continueToMoonPay": {
"message": "Continue to MoonPay"
},
@ -734,9 +740,6 @@
"customGasSubTitle": {
"message": "Increasing fee may decrease processing times, but it is not guaranteed."
},
"customNetworks": {
"message": "Custom networks"
},
"customSpendLimit": {
"message": "Custom Spend Limit"
},
@ -804,9 +807,6 @@
"decryptRequest": {
"message": "Decrypt request"
},
"defaultTheme": {
"message": "Default"
},
"delete": {
"message": "Delete"
},
@ -1187,10 +1187,6 @@
"externalExtension": {
"message": "External Extension"
},
"extraApprovalGas": {
"message": "+$1 approval gas",
"description": "Expresses an additional gas amount the user will have to pay, on top of some other displayed amount. $1 is a decimal amount of gas"
},
"failed": {
"message": "Failed"
},
@ -1251,6 +1247,10 @@
"message": "All Flask APIs are experimental. They may be changed or removed without notice, or they might stay on Flask indefinitely without ever being migrated to stable MetaMask. Use them at your own risk.",
"description": "This message warns developers about unstable Flask APIs"
},
"flaskWelcomeWarning4": {
"message": "Make sure to disable your regular MetaMask extension when using Flask.",
"description": "This message calls to pay attention about multiple versions of MetaMask running on the same site (Flask + Prod)"
},
"flaskWelcomeWarningAcceptButton": {
"message": "I accept the risks",
"description": "this text is shown on a button, which the user presses to confirm they understand the risks of using Flask"
@ -1442,6 +1442,9 @@
"hide": {
"message": "Hide"
},
"hideFullTransactionDetails": {
"message": "Hide full transaction details"
},
"hideSeedPhrase": {
"message": "Hide seed phrase"
},
@ -1471,6 +1474,12 @@
"history": {
"message": "History"
},
"ignoreAll": {
"message": "Ignore all"
},
"ignoreTokenWarning": {
"message": "If you hide tokens, they will not be shown in your wallet. However, you can still add them by searching for them."
},
"import": {
"message": "Import",
"description": "Button to import an account from a selected file"
@ -1520,6 +1529,10 @@
"importWallet": {
"message": "Import wallet"
},
"importWithCount": {
"message": "Import $1",
"description": "$1 will the number of detected tokens that are selected for importing, if all of them are selected then $1 will be all"
},
"importYourExisting": {
"message": "Import your existing wallet using a Secret Recovery Phrase"
},
@ -1714,6 +1727,9 @@
"levelArrow": {
"message": "level arrow"
},
"lightTheme": {
"message": "Light"
},
"likeToImportTokens": {
"message": "Would you like to import these tokens?"
},
@ -2027,6 +2043,13 @@
"newToMetaMask": {
"message": "New to MetaMask?"
},
"newTokensImportedMessage": {
"message": "Youve successfully imported $1.",
"description": "$1 is the string of symbols of all the tokens imported"
},
"newTokensImportedTitle": {
"message": "Token imported"
},
"newTotal": {
"message": "New Total"
},
@ -2109,6 +2132,9 @@
"notEnoughGas": {
"message": "Not Enough Gas"
},
"notifications": {
"message": "Notifications"
},
"notifications10ActionText": {
"message": "Visit in settings",
"description": "The 'call to action' on the button, or link, of the 'Visit in settings' notification. Upon clicking, users will be taken to settings page."
@ -2131,6 +2157,15 @@
"notifications11Title": {
"message": "Scam and security risks"
},
"notifications12ActionText": {
"message": "Enable dark mode"
},
"notifications12Description": {
"message": "Dark mode on Extension is finally here! To turn it on, go to Settings -> Experimental and select one of the display options: Light, Dark, System."
},
"notifications12Title": {
"message": "Wen dark mode? Now dark mode! 🕶️🦊"
},
"notifications1Description": {
"message": "MetaMask Mobile users can now swap tokens inside their mobile wallet. Scan the QR code to get the mobile app and start swapping.",
"description": "Description of a notification in the 'See What's New' popup. Describes the swapping on mobile feature."
@ -2220,6 +2255,19 @@
"notifications9Title": {
"message": "👓 We are making transactions easier to read."
},
"notificationsEmptyText": {
"message": "Nothing to see here."
},
"notificationsHeader": {
"message": "Notifications"
},
"notificationsInfos": {
"message": "$1 from $2",
"description": "$1 is the date at which the notification has been dispatched and $2 is the link to the snap that dispatched the notification."
},
"notificationsMarkAllAsRead": {
"message": "Mark all as read"
},
"numberOfNewTokensDetected": {
"message": "$1 new tokens found in this account",
"description": "$1 is the number of new tokens detected"
@ -2299,9 +2347,6 @@
"onlyConnectTrust": {
"message": "Only connect with sites you trust."
},
"onlyInteractWith": {
"message": "Only interact with entities you trust."
},
"openFullScreenForLedgerWebHid": {
"message": "Open MetaMask in full screen to connect your ledger via WebHID.",
"description": "Shown to the user on the confirm screen when they are viewing MetaMask in a popup window but need to connect their ledger via webhid."
@ -2321,6 +2366,9 @@
"origin": {
"message": "Origin"
},
"osTheme": {
"message": "System"
},
"padlock": {
"message": "Padlock"
},
@ -2394,6 +2442,10 @@
"message": "See address, account balance, activity and suggest transactions to approve",
"description": "The description for the `eth_accounts` permission"
},
"permission_longRunning": {
"message": "Run indefinitely.",
"description": "The description for the `endowment:long-running` permission"
},
"permission_manageBip44Keys": {
"message": "Control your \"$1\" accounts and assets.",
"description": "The description for the `snap_getBip44Entropy_*` permission. $1 is the name of a protocol, e.g. 'Filecoin'."
@ -2423,6 +2475,9 @@
"message": "+ $1 more",
"description": "$1 is a number of additional but unshown items in a list- this message will be shown in place of those items"
},
"popularCustomNetworks": {
"message": "Popular custom networks"
},
"preferredLedgerConnectionType": {
"message": "Preferred Ledger Connection Type",
"description": "A header for a dropdown in the advanced section of settings. Appears above the ledgerConnectionPreferenceDescription message"
@ -3621,6 +3676,10 @@
"tokenSymbol": {
"message": "Token Symbol"
},
"tokensFoundTitle": {
"message": "$1 new tokens found",
"description": "$1 is the number of new tokens detected"
},
"tooltipApproveButton": {
"message": "I understand"
},
@ -3926,6 +3985,9 @@
"walletCreationSuccessTitle": {
"message": "Wallet creation successful"
},
"warning": {
"message": "Warning"
},
"weak": {
"message": "Weak"
},

File diff suppressed because it is too large Load Diff

View File

@ -179,9 +179,6 @@
"affirmAgree": {
"message": "Acepto"
},
"aggregatorFeeCost": {
"message": "Cuota de red de agregador"
},
"alertDisableTooltip": {
"message": "Esto se puede modificar en \"Configuración > Alertas\""
},
@ -233,12 +230,6 @@
"message": "MetaMask Flask",
"description": "The name of the application (Flask)"
},
"approvalAndAggregatorTxFeeCost": {
"message": "Cuota de red de agregador y aprobación"
},
"approvalTxGasCost": {
"message": "Costo de gas por transacción de aprobación"
},
"approve": {
"message": "Aprobar límite de gastos"
},
@ -372,10 +363,10 @@
"message": "Comprar"
},
"buyWithWyre": {
"message": "Comprar ETH con Wyre"
"message": "Comprar $1 con Wyre"
},
"buyWithWyreDescription": {
"message": "Wyre le permite usar una tarjeta de débito para depositar ETH directamente en su cuenta de MetaMask."
"message": "Wyre le permite usar una tarjeta de débito para depositar $1 directamente en su cuenta de MetaMask."
},
"bytes": {
"message": "Bytes"
@ -1047,10 +1038,6 @@
"externalExtension": {
"message": "Extensión externa"
},
"extraApprovalGas": {
"message": "+$1 de gas por aprobación",
"description": "Expresses an additional gas amount the user will have to pay, on top of some other displayed amount. $1 is a decimal amount of gas"
},
"failed": {
"message": "Con errores"
},

View File

@ -137,10 +137,10 @@
"message": "Teie lehitsejat ei toetata..."
},
"buyWithWyre": {
"message": "Ostke ETH-d Wyre'iga"
"message": "Ostke $1 -d Wyre'iga"
},
"buyWithWyreDescription": {
"message": "Wyre võimaldab kasutada krediitkaarti, et teha ETH sissemakse otse MetaMaski kontole."
"message": "Wyre võimaldab kasutada krediitkaarti, et teha $1 sissemakse otse MetaMaski kontole."
},
"bytes": {
"message": "Baidid"

View File

@ -137,10 +137,10 @@
"message": "مرورگر شما پشتیبانی نمیشود"
},
"buyWithWyre": {
"message": "ETH را توسط Wyre خریداری نمایید"
"message": "$1 را توسط Wyre خریداری نمایید"
},
"buyWithWyreDescription": {
"message": "Wyre به شما اجازه میدهد تا یک کردیت کارت را جهت پرداخت ETH مستقیمًا به حساب MetaMask تان استفاده نمایید."
"message": "Wyre به شما اجازه میدهد تا یک کردیت کارت را جهت پرداخت 1$ مستقیمًا به حساب MetaMask تان استفاده نمایید."
},
"bytes": {
"message": "بایت ها"

View File

@ -137,10 +137,10 @@
"message": "Selaintasi ei tueta..."
},
"buyWithWyre": {
"message": "Osta ETH:ta Wyrella"
"message": "Osta $1 :ta Wyrella"
},
"buyWithWyreDescription": {
"message": "Wyre antaa sinun käyttää luottokorttia, jotta voit tallettaa ETH:ta suoraan MetaMask-tilillesi."
"message": "Wyre antaa sinun käyttää luottokorttia, jotta voit tallettaa $1 :ta suoraan MetaMask-tilillesi."
},
"bytes": {
"message": "Tavua"

View File

@ -122,10 +122,10 @@
"message": "Hindi sinusuportahan ang iyong Browser..."
},
"buyWithWyre": {
"message": "Bumili ng ETH gamit ang Wyre"
"message": "Bumili ng $1 gamit ang Wyre"
},
"buyWithWyreDescription": {
"message": "Binibigyang-daan ka ng Wyre na gumamit ng credit card para magdeposito ng ETH nang direkta sa iyong MetaMask account."
"message": "Binibigyang-daan ka ng Wyre na gumamit ng credit card para magdeposito ng $1 nang direkta sa iyong MetaMask account."
},
"cancel": {
"message": "Kanselahin"

File diff suppressed because it is too large Load Diff

View File

@ -140,7 +140,7 @@
"message": "רכישת את'ר עם Wyre"
},
"buyWithWyreDescription": {
"message": "Wyre מאפשרת לך להשתמש בכרטיס אשראי כדי להפקיד ETH ישירות בחשבון ה-MetaMask שלך."
"message": "Wyre מאפשרת לך להשתמש בכרטיס אשראי כדי להפקיד $1 ישירות בחשבון ה-MetaMask שלך."
},
"bytes": {
"message": "בייטים"

File diff suppressed because it is too large Load Diff

View File

@ -137,10 +137,10 @@
"message": "Vaš se preglednik ne podržava..."
},
"buyWithWyre": {
"message": "Kupi ETH Wyerom"
"message": "Kupi $1 Wyerom"
},
"buyWithWyreDescription": {
"message": "Wyreom vam se omogućava korištenje kreditnom karticom za polaganje ETH-a izravno na vaš račun za MetaMask."
"message": "Wyreom vam se omogućava korištenje kreditnom karticom za polaganje $1 -a izravno na vaš račun za MetaMask."
},
"bytes": {
"message": "Bajtovi"

View File

@ -137,7 +137,7 @@
"message": "Az ön böngészője nem támogatott..."
},
"buyWithWyre": {
"message": "Vásároljon ETH-t a Wyre-rel"
"message": "Vásároljon $1 -t a Wyre-rel"
},
"buyWithWyreDescription": {
"message": "A Wyre segítségével egyensen a MetaMaks fiókjában tehet letétbe ETH-t."

File diff suppressed because it is too large Load Diff

View File

@ -67,9 +67,6 @@
"affirmAgree": {
"message": "Acconsento"
},
"aggregatorFeeCost": {
"message": "Tassa per la rete aggregatore"
},
"alertDisableTooltip": {
"message": "Può essere cambiato in \"Impostazioni > Avvisi\""
},
@ -117,12 +114,6 @@
"message": "MetaMask Flask",
"description": "The name of the application (Flask)"
},
"approvalAndAggregatorTxFeeCost": {
"message": "Tassa di approvazione per la rete aggregatore"
},
"approvalTxGasCost": {
"message": "Costo Gas Approvazione Tx"
},
"approve": {
"message": "Approva"
},
@ -198,10 +189,10 @@
"message": "Compra"
},
"buyWithWyre": {
"message": "Compra ETH con Wyre"
"message": "Compra $1 con Wyre"
},
"buyWithWyreDescription": {
"message": "Wyre ti consente di usare la carta di credito per depositare ETH direttamente nel tuo account MetaMask."
"message": "Wyre ti consente di usare la carta di credito per depositare $1 direttamente nel tuo account MetaMask."
},
"canToggleInSettings": {
"message": "Puoi riabilitare questa notifica in Impostazioni -> Avvisi."
@ -568,10 +559,6 @@
"externalExtension": {
"message": "Estensione Esterna"
},
"extraApprovalGas": {
"message": "+$1 gas approvazione",
"description": "Expresses an additional gas amount the user will have to pay, on top of some other displayed amount. $1 is a decimal amount of gas"
},
"failed": {
"message": "Fallita"
},

File diff suppressed because it is too large Load Diff

View File

@ -137,10 +137,10 @@
"message": "ನಿಮ್ಮ ಬ್ರೌಸರ್ ಬೆಂಬಲಿಸುತ್ತಿಲ್ಲ..."
},
"buyWithWyre": {
"message": "Wyre ನೊಂದಿಗೆ ETH ಖರೀದಿಸಿ"
"message": "Wyre ನೊಂದಿಗೆ $1 ಖರೀದಿಸಿ"
},
"buyWithWyreDescription": {
"message": "ನಿಮ್ಮ MetaMask ಖಾತೆಗೆ ETH ಅನ್ನು ಜಮಾ ಮಾಡಲು ಕ್ರೆಡಿಟ್ ಕಾರ್ಡ್ ಬಳಸಲು Wyre ನಿಮಗೆ ಅನುಮತಿಸುತ್ತದೆ."
"message": "ನಿಮ್ಮ MetaMask ಖಾತೆಗೆ $1 ಅನ್ನು ಜಮಾ ಮಾಡಲು ಕ್ರೆಡಿಟ್ ಕಾರ್ಡ್ ಬಳಸಲು Wyre ನಿಮಗೆ ಅನುಮತಿಸುತ್ತದೆ."
},
"bytes": {
"message": "ಬೈಟ್‌ಗಳು"

File diff suppressed because it is too large Load Diff

View File

@ -137,10 +137,10 @@
"message": "Jūsų naršyklė neatpažįstama..."
},
"buyWithWyre": {
"message": "Pirkti ETH su „Wyre“"
"message": "Pirkti $1 su „Wyre“"
},
"buyWithWyreDescription": {
"message": "„Wyre“ leidžia naudotis kreditine kortele norint įnešti ETH tiesiai į jūsų „MetaMask“ paskyrą."
"message": "„Wyre“ leidžia naudotis kreditine kortele norint įnešti $1 tiesiai į jūsų „MetaMask“ paskyrą."
},
"bytes": {
"message": "Baitai"

View File

@ -137,10 +137,10 @@
"message": "Jūsu pārlūkprogramma netiek atbalstīta..."
},
"buyWithWyre": {
"message": "Pirkt ETH ar Wyre"
"message": "Pirkt $1 ar Wyre"
},
"buyWithWyreDescription": {
"message": "Wyre ļauj noguldīt ETH tieši jūsu MetaMask kontā, izmantojot kredītkarti."
"message": "Wyre ļauj noguldīt $1 tieši jūsu MetaMask kontā, izmantojot kredītkarti."
},
"bytes": {
"message": "Baiti"

View File

@ -137,10 +137,10 @@
"message": "Pelayar anda tidak disokong..."
},
"buyWithWyre": {
"message": "Beli ETH dengan Wyre"
"message": "Beli $1 dengan Wyre"
},
"buyWithWyreDescription": {
"message": "Wyre membolehkan anda menggunakan kad kredit untuk mendeposit ETH secara terus ke dalam akaun MetaMask anda."
"message": "Wyre membolehkan anda menggunakan kad kredit untuk mendeposit $1 secara terus ke dalam akaun MetaMask anda."
},
"bytes": {
"message": "Bait"

View File

@ -137,10 +137,10 @@
"message": "Nettleseren din støttes ikke ..."
},
"buyWithWyre": {
"message": "Kjøp ETH med Wyre"
"message": "Kjøp $1 med Wyre"
},
"buyWithWyreDescription": {
"message": "Wyre lar deg bruke et kredittkort for å sette inn ETH rett på MetaMask-kontoen din."
"message": "Wyre lar deg bruke et kredittkort for å sette inn $1 rett på MetaMask-kontoen din."
},
"cancel": {
"message": "Avbryt"

View File

@ -91,9 +91,6 @@
"affirmAgree": {
"message": "Sang-ayon ako"
},
"aggregatorFeeCost": {
"message": "Bayarin sa aggregator network"
},
"alertDisableTooltip": {
"message": "Mababago ito sa \"Mga Setting > Mga Alerto\""
},
@ -141,12 +138,6 @@
"message": "MetaMask Flask",
"description": "The name of the application (Flask)"
},
"approvalAndAggregatorTxFeeCost": {
"message": "Bayarin sa pag-apruba at aggregator network"
},
"approvalTxGasCost": {
"message": "Approval Tx Gas Cost"
},
"approve": {
"message": "Aprubahan ang limitasyon sa paggastos"
},
@ -237,10 +228,10 @@
"message": "Bumili"
},
"buyWithWyre": {
"message": "Bumili ng ETH gamit ang Wyre"
"message": "Bumili ng $1 gamit ang Wyre"
},
"buyWithWyreDescription": {
"message": "Binibigyang-daan ka ng Wyre na gumamit ng debit card para mag-deposit ng ETH sa mismong MetaMask account mo."
"message": "Binibigyang-daan ka ng Wyre na gumamit ng debit card para mag-deposit ng $1 sa mismong MetaMask account mo."
},
"bytes": {
"message": "Bytes"
@ -674,10 +665,6 @@
"externalExtension": {
"message": "External Extension"
},
"extraApprovalGas": {
"message": "+$1 na pag-apruba sa gas",
"description": "Expresses an additional gas amount the user will have to pay, on top of some other displayed amount. $1 is a decimal amount of gas"
},
"failed": {
"message": "Hindi matagumpay"
},

View File

@ -137,10 +137,10 @@
"message": "Twoja przeglądarka nie jest obsługiwana..."
},
"buyWithWyre": {
"message": "Kup ETH poprzez Wyre"
"message": "Kup $1 poprzez Wyre"
},
"buyWithWyreDescription": {
"message": "Dzięki Wyre możesz użyć karty kredytowej, aby wpłacić ETH bezpośrednio na swoje konto MetaMask."
"message": "Dzięki Wyre możesz użyć karty kredytowej, aby wpłacić $1 bezpośrednio na swoje konto MetaMask."
},
"bytes": {
"message": "Bajty"

File diff suppressed because it is too large Load Diff

View File

@ -179,9 +179,6 @@
"affirmAgree": {
"message": "Concordo"
},
"aggregatorFeeCost": {
"message": "Taxa de rede do agregador"
},
"alertDisableTooltip": {
"message": "Isso pode ser alterado em \"Configurações > Alertas\""
},
@ -233,12 +230,6 @@
"message": "MetaMask Flask",
"description": "The name of the application (Flask)"
},
"approvalAndAggregatorTxFeeCost": {
"message": "Taxa de aprovação e da rede do agregador"
},
"approvalTxGasCost": {
"message": "Custo em gás da transação de aprovação"
},
"approve": {
"message": "Aprovar limite de gastos"
},
@ -372,10 +363,10 @@
"message": "Comprar"
},
"buyWithWyre": {
"message": "Comprar ETH com Wyre"
"message": "Comprar $1 com Wyre"
},
"buyWithWyreDescription": {
"message": "Com o Wyre, você pode usar um cartão de débito para depositar ETH diretamente na sua conta da MetaMask."
"message": "Com o Wyre, você pode usar um cartão de débito para depositar $1 diretamente na sua conta da MetaMask."
},
"bytes": {
"message": "Bytes"
@ -1031,10 +1022,6 @@
"externalExtension": {
"message": "Extensão externa"
},
"extraApprovalGas": {
"message": "+$1 de gás por aprovação",
"description": "Expresses an additional gas amount the user will have to pay, on top of some other displayed amount. $1 is a decimal amount of gas"
},
"failed": {
"message": "Falhou"
},

View File

@ -137,10 +137,10 @@
"message": "Browserul dvs. nu este compatibil..."
},
"buyWithWyre": {
"message": "Cumpărați ETH cu Wyre"
"message": "Cumpărați $1 cu Wyre"
},
"buyWithWyreDescription": {
"message": "Wyre vă permite să folosiți un card de credit pentru a depune ETH direct în contul dvs. MetaMask."
"message": "Wyre vă permite să folosiți un card de credit pentru a depune $1 direct în contul dvs. MetaMask."
},
"bytes": {
"message": "Octeți"

File diff suppressed because it is too large Load Diff

View File

@ -131,10 +131,10 @@
"message": "Váš prehliadač nie je podporovaný..."
},
"buyWithWyre": {
"message": "Kúpte ETH s Wyre"
"message": "Kúpte $1 s Wyre"
},
"buyWithWyreDescription": {
"message": "Wyre vám umožňuje použiť kreditnú kartu na vloženie depozitu ETH priamo na váš účet MetaMask."
"message": "Wyre vám umožňuje použiť kreditnú kartu na vloženie depozitu $1 priamo na váš účet MetaMask."
},
"bytes": {
"message": "Bajty"

View File

@ -137,10 +137,10 @@
"message": "Vaš brskalnik ni podptrt ..."
},
"buyWithWyre": {
"message": "Kupi ETH z Wyre"
"message": "Kupi $1 z Wyre"
},
"buyWithWyreDescription": {
"message": "Wyre vam omogoča, da s kreditno kartico nakažete ETH neposredno na svoj račun MetaMask."
"message": "Wyre vam omogoča, da s kreditno kartico nakažete $1 neposredno na svoj račun MetaMask."
},
"bytes": {
"message": "Bajti"

View File

@ -137,10 +137,10 @@
"message": "Vaš pregledač nije podržan..."
},
"buyWithWyre": {
"message": "Kupite ETH preko servisa Wyre"
"message": "Kupite $1 preko servisa Wyre"
},
"buyWithWyreDescription": {
"message": "Wyre vam dozvoljava da koristite kreditnu karticu kako biste deponovali ETH pravo na vaš MetaMask nalog."
"message": "Wyre vam dozvoljava da koristite kreditnu karticu kako biste deponovali $1 pravo na vaš MetaMask nalog."
},
"bytes": {
"message": "Bajtovi"

View File

@ -137,10 +137,10 @@
"message": "Din webbläsare stöds inte..."
},
"buyWithWyre": {
"message": "Köp ETH med Wyre"
"message": "Köp $1 med Wyre"
},
"buyWithWyreDescription": {
"message": "Wyre låter dig använda ett kreditkort för att sätta in ETH direkt på ditt MetaMask-konto."
"message": "Wyre låter dig använda ett kreditkort för att sätta in $1 direkt på ditt MetaMask-konto."
},
"cancel": {
"message": "Avbryt"

View File

@ -134,10 +134,10 @@
"message": "Kivinjari chaku hakiwezeshwi..."
},
"buyWithWyre": {
"message": "Nunua ETH kwa kutumia Wyre"
"message": "Nunua $1 kwa kutumia Wyre"
},
"buyWithWyreDescription": {
"message": "Wyre inakuwezesha kutumia kadi ya benki kuweka ETH moja kwa moja kwenye akaunti yako ya MetaMask."
"message": "Wyre inakuwezesha kutumia kadi ya benki kuweka $1 moja kwa moja kwenye akaunti yako ya MetaMask."
},
"cancel": {
"message": "Ghairi"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -137,10 +137,10 @@
"message": "Ваш браузер не підтримується..."
},
"buyWithWyre": {
"message": "Купити ETH через Wyre"
"message": "Купити $1 через Wyre"
},
"buyWithWyreDescription": {
"message": "Wyre дає змогу використовувати кредитну картку для внесення валюти ETH безпосередньо у свій гаманець MetaMask."
"message": "Wyre дає змогу використовувати кредитну картку для внесення валюти $1 безпосередньо у свій гаманець MetaMask."
},
"bytes": {
"message": "Байти"

File diff suppressed because it is too large Load Diff

3980
app/_locales/zh/messages.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -179,9 +179,6 @@
"affirmAgree": {
"message": "我同意"
},
"aggregatorFeeCost": {
"message": "聚集器网络手续费"
},
"alertDisableTooltip": {
"message": "这个可以在“设置 > 提醒”中进行更改"
},
@ -233,12 +230,6 @@
"message": "MetaMask Flask",
"description": "The name of the application (Flask)"
},
"approvalAndAggregatorTxFeeCost": {
"message": "批准聚合商网络手续费"
},
"approvalTxGasCost": {
"message": "批准交易燃料成本"
},
"approve": {
"message": "批准消费限额"
},
@ -372,10 +363,10 @@
"message": "购买"
},
"buyWithWyre": {
"message": "使用 Wyre 购买 ETH"
"message": "使用 Wyre 购买 $1"
},
"buyWithWyreDescription": {
"message": "您可以通过 Wyre 使用信用卡将 ETH 存入您的 MetaMask 账户。"
"message": "您可以通过 Wyre 使用信用卡将 $1 存入您的 MetaMask 账户。"
},
"bytes": {
"message": "字节"
@ -1017,10 +1008,6 @@
"externalExtension": {
"message": "外部扩展"
},
"extraApprovalGas": {
"message": "+$1 批准燃料",
"description": "Expresses an additional gas amount the user will have to pay, on top of some other displayed amount. $1 is a decimal amount of gas"
},
"failed": {
"message": "失败"
},

View File

@ -140,10 +140,10 @@
"message": "買"
},
"buyWithWyre": {
"message": "用 Wyre 購買 ETH"
"message": "用 Wyre 購買 $1"
},
"buyWithWyreDescription": {
"message": "Wyre 讓你使用信用卡在 MetaMask 帳號中直接存入 ETH。"
"message": "Wyre 讓你使用信用卡在 MetaMask 帳號中直接存入 $1 。"
},
"bytes": {
"message": "位元組"

View File

@ -6,7 +6,7 @@
<body>
<script src="./globalthis.js" type="text/javascript" charset="utf-8"></script>
<script src="./sentry-install.js" type="text/javascript" charset="utf-8"></script>
{{@if(it.useLavamoat)}}
{{@if(it.applyLavaMoat)}}
<script src="./runtime-lavamoat.js" type="text/javascript" charset="utf-8"></script>
<script src="./lockdown-more.js" type="text/javascript" charset="utf-8"></script>
<script src="./policy-load.js" type="text/javascript" charset="utf-8"></script>

View File

@ -8,11 +8,11 @@
<link rel="stylesheet" type="text/css" href="./index-rtl.css" title="rtl" disabled>
</head>
<body>
<div id="app-content"></div>
<div id="app-content"><div id="app-loader">Loading...</div></div>
<div id="popover-content"></div>
<script src="./globalthis.js" type="text/javascript" charset="utf-8"></script>
<script src="./sentry-install.js" type="text/javascript" charset="utf-8"></script>
{{@if(it.useLavamoat)}}
{{@if(it.applyLavaMoat)}}
<script src="./runtime-lavamoat.js" type="text/javascript" charset="utf-8"></script>
<script src="./lockdown-more.js" type="text/javascript" charset="utf-8"></script>
<script src="./policy-load.js" type="text/javascript" charset="utf-8"></script>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

1
app/images/palm.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 314 KiB

View File

@ -0,0 +1,81 @@
{
"action": {
"default_icon": {
"16": "images/icon-16.png",
"19": "images/icon-19.png",
"32": "images/icon-32.png",
"38": "images/icon-38.png",
"64": "images/icon-64.png",
"128": "images/icon-128.png",
"512": "images/icon-512.png"
},
"default_title": "MetaMask",
"default_popup": "popup.html"
},
"author": "https://metamask.io",
"background": {
"service_worker": "app-init.js"
},
"commands": {
"_execute_browser_action": {
"suggested_key": {
"windows": "Alt+Shift+M",
"mac": "Alt+Shift+M",
"chromeos": "Alt+Shift+M",
"linux": "Alt+Shift+M"
}
}
},
"content_scripts": [
{
"matches": ["file://*/*", "http://*/*", "https://*/*"],
"js": [
"disable-console.js",
"globalthis.js",
"lockdown-install.js",
"lockdown-run.js",
"lockdown-more.js",
"contentscript.js"
],
"run_at": "document_start",
"all_frames": true
},
{
"matches": ["*://connect.trezor.io/*/popup.html"],
"js": ["vendor/trezor/content-script.js"]
}
],
"default_locale": "en",
"description": "__MSG_appDescription__",
"icons": {
"16": "images/icon-16.png",
"19": "images/icon-19.png",
"32": "images/icon-32.png",
"38": "images/icon-38.png",
"48": "images/icon-48.png",
"64": "images/icon-64.png",
"128": "images/icon-128.png",
"512": "images/icon-512.png"
},
"manifest_version": 3,
"name": "__MSG_appName__",
"permissions": [
"storage",
"unlimitedStorage",
"clipboardWrite",
"http://localhost:8545/",
"https://*.infura.io/",
"https://lattice.gridplus.io/*",
"activeTab",
"webRequest",
"*://*.eth/",
"notifications"
],
"short_name": "__MSG_appName__",
"web_accessible_resources": [
{
"resources": ["inpage.js", "phishing.html"],
"matches": ["http://*/*", "https://*/*"]
}
]
}

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,7 @@
{
"externally_connectable": {
"matches": ["https://metamask.io/*"],
"ids": ["*"]
},
"minimum_chrome_version": "66"
}

View File

@ -0,0 +1,26 @@
{
"applications": {
"gecko": {
"id": "webextension@metamask.io",
"strict_min_version": "68.0"
}
},
"background": {
"page": "background.html",
"persistent": true
},
"browser_action": {
"default_icon": {
"16": "images/icon-16.png",
"19": "images/icon-19.png",
"32": "images/icon-32.png",
"38": "images/icon-38.png",
"64": "images/icon-64.png",
"128": "images/icon-128.png",
"512": "images/icon-512.png"
},
"default_title": "MetaMask",
"default_popup": "popup.html"
},
"manifest_version": 2
}

View File

@ -0,0 +1,9 @@
{
"permissions": [
"storage",
"tabs",
"clipboardWrite",
"clipboardRead",
"http://localhost:8545/"
]
}

View File

@ -35,7 +35,7 @@
<div id="popover-content"></div>
<script src="./globalthis.js" type="text/javascript" charset="utf-8"></script>
<script src="./sentry-install.js" type="text/javascript" charset="utf-8"></script>
{{@if(it.useLavamoat)}}
{{@if(it.applyLavaMoat)}}
<script src="./runtime-lavamoat.js" type="text/javascript" charset="utf-8"></script>
<script src="./lockdown-more.js" type="text/javascript" charset="utf-8"></script>
<script src="./policy-load.js" type="text/javascript" charset="utf-8"></script>

View File

@ -12,7 +12,7 @@
<div id="popover-content"></div>
<script src="./globalthis.js" type="text/javascript" charset="utf-8"></script>
<script src="./sentry-install.js" type="text/javascript" charset="utf-8"></script>
{{@if(it.useLavamoat)}}
{{@if(it.applyLavaMoat)}}
<script src="./runtime-lavamoat.js" type="text/javascript" charset="utf-8"></script>
<script src="./lockdown-more.js" type="text/javascript" charset="utf-8"></script>
<script src="./policy-load.js" type="text/javascript" charset="utf-8"></script>

55
app/scripts/app-init.js Normal file
View File

@ -0,0 +1,55 @@
// eslint-disable-next-line import/unambiguous
function tryImport(...fileNames) {
try {
// eslint-disable-next-line
importScripts(...fileNames);
return true;
} catch (e) {
console.error(e);
return false;
}
}
function importAllScripts() {
const startImportScriptsTime = Date.now();
// applyLavaMoat has been hard coded to "true" as
// tryImport('./runtime-cjs.js') is giving issue with XMLHttpRequest object which is not avaialble to service worker.
// we need to dynamically inject values of applyLavaMoat once this is fixed.
const applyLavaMoat = true;
tryImport('./globalthis.js');
tryImport('./sentry-install.js');
if (applyLavaMoat) {
tryImport('./runtime-lavamoat.js');
tryImport('./lockdown-more.js');
tryImport('./policy-load.js');
} else {
tryImport('./lockdown-install.js');
tryImport('./lockdown-more.js');
tryImport('./lockdown-run.js');
tryImport('./runtime-cjs.js');
}
const fileList = [
// The list of files is injected at build time by replacing comment below with comma separated strings of file names
/** FILE NAMES */
];
fileList.forEach((fileName) => tryImport(fileName));
// for performance metrics/reference
console.log(
`SCRIPTS IMPORT COMPLETE in Seconds: ${
(Date.now() - startImportScriptsTime) / 1000
}`,
);
}
// Placing script import call here ensures that scripts are inported each time service worker is activated.
importAllScripts();
/**
* An open issue is changes in this file break during hot reloading. Reason is dynamic injection of "FILE NAMES".
* Developers need to restart local server if they change this file.
*/

View File

@ -23,6 +23,7 @@ import {
REJECT_NOTFICIATION_CLOSE,
REJECT_NOTFICIATION_CLOSE_SIG,
} from '../../shared/constants/metametrics';
import { isManifestV3 } from '../../shared/modules/mv3.utils';
import migrations from './migrations';
import Migrator from './lib/migrator';
import ExtensionPlatform from './platforms/extension';
@ -45,6 +46,14 @@ import { getPlatform } from './lib/util';
const { sentry } = global;
const firstTimeState = { ...rawFirstTimeState };
const metamaskInternalProcessHash = {
[ENVIRONMENT_TYPE_POPUP]: true,
[ENVIRONMENT_TYPE_NOTIFICATION]: true,
[ENVIRONMENT_TYPE_FULLSCREEN]: true,
};
const metamaskBlockedPorts = ['trezor-connect'];
log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'info');
const platform = new ExtensionPlatform();
@ -73,8 +82,23 @@ const ONE_SECOND_IN_MILLISECONDS = 1_000;
// Timeout for initializing phishing warning page.
const PHISHING_WARNING_PAGE_TIMEOUT = ONE_SECOND_IN_MILLISECONDS;
// initialization flow
initialize().catch(log.error);
/**
* In case of MV3 we attach a "onConnect" event listener as soon as the application is initialised.
* Reason is that in case of MV3 a delay in doing this was resulting in missing first connect event after service worker is re-activated.
*/
const initApp = async (remotePort) => {
browser.runtime.onConnect.removeListener(initApp);
await initialize(remotePort);
log.info('MetaMask initialization complete.');
};
if (isManifestV3()) {
browser.runtime.onConnect.addListener(initApp);
} else {
// initialization flow
initialize().catch(log.error);
}
/**
* @typedef {import('../../shared/constants/transaction').TransactionMeta} TransactionMeta
@ -134,12 +158,13 @@ initialize().catch(log.error);
/**
* Initializes the MetaMask controller, and sets up all platform configuration.
*
* @param {string} remotePort - remote application port connecting to extension.
* @returns {Promise} Setup complete.
*/
async function initialize() {
async function initialize(remotePort) {
const initState = await loadStateFromPersistence();
const initLangCode = await getFirstPreferredLangCode();
await setupController(initState, initLangCode);
await setupController(initState, initLangCode, remotePort);
await loadPhishingWarningPage();
log.info('MetaMask initialization complete.');
}
@ -278,9 +303,10 @@ async function loadStateFromPersistence() {
*
* @param {Object} initState - The initial state to start the controller with, matches the state that is emitted from the controller.
* @param {string} initLangCode - The region code for the language preferred by the current user.
* @param {string} remoteSourcePort - remote application port connecting to extension.
* @returns {Promise} After setup is complete.
*/
function setupController(initState, initLangCode) {
function setupController(initState, initLangCode, remoteSourcePort) {
//
// MetaMask Controller
//
@ -367,17 +393,13 @@ function setupController(initState, initLangCode) {
//
// connect to other contexts
//
if (isManifestV3() && remoteSourcePort) {
connectRemote(remoteSourcePort);
}
browser.runtime.onConnect.addListener(connectRemote);
browser.runtime.onConnectExternal.addListener(connectExternal);
const metamaskInternalProcessHash = {
[ENVIRONMENT_TYPE_POPUP]: true,
[ENVIRONMENT_TYPE_NOTIFICATION]: true,
[ENVIRONMENT_TYPE_FULLSCREEN]: true,
};
const metamaskBlockedPorts = ['trezor-connect'];
const isClientOpenStatus = () => {
return (
popupIsOpen ||
@ -445,6 +467,13 @@ function setupController(initState, initLangCode) {
controller.isClientOpen = true;
controller.setupTrustedCommunication(portStream, remotePort.sender);
if (isManifestV3()) {
// Message below if captured by UI code in app/scripts/ui.js which will trigger UI initialisation
// This ensures that UI is initialised only after background is ready
// It fixes the issue of blank screen coming when extension is loaded, the issue is very frequent in MV3
remotePort.postMessage({ name: 'CONNECTION_READY' });
}
if (processName === ENVIRONMENT_TYPE_POPUP) {
popupIsOpen = true;
endOfStream(portStream, () => {
@ -566,8 +595,14 @@ function setupController(initState, initLangCode) {
if (count) {
label = String(count);
}
browser.browserAction.setBadgeText({ text: label });
browser.browserAction.setBadgeBackgroundColor({ color: '#037DD6' });
// browserAction has been replaced by action in MV3
if (isManifestV3()) {
browser.action.setBadgeText({ text: label });
browser.action.setBadgeBackgroundColor({ color: '#037DD6' });
} else {
browser.browserAction.setBadgeText({ text: label });
browser.browserAction.setBadgeBackgroundColor({ color: '#037DD6' });
}
}
function getUnapprovedTransactionCount() {

View File

@ -1,2 +1,3 @@
export const TRANSAK_API_KEY = '25ac1309-a49b-4411-b20e-5e56c61a5b1c'; // It's a public key, which will be included in a URL for Transak.
export const MOONPAY_API_KEY = 'pk_live_WbCpe6PxSIcGPCSd6lKCbJNRht7uy'; // Publishable key.
export const COINBASEPAY_API_KEY = 'ab4b8829-a59d-44d3-accc-de77e4f18df2'; // Publishable key.

View File

@ -6,6 +6,8 @@ import browser from 'webextension-polyfill';
import PortStream from 'extension-port-stream';
import { obj as createThoughStream } from 'through2';
import { isManifestV3 } from '../../shared/modules/mv3.utils';
// These require calls need to use require to be statically recognized by browserify
const fs = require('fs');
const path = require('path');
@ -54,7 +56,12 @@ function injectScript(content) {
const container = document.head || document.documentElement;
const scriptTag = document.createElement('script');
scriptTag.setAttribute('async', 'false');
scriptTag.textContent = content;
// Inline scripts do not work in MV3 due to more strict security policy
if (isManifestV3()) {
scriptTag.setAttribute('src', browser.runtime.getURL('inpage.js'));
} else {
scriptTag.textContent = content;
}
container.insertBefore(scriptTag, container.children[0]);
container.removeChild(scriptTag);
} catch (error) {

View File

@ -6,6 +6,9 @@ import { MINUTE } from '../../../shared/constants/time';
import { MAINNET_CHAIN_ID } from '../../../shared/constants/network';
import { isTokenDetectionEnabledForNetwork } from '../../../shared/modules/network.utils';
import { isEqualCaseInsensitive } from '../../../shared/modules/string-utils';
import { TOKEN_STANDARDS } from '../../../ui/helpers/constants/common';
import { ASSET_TYPES } from '../../../shared/constants/transaction';
import { EVENT, EVENT_NAMES } from '../../../shared/constants/metametrics';
// By default, poll every 3 minutes
const DEFAULT_INTERVAL = MINUTE * 3;
@ -26,6 +29,7 @@ export default class DetectTokensController {
* @param config.tokenList
* @param config.tokensController
* @param config.assetsContractController
* @param config.trackMetaMetricsEvent
*/
constructor({
interval = DEFAULT_INTERVAL,
@ -35,6 +39,7 @@ export default class DetectTokensController {
tokenList,
tokensController,
assetsContractController = null,
trackMetaMetricsEvent,
} = {}) {
this.assetsContractController = assetsContractController;
this.tokensController = tokensController;
@ -51,6 +56,7 @@ export default class DetectTokensController {
this.detectedTokens = process.env.TOKEN_DETECTION_V2
? this.tokensController?.state.detectedTokens
: [];
this._trackMetaMetricsEvent = trackMetaMetricsEvent;
preferences?.store.subscribe(({ selectedAddress, useTokenDetection }) => {
if (
@ -162,6 +168,7 @@ export default class DetectTokensController {
let tokensWithBalance = [];
if (process.env.TOKEN_DETECTION_V2) {
const eventTokensDetails = [];
if (result) {
const nonZeroTokenAddresses = Object.keys(result);
for (const nonZeroTokenAddress of nonZeroTokenAddresses) {
@ -172,6 +179,9 @@ export default class DetectTokensController {
iconUrl,
aggregators,
} = tokenList[nonZeroTokenAddress];
eventTokensDetails.push(`${symbol} - ${address}`);
tokensWithBalance.push({
address,
symbol,
@ -180,7 +190,17 @@ export default class DetectTokensController {
aggregators,
});
}
if (tokensWithBalance.length > 0) {
this._trackMetaMetricsEvent({
event: EVENT_NAMES.TOKEN_DETECTED,
category: EVENT.CATEGORIES.WALLET,
properties: {
tokens: eventTokensDetails,
token_standard: TOKEN_STANDARDS.ERC20,
asset_type: ASSET_TYPES.TOKEN,
},
});
await this.tokensController.addDetectedTokens(tokensWithBalance);
}
}

View File

@ -1,4 +1,13 @@
import { isEqual, merge, omit, omitBy, pickBy, size, sum } from 'lodash';
import {
isEqual,
memoize,
merge,
omit,
omitBy,
pickBy,
size,
sum,
} from 'lodash';
import { ObservableStore } from '@metamask/obs-store';
import { bufferToHex, keccak } from 'ethereumjs-util';
import { generateUUID } from 'pubnub';
@ -540,9 +549,7 @@ export default class MetaMetricsController {
* @returns {MetaMetricsTraits | null} traits that have changed since last update
*/
_buildUserTraitsObject(metamaskState) {
/**
* @type {MetaMetricsTraits}
*/
/** @type {MetaMetricsTraits} */
const currentTraits = {
[TRAITS.ADDRESS_BOOK_ENTRIES]: sum(
Object.values(metamaskState.addressBook).map(size),
@ -551,16 +558,29 @@ export default class MetaMetricsController {
[TRAITS.NETWORKS_ADDED]: metamaskState.frequentRpcListDetail.map(
(rpc) => rpc.chainId,
),
[TRAITS.NETWORKS_WITHOUT_TICKER]: metamaskState.frequentRpcListDetail.reduce(
(networkList, currentNetwork) => {
if (!currentNetwork.ticker) {
networkList.push(currentNetwork.chainId);
}
return networkList;
},
[],
),
[TRAITS.NFT_AUTODETECTION_ENABLED]: metamaskState.useCollectibleDetection,
[TRAITS.NUMBER_OF_ACCOUNTS]: Object.values(metamaskState.identities)
.length,
[TRAITS.NUMBER_OF_NFT_COLLECTIONS]: this._getNumberOfNFtCollection(
metamaskState,
[TRAITS.NUMBER_OF_NFT_COLLECTIONS]: this._getAllUniqueNFTAddressesLength(
metamaskState.allCollectibles,
),
[TRAITS.NUMBER_OF_NFTS]: this._getAllNFTsFlattened(
metamaskState.allCollectibles,
).length,
[TRAITS.NUMBER_OF_TOKENS]: this._getNumberOfTokens(metamaskState),
[TRAITS.OPENSEA_API_ENABLED]: metamaskState.openSeaEnabled,
[TRAITS.THREE_BOX_ENABLED]: metamaskState.threeBoxSyncingAllowed,
[TRAITS.THEME]: metamaskState.theme || 'default',
[TRAITS.TOKEN_DETECTION_ENABLED]: metamaskState.useTokenDetection,
};
if (!this.previousTraits) {
@ -603,22 +623,33 @@ export default class MetaMetricsController {
}
/**
* Returns an array of all of the collectibles/NFTs the user
* possesses across all networks and accounts.
*
* @param {object} metamaskState
* @returns number of unique collectible addresses
* @param {Object} allCollectibles
* @returns {[]}
*/
_getNumberOfNFtCollection(metamaskState) {
const { allCollectibles } = metamaskState;
if (!allCollectibles) {
return 0;
}
_getAllNFTsFlattened = memoize((allCollectibles = {}) => {
return Object.values(allCollectibles)
.reduce((result, chainNFTs) => {
return result.concat(Object.values(chainNFTs));
}, [])
.flat();
});
const allAddresses = Object.values(allCollectibles)
.flatMap((chainCollectibles) => Object.values(chainCollectibles))
.flat()
.map((collectible) => collectible.address);
const unique = [...new Set(allAddresses)];
return unique.length;
/**
* Returns the number of unique collectible/NFT addresses the user
* possesses across all networks and accounts.
*
* @param {Object} allCollectibles
* @returns {number}
*/
_getAllUniqueNFTAddressesLength(allCollectibles = {}) {
const allNFTAddresses = this._getAllNFTsFlattened(allCollectibles).map(
(nft) => nft.address,
);
const uniqueAddresses = new Set(allNFTAddresses);
return uniqueAddresses.size;
}
/**

View File

@ -9,8 +9,10 @@ import {
} from '../../../shared/constants/metametrics';
import waitUntilCalled from '../../../test/lib/wait-until-called';
import {
ETH_SYMBOL,
MAINNET_CHAIN_ID,
ROPSTEN_CHAIN_ID,
TEST_ETH_SYMBOL,
} from '../../../shared/constants/network';
import MetaMetricsController from './metametrics';
import { NETWORK_EVENTS } from './network';
@ -673,8 +675,9 @@ describe('MetaMetricsController', function () {
},
allTokens: MOCK_ALL_TOKENS,
frequentRpcListDetail: [
{ chainId: MAINNET_CHAIN_ID },
{ chainId: ROPSTEN_CHAIN_ID },
{ chainId: MAINNET_CHAIN_ID, ticker: ETH_SYMBOL },
{ chainId: ROPSTEN_CHAIN_ID, ticker: TEST_ETH_SYMBOL },
{ chainId: '0xaf' },
],
identities: [{}, {}],
ledgerTransportType: 'web-hid',
@ -682,19 +685,23 @@ describe('MetaMetricsController', function () {
threeBoxSyncingAllowed: false,
useCollectibleDetection: false,
theme: 'default',
useTokenDetection: true,
});
assert.deepEqual(traits, {
[TRAITS.ADDRESS_BOOK_ENTRIES]: 3,
[TRAITS.LEDGER_CONNECTION_TYPE]: 'web-hid',
[TRAITS.NETWORKS_ADDED]: [MAINNET_CHAIN_ID, ROPSTEN_CHAIN_ID],
[TRAITS.NETWORKS_ADDED]: [MAINNET_CHAIN_ID, ROPSTEN_CHAIN_ID, '0xaf'],
[TRAITS.NETWORKS_WITHOUT_TICKER]: ['0xaf'],
[TRAITS.NFT_AUTODETECTION_ENABLED]: false,
[TRAITS.NUMBER_OF_ACCOUNTS]: 2,
[TRAITS.NUMBER_OF_NFT_COLLECTIONS]: 3,
[TRAITS.NUMBER_OF_NFTS]: 4,
[TRAITS.NUMBER_OF_TOKENS]: 5,
[TRAITS.OPENSEA_API_ENABLED]: true,
[TRAITS.THREE_BOX_ENABLED]: false,
[TRAITS.THEME]: 'default',
[TRAITS.TOKEN_DETECTION_ENABLED]: true,
});
});
@ -716,6 +723,7 @@ describe('MetaMetricsController', function () {
threeBoxSyncingAllowed: false,
useCollectibleDetection: false,
theme: 'default',
useTokenDetection: true,
});
const updatedTraits = metaMetricsController._buildUserTraitsObject({
@ -736,6 +744,7 @@ describe('MetaMetricsController', function () {
threeBoxSyncingAllowed: false,
useCollectibleDetection: false,
theme: 'default',
useTokenDetection: true,
});
assert.deepEqual(updatedTraits, {
@ -764,6 +773,7 @@ describe('MetaMetricsController', function () {
threeBoxSyncingAllowed: false,
useCollectibleDetection: true,
theme: 'default',
useTokenDetection: true,
});
const updatedTraits = metaMetricsController._buildUserTraitsObject({
@ -782,6 +792,7 @@ describe('MetaMetricsController', function () {
threeBoxSyncingAllowed: false,
useCollectibleDetection: true,
theme: 'default',
useTokenDetection: true,
});
assert.equal(updatedTraits, null);

View File

@ -18,6 +18,7 @@ import {
MAINNET_CHAIN_ID,
RINKEBY_CHAIN_ID,
INFURA_BLOCKED_KEY,
TEST_NETWORK_TICKER_MAP,
} from '../../../../shared/constants/network';
import { SECOND } from '../../../../shared/constants/time';
import {
@ -41,7 +42,11 @@ if (process.env.IN_TEST) {
nickname: 'Localhost 8545',
};
} else if (process.env.METAMASK_DEBUG || env === 'test') {
defaultProviderConfigOpts = { type: RINKEBY, chainId: RINKEBY_CHAIN_ID };
defaultProviderConfigOpts = {
type: RINKEBY,
chainId: RINKEBY_CHAIN_ID,
ticker: TEST_NETWORK_TICKER_MAP.rinkeby,
};
} else {
defaultProviderConfigOpts = { type: MAINNET, chainId: MAINNET_CHAIN_ID };
}
@ -296,12 +301,12 @@ export default class NetworkController extends EventEmitter {
INFURA_PROVIDER_TYPES.includes(type),
`Unknown Infura provider type "${type}".`,
);
const { chainId } = NETWORK_TYPE_TO_ID_MAP[type];
const { chainId, ticker } = NETWORK_TYPE_TO_ID_MAP[type];
this.setProviderConfig({
type,
rpcUrl: '',
chainId,
ticker: 'ETH',
ticker: ticker ?? 'ETH',
nickname: '',
});
}

View File

@ -1,4 +1,4 @@
import { endowmentPermissionBuilders } from '@metamask/controllers';
import { endowmentPermissionBuilders } from '@metamask/snap-controllers';
import {
restrictedMethodPermissionBuilders,
selectHooks,

View File

@ -1,5 +1,4 @@
import { ObservableStore } from '@metamask/obs-store';
import stringify from 'fast-safe-stringify';
import { CaveatTypes } from '../../../../shared/constants/permissions';
import {
LOG_IGNORE_METHODS,
@ -158,9 +157,7 @@ export class PermissionLogController {
? LOG_METHOD_TYPES.internal
: LOG_METHOD_TYPES.restricted,
origin: request.origin,
request: stringify(request, null, 2),
requestTime: Date.now(),
response: null,
responseTime: null,
success: null,
};
@ -181,9 +178,12 @@ export class PermissionLogController {
return;
}
entry.response = stringify(response, null, 2);
// The JSON-RPC 2.0 specification defines "success" by the presence of
// either the "result" or "error" property. The specification forbids
// both properties from being present simultaneously, and our JSON-RPC
// stack is spec-compliant at the time of writing.
entry.success = Object.hasOwnProperty.call(response, 'result');
entry.responseTime = time;
entry.success = !response.error;
}
/**

View File

@ -1,6 +1,5 @@
import nanoid from 'nanoid';
import { useFakeTimers } from 'sinon';
import stringify from 'fast-safe-stringify';
import { constants, getters, noop } from '../../../../test/mocks/permissions';
import { PermissionLogController } from './permission-log';
import { LOG_LIMIT, LOG_METHOD_TYPES } from './enums';
@ -67,7 +66,7 @@ describe('PermissionLogController', () => {
req = RPC_REQUESTS.test_method(SUBJECTS.a.origin);
req.id = REQUEST_IDS.a;
res = { foo: 'bar' };
res = { result: 'bar' };
logMiddleware({ ...req }, res);
@ -143,11 +142,17 @@ describe('PermissionLogController', () => {
false,
);
// validate final state
// Validate final state
expect(entry1).toStrictEqual(log[0]);
expect(entry2).toStrictEqual(log[1]);
expect(entry3).toStrictEqual(log[2]);
expect(entry4).toStrictEqual(log[3]);
// Regression test: ensure "response" and "request" properties
// are not present
log.forEach((entry) =>
expect('request' in entry && 'response' in entry).toBe(false),
);
});
it('handles responses added out of order', () => {
@ -163,15 +168,15 @@ describe('PermissionLogController', () => {
// get make requests
req.id = id1;
const res1 = { foo: id1 };
const res1 = { result: id1 };
logMiddleware({ ...req }, { ...res1 }, getSavedMockNext(handlerArray));
req.id = id2;
const res2 = { foo: id2 };
const res2 = { result: id2 };
logMiddleware({ ...req }, { ...res2 }, getSavedMockNext(handlerArray));
req.id = id3;
const res3 = { foo: id3 };
const res3 = { result: id3 };
logMiddleware({ ...req }, { ...res3 }, getSavedMockNext(handlerArray));
// verify log state
@ -181,10 +186,10 @@ describe('PermissionLogController', () => {
const entry2 = log[1];
const entry3 = log[2];
// all entries should be in correct order, without responses
expect(entry1).toMatchObject({ id: id1, response: null });
expect(entry2).toMatchObject({ id: id2, response: null });
expect(entry3).toMatchObject({ id: id3, response: null });
// all entries should be in correct order
expect(entry1).toMatchObject({ id: id1, responseTime: null });
expect(entry2).toMatchObject({ id: id2, responseTime: null });
expect(entry3).toMatchObject({ id: id3, responseTime: null });
// call response handlers
for (const i of [1, 2, 0]) {
@ -226,7 +231,7 @@ describe('PermissionLogController', () => {
it('handles a lack of response', () => {
let req = RPC_REQUESTS.test_method(SUBJECTS.a.origin);
req.id = REQUEST_IDS.a;
let res = { foo: 'bar' };
let res = { result: 'bar' };
// noop for next handler prevents recording of response
logMiddleware({ ...req }, res, noop);
@ -270,7 +275,7 @@ describe('PermissionLogController', () => {
let log = permLog.getActivityLog();
expect(log).toHaveLength(0);
const res = { foo: 'bar' };
const res = { result: 'bar' };
const req1 = RPC_REQUESTS.metamask_sendDomainMetadata(
SUBJECTS.c.origin,
'foobar',
@ -288,7 +293,7 @@ describe('PermissionLogController', () => {
it('enforces log limit', () => {
const req = RPC_REQUESTS.test_method(SUBJECTS.a.origin);
const res = { foo: 'bar' };
const res = { result: 'bar' };
// max out log
let lastId;
@ -647,19 +652,15 @@ function validateActivityEntry(entry, req, res, methodType, success) {
expect(entry.method).toStrictEqual(req.method);
expect(entry.origin).toStrictEqual(req.origin);
expect(entry.methodType).toStrictEqual(methodType);
expect(entry.request).toStrictEqual(stringify(req, null, 2));
expect(Number.isInteger(entry.requestTime)).toBe(true);
if (res) {
expect(Number.isInteger(entry.responseTime)).toBe(true);
expect(entry.requestTime <= entry.responseTime).toBe(true);
expect(entry.success).toStrictEqual(success);
expect(entry.response).toStrictEqual(stringify(res, null, 2));
} else {
expect(entry.requestTime > 0).toBe(true);
expect(entry).toMatchObject({
response: null,
responseTime: null,
success: null,
});

View File

@ -68,7 +68,7 @@ export default class PreferencesController {
ledgerTransportType: window.navigator.hid
? LEDGER_TRANSPORT_TYPES.WEBHID
: LEDGER_TRANSPORT_TYPES.U2F,
theme: 'default',
theme: 'light',
...opts.initState,
};

View File

@ -353,14 +353,14 @@ describe('preferences controller', function () {
});
describe('setTheme', function () {
it('should default to value "default"', function () {
it('should default to value "light"', function () {
const state = preferencesController.store.getState();
assert.equal(state.theme, 'default');
assert.equal(state.theme, 'light');
});
it('should set the setTheme property in state', function () {
const state = preferencesController.store.getState();
assert.equal(state.theme, 'default');
assert.equal(state.theme, 'light');
preferencesController.setTheme('dark');
assert.equal(preferencesController.store.getState().theme, 'dark');
});

View File

@ -19,6 +19,11 @@ import {
SWAPS_CHAINID_CONTRACT_ADDRESS_MAP,
} from '../../../shared/constants/swaps';
import { GAS_ESTIMATE_TYPES } from '../../../shared/constants/gas';
import {
FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME,
FALLBACK_SMART_TRANSACTIONS_DEADLINE,
FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER,
} from '../../../shared/constants/smartTransactions';
import { isSwapsDefaultTokenAddress } from '../../../shared/modules/swaps.utils';
@ -41,8 +46,6 @@ const POLL_COUNT_LIMIT = 3;
// If for any reason the MetaSwap API fails to provide a refresh time,
// provide a reasonable fallback to avoid further errors
const FALLBACK_QUOTE_REFRESH_TIME = MINUTE;
const FALLBACK_SMART_TRANSACTION_REFRESH_TIME = SECOND * 10;
const FALLBACK_SMART_TRANSACTIONS_DEADLINE = 180;
function calculateGasEstimateWithRefund(
maxGas = MAX_GAS_LIMIT,
@ -86,8 +89,9 @@ const initialState = {
saveFetchedQuotes: false,
swapsQuoteRefreshTime: FALLBACK_QUOTE_REFRESH_TIME,
swapsQuotePrefetchingRefreshTime: FALLBACK_QUOTE_REFRESH_TIME,
swapsStxBatchStatusRefreshTime: FALLBACK_SMART_TRANSACTION_REFRESH_TIME,
swapsStxGetTransactionsRefreshTime: FALLBACK_SMART_TRANSACTION_REFRESH_TIME,
swapsStxBatchStatusRefreshTime: FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME,
swapsStxGetTransactionsRefreshTime: FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME,
swapsStxMaxFeeMultiplier: FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER,
swapsFeatureFlags: {},
},
};
@ -129,13 +133,13 @@ export default class SwapsController {
});
}
async fetchSwapsRefreshRates(chainId) {
async fetchSwapsNetworkConfig(chainId) {
const response = await fetchWithCache(
getBaseApi('network', chainId),
{ method: 'GET' },
{ cacheRefreshTime: 600000 },
);
const { refreshRates } = response || {};
const { refreshRates, parameters = {} } = response || {};
if (
!refreshRates ||
typeof refreshRates.quotes !== 'number' ||
@ -152,35 +156,39 @@ export default class SwapsController {
stxGetTransactions: refreshRates.stxGetTransactions * 1000,
stxBatchStatus: refreshRates.stxBatchStatus * 1000,
stxStatusDeadline: refreshRates.stxStatusDeadline,
stxMaxFeeMultiplier: parameters.stxMaxFeeMultiplier,
};
}
// Sets the refresh rate for quote updates from the MetaSwap API
async _setSwapsRefreshRates() {
// Sets the network config from the MetaSwap API.
async _setSwapsNetworkConfig() {
const chainId = this._getCurrentChainId();
let swapsRefreshRates;
let swapsNetworkConfig;
try {
swapsRefreshRates = await this.fetchSwapsRefreshRates(chainId);
swapsNetworkConfig = await this.fetchSwapsNetworkConfig(chainId);
} catch (e) {
console.error('Request for swaps quote refresh time failed: ', e);
console.error('Request for Swaps network config failed: ', e);
}
const { swapsState: latestSwapsState } = this.store.getState();
this.store.updateState({
swapsState: {
...latestSwapsState,
swapsQuoteRefreshTime:
swapsRefreshRates?.quotes || FALLBACK_QUOTE_REFRESH_TIME,
swapsNetworkConfig?.quotes || FALLBACK_QUOTE_REFRESH_TIME,
swapsQuotePrefetchingRefreshTime:
swapsRefreshRates?.quotesPrefetching || FALLBACK_QUOTE_REFRESH_TIME,
swapsNetworkConfig?.quotesPrefetching || FALLBACK_QUOTE_REFRESH_TIME,
swapsStxGetTransactionsRefreshTime:
swapsRefreshRates?.stxGetTransactions ||
FALLBACK_SMART_TRANSACTION_REFRESH_TIME,
swapsNetworkConfig?.stxGetTransactions ||
FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME,
swapsStxBatchStatusRefreshTime:
swapsRefreshRates?.stxBatchStatus ||
FALLBACK_SMART_TRANSACTION_REFRESH_TIME,
swapsNetworkConfig?.stxBatchStatus ||
FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME,
swapsStxStatusDeadline:
swapsRefreshRates?.stxStatusDeadline ||
swapsNetworkConfig?.stxStatusDeadline ||
FALLBACK_SMART_TRANSACTIONS_DEADLINE,
swapsStxMaxFeeMultiplier:
swapsNetworkConfig?.stxMaxFeeMultiplier ||
FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER,
},
});
}
@ -253,7 +261,7 @@ export default class SwapsController {
this._fetchTradesInfo(fetchParams, {
...fetchParamsMetaData,
}),
this._setSwapsRefreshRates(),
this._setSwapsNetworkConfig(),
]);
const {

View File

@ -13,6 +13,10 @@ import { ETH_SWAPS_TOKEN_OBJECT } from '../../../shared/constants/swaps';
import { createTestProviderTools } from '../../../test/stub/provider';
import { SECOND } from '../../../shared/constants/time';
import { GAS_ESTIMATE_TYPES } from '../../../shared/constants/gas';
import {
FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME,
FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER,
} from '../../../shared/constants/smartTransactions';
import SwapsController, { utils } from './swaps';
import { NETWORK_EVENTS } from './network';
@ -134,8 +138,9 @@ const EMPTY_INIT_STATE = {
swapsFeatureFlags: {},
swapsQuoteRefreshTime: 60000,
swapsQuotePrefetchingRefreshTime: 60000,
swapsStxBatchStatusRefreshTime: 10000,
swapsStxGetTransactionsRefreshTime: 10000,
swapsStxBatchStatusRefreshTime: FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME,
swapsStxGetTransactionsRefreshTime: FALLBACK_SMART_TRANSACTIONS_REFRESH_TIME,
swapsStxMaxFeeMultiplier: FALLBACK_SMART_TRANSACTIONS_MAX_FEE_MULTIPLIER,
swapsUserFeeLevel: '',
saveFetchedQuotes: false,
},

View File

@ -18,10 +18,12 @@ import {
getChainType,
} from '../../lib/util';
import { TRANSACTION_NO_CONTRACT_ERROR_KEY } from '../../../../ui/helpers/constants/error-keys';
import { calcGasTotal } from '../../../../ui/pages/send/send.utils';
import { getSwapsTokensReceivedFromTxMeta } from '../../../../ui/pages/swaps/swaps.util';
import {
hexWEIToDecGWEI,
decimalToHex,
hexWEIToDecETH,
} from '../../../../ui/helpers/utils/conversions.util';
import {
TRANSACTION_STATUSES,
@ -39,6 +41,7 @@ import {
PRIORITY_LEVELS,
} from '../../../../shared/constants/gas';
import { decGWEIToHexWEI } from '../../../../shared/modules/conversion.utils';
import { EVENT } from '../../../../shared/constants/metametrics';
import {
HARDFORKS,
MAINNET,
@ -50,6 +53,7 @@ import {
determineTransactionType,
isEIP1559Transaction,
} from '../../../../shared/modules/transaction.utils';
import { ORIGIN_METAMASK } from '../../../../shared/constants/app';
import TransactionStateManager from './tx-state-manager';
import TxGasUtil from './tx-gas-utils';
import PendingTransactionTracker from './pending-tx-tracker';
@ -62,6 +66,15 @@ const SWAP_TRANSACTION_TYPES = [
TRANSACTION_TYPES.SWAP_APPROVAL,
];
// Only certain types of transactions should be allowed to be specified when
// adding a new unapproved transaction.
const VALID_UNAPPROVED_TRANSACTION_TYPES = [
...SWAP_TRANSACTION_TYPES,
TRANSACTION_TYPES.SIMPLE_SEND,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM,
];
/**
* @typedef {import('../../../../shared/constants/transaction').TransactionMeta} TransactionMeta
* @typedef {import('../../../../shared/constants/transaction').TransactionMetaMetricsEventString} TransactionMetaMetricsEventString
@ -432,7 +445,7 @@ export default class TransactionController extends EventEmitter {
* @param {string} editableParams.gasPrice
* @returns {TransactionMeta} the txMeta of the updated transaction
*/
updateEditableParams(txId, { data, from, to, value, gas, gasPrice }) {
async updateEditableParams(txId, { data, from, to, value, gas, gasPrice }) {
this._throwErrorIfNotUnapprovedTx(txId, 'updateEditableParams');
const editableParams = {
@ -448,7 +461,20 @@ export default class TransactionController extends EventEmitter {
// only update what is defined
editableParams.txParams = pickBy(editableParams.txParams);
// update transaction type in case it has changes
const transactionBeforeEdit = this._getTransaction(txId);
const { type } = await determineTransactionType(
{
...transactionBeforeEdit.txParams,
...editableParams.txParams,
},
this.query,
);
editableParams.type = type;
const note = `Update Editable Params for ${txId}`;
this._updateTransaction(txId, editableParams, note);
return this._getTransaction(txId);
}
@ -636,6 +662,35 @@ export default class TransactionController extends EventEmitter {
return this._getTransaction(txId);
}
/**
* append new sendFlowHistory to the transaction with id if the transaction
* state is unapproved. Returns the updated transaction.
*
* @param {string} txId - transaction id
* @param {Array<{ entry: string, timestamp: number }>} sendFlowHistory -
* history to add to the sendFlowHistory property of txMeta.
* @returns {TransactionMeta} the txMeta of the updated transaction
*/
updateTransactionSendFlowHistory(txId, sendFlowHistory) {
this._throwErrorIfNotUnapprovedTx(txId, 'updateTransactionSendFlowHistory');
const txMeta = this._getTransaction(txId);
// only update what is defined
const note = `Update sendFlowHistory for ${txId}`;
this.txStateManager.updateTransaction(
{
...txMeta,
sendFlowHistory: [
...(txMeta?.sendFlowHistory ?? []),
...sendFlowHistory,
],
},
note,
);
return this._getTransaction(txId);
}
// ====================================================================================================================================================
/**
@ -645,12 +700,18 @@ export default class TransactionController extends EventEmitter {
* @param txParams
* @param origin
* @param transactionType
* @param sendFlowHistory
* @returns {txMeta}
*/
async addUnapprovedTransaction(txParams, origin, transactionType) {
async addUnapprovedTransaction(
txParams,
origin,
transactionType,
sendFlowHistory = [],
) {
if (
transactionType !== undefined &&
!SWAP_TRANSACTION_TYPES.includes(transactionType)
!VALID_UNAPPROVED_TRANSACTION_TYPES.includes(transactionType)
) {
throw new Error(
`TransactionController - invalid transactionType value: ${transactionType}`,
@ -672,9 +733,10 @@ export default class TransactionController extends EventEmitter {
let txMeta = this.txStateManager.generateTxMeta({
txParams: normalizedTxParams,
origin,
sendFlowHistory,
});
if (origin === 'metamask') {
if (origin === ORIGIN_METAMASK) {
// Assert the from address is the selected address
if (normalizedTxParams.from !== this.getSelectedAddress()) {
throw ethErrors.rpc.internal({
@ -783,7 +845,7 @@ export default class TransactionController extends EventEmitter {
// then we set maxFeePerGas and maxPriorityFeePerGas to the suggested gasPrice.
txMeta.txParams.maxFeePerGas = txMeta.txParams.gasPrice;
txMeta.txParams.maxPriorityFeePerGas = txMeta.txParams.gasPrice;
if (eip1559V2Enabled && txMeta.origin !== 'metamask') {
if (eip1559V2Enabled && txMeta.origin !== ORIGIN_METAMASK) {
txMeta.userFeeLevel = PRIORITY_LEVELS.DAPP_SUGGESTED;
} else {
txMeta.userFeeLevel = CUSTOM_GAS_ESTIMATE;
@ -794,7 +856,7 @@ export default class TransactionController extends EventEmitter {
defaultMaxPriorityFeePerGas &&
!txMeta.txParams.maxFeePerGas &&
!txMeta.txParams.maxPriorityFeePerGas) ||
txMeta.origin === 'metamask'
txMeta.origin === ORIGIN_METAMASK
) {
txMeta.userFeeLevel = GAS_RECOMMENDATIONS.MEDIUM;
} else if (eip1559V2Enabled) {
@ -1779,7 +1841,10 @@ export default class TransactionController extends EventEmitter {
txMeta,
'transactions/pending-tx-tracker#event: tx:confirmed reference to confirmed txHash with same nonce',
);
this._dropTransaction(otherTxMeta.id);
// Drop any transaction that wasn't previously failed (off chain failure)
if (otherTxMeta.status !== TRANSACTION_STATUSES.FAILED) {
this._dropTransaction(otherTxMeta.id);
}
});
}
@ -1827,13 +1892,37 @@ export default class TransactionController extends EventEmitter {
this.memStore.updateState({ unapprovedTxs, currentNetworkTxList });
}
_calculateTransactionsCost(txMeta, approvalTxMeta) {
let approvalGasCost = '0x0';
if (approvalTxMeta?.txReceipt) {
approvalGasCost = calcGasTotal(
approvalTxMeta.txReceipt.gasUsed,
approvalTxMeta.txReceipt.effectiveGasPrice,
);
}
const tradeGasCost = calcGasTotal(
txMeta.txReceipt.gasUsed,
txMeta.txReceipt.effectiveGasPrice,
);
const tradeAndApprovalGasCost = new BigNumber(tradeGasCost, 16)
.plus(approvalGasCost, 16)
.toString(16);
return {
approvalGasCostInEth: Number(hexWEIToDecETH(approvalGasCost)),
tradeGasCostInEth: Number(hexWEIToDecETH(tradeGasCost)),
tradeAndApprovalGasCostInEth: Number(
hexWEIToDecETH(tradeAndApprovalGasCost),
),
};
}
_trackSwapsMetrics(txMeta, approvalTxMeta) {
if (this._getParticipateInMetrics() && txMeta.swapMetaData) {
if (txMeta.txReceipt.status === '0x0') {
this._trackMetaMetricsEvent({
event: 'Swap Failed',
sensitiveProperties: { ...txMeta.swapMetaData },
category: 'swaps',
category: EVENT.CATEGORIES.SWAPS,
});
} else {
const tokensReceived = getSwapsTokensReceivedFromTxMeta(
@ -1861,14 +1950,23 @@ export default class TransactionController extends EventEmitter {
.round(2)}%`
: null;
const transactionsCost = this._calculateTransactionsCost(
txMeta,
approvalTxMeta,
);
this._trackMetaMetricsEvent({
event: 'Swap Completed',
category: 'swaps',
category: EVENT.CATEGORIES.SWAPS,
sensitiveProperties: {
...txMeta.swapMetaData,
token_to_amount_received: tokensReceived,
quote_vs_executionRatio: quoteVsExecutionRatio,
estimated_vs_used_gasRatio: estimatedVsUsedGasRatio,
approval_gas_cost_in_eth: transactionsCost.approvalGasCostInEth,
trade_gas_cost_in_eth: transactionsCost.tradeGasCostInEth,
trade_and_approval_gas_cost_in_eth:
transactionsCost.tradeAndApprovalGasCostInEth,
},
});
}
@ -1893,7 +1991,7 @@ export default class TransactionController extends EventEmitter {
defaultGasEstimates,
metamaskNetworkId: network,
} = txMeta;
const source = referrer === 'metamask' ? 'user' : 'dapp';
const source = referrer === ORIGIN_METAMASK ? 'user' : 'dapp';
const { assetType, tokenStandard } = await determineTransactionAssetType(
txMeta,
@ -1955,6 +2053,10 @@ export default class TransactionController extends EventEmitter {
gasParams.estimate_used = estimateUsed;
}
if (extraParams?.gas_used) {
gasParams.gas_used = extraParams.gas_used;
}
const gasParamsInGwei = this._getGasValuesInGWEI(gasParams);
let eip1559Version = '0';
@ -1985,8 +2087,8 @@ export default class TransactionController extends EventEmitter {
: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
first_seen: time,
gas_limit: gasLimit,
...gasParamsInGwei,
...extraParams,
...gasParamsInGwei,
};
return { properties, sensitiveProperties };
@ -2036,7 +2138,7 @@ export default class TransactionController extends EventEmitter {
// occur.
case TRANSACTION_EVENTS.ADDED:
this.createEventFragment({
category: 'Transactions',
category: EVENT.CATEGORIES.TRANSACTIONS,
initialEvent: TRANSACTION_EVENTS.ADDED,
successEvent: TRANSACTION_EVENTS.APPROVED,
failureEvent: TRANSACTION_EVENTS.REJECTED,
@ -2057,7 +2159,7 @@ export default class TransactionController extends EventEmitter {
case TRANSACTION_EVENTS.APPROVED:
case TRANSACTION_EVENTS.REJECTED:
this.createEventFragment({
category: 'Transactions',
category: EVENT.CATEGORIES.TRANSACTIONS,
successEvent: TRANSACTION_EVENTS.APPROVED,
failureEvent: TRANSACTION_EVENTS.REJECTED,
properties,
@ -2078,7 +2180,7 @@ export default class TransactionController extends EventEmitter {
// properties to the transaction event.
case TRANSACTION_EVENTS.SUBMITTED:
this.createEventFragment({
category: 'Transactions',
category: EVENT.CATEGORIES.TRANSACTIONS,
initialEvent: TRANSACTION_EVENTS.SUBMITTED,
successEvent: TRANSACTION_EVENTS.FINALIZED,
properties,
@ -2097,7 +2199,7 @@ export default class TransactionController extends EventEmitter {
// fragment does not exist.
case TRANSACTION_EVENTS.FINALIZED:
this.createEventFragment({
category: 'Transactions',
category: EVENT.CATEGORIES.TRANSACTIONS,
successEvent: TRANSACTION_EVENTS.FINALIZED,
properties,
sensitiveProperties,

View File

@ -10,6 +10,7 @@ import {
getTestAccounts,
} from '../../../../test/stub/provider';
import mockEstimates from '../../../../test/data/mock-estimates.json';
import { EVENT } from '../../../../shared/constants/metametrics';
import {
TRANSACTION_STATUSES,
TRANSACTION_TYPES,
@ -26,6 +27,7 @@ import {
import { TRANSACTION_ENVELOPE_TYPE_NAMES } from '../../../../ui/helpers/constants/transactions';
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
import { TOKEN_STANDARDS } from '../../../../ui/helpers/constants/common';
import { ORIGIN_METAMASK } from '../../../../shared/constants/app';
import TransactionController from '.';
const noop = () => true;
@ -791,7 +793,7 @@ describe('Transaction Controller', function () {
},
type: TRANSACTION_TYPES.SIMPLE_SEND,
transaction_envelope_type: TRANSACTION_ENVELOPE_TYPE_NAMES.LEGACY,
origin: 'metamask',
origin: ORIGIN_METAMASK,
chainId: currentChainId,
time: 1624408066355,
metamaskNetworkId: currentNetworkId,
@ -1442,7 +1444,7 @@ describe('Transaction Controller', function () {
nonce: '0x4b',
},
type: TRANSACTION_TYPES.SIMPLE_SEND,
origin: 'metamask',
origin: ORIGIN_METAMASK,
chainId: currentChainId,
time: 1624408066355,
metamaskNetworkId: currentNetworkId,
@ -1459,7 +1461,7 @@ describe('Transaction Controller', function () {
successEvent: 'Transaction Approved',
failureEvent: 'Transaction Rejected',
uniqueIdentifier: 'transaction-added-1',
category: 'Transactions',
category: EVENT.CATEGORIES.TRANSACTIONS,
persist: true,
properties: {
chain_id: '0x2a',
@ -1467,8 +1469,8 @@ describe('Transaction Controller', function () {
gas_edit_attempted: 'none',
gas_edit_type: 'none',
network: '42',
referrer: 'metamask',
source: 'user',
referrer: ORIGIN_METAMASK,
source: EVENT.SOURCE.TRANSACTION.USER,
type: TRANSACTION_TYPES.SIMPLE_SEND,
account_type: 'MetaMask',
asset_type: ASSET_TYPES.NATIVE,
@ -1538,7 +1540,7 @@ describe('Transaction Controller', function () {
initialEvent: 'Transaction Submitted',
successEvent: 'Transaction Finalized',
uniqueIdentifier: 'transaction-submitted-1',
category: 'Transactions',
category: EVENT.CATEGORIES.TRANSACTIONS,
persist: true,
properties: {
chain_id: '0x2a',
@ -1546,8 +1548,8 @@ describe('Transaction Controller', function () {
gas_edit_attempted: 'none',
gas_edit_type: 'none',
network: '42',
referrer: 'metamask',
source: 'user',
referrer: ORIGIN_METAMASK,
source: EVENT.SOURCE.TRANSACTION.USER,
type: TRANSACTION_TYPES.SIMPLE_SEND,
account_type: 'MetaMask',
asset_type: ASSET_TYPES.NATIVE,
@ -1627,7 +1629,7 @@ describe('Transaction Controller', function () {
successEvent: 'Transaction Approved',
failureEvent: 'Transaction Rejected',
uniqueIdentifier: 'transaction-added-1',
category: 'Transactions',
category: EVENT.CATEGORIES.TRANSACTIONS,
persist: true,
properties: {
chain_id: '0x2a',
@ -1636,7 +1638,7 @@ describe('Transaction Controller', function () {
gas_edit_type: 'none',
network: '42',
referrer: 'other',
source: 'dapp',
source: EVENT.SOURCE.TRANSACTION.DAPP,
type: TRANSACTION_TYPES.SIMPLE_SEND,
account_type: 'MetaMask',
asset_type: ASSET_TYPES.NATIVE,
@ -1708,7 +1710,7 @@ describe('Transaction Controller', function () {
initialEvent: 'Transaction Submitted',
successEvent: 'Transaction Finalized',
uniqueIdentifier: 'transaction-submitted-1',
category: 'Transactions',
category: EVENT.CATEGORIES.TRANSACTIONS,
persist: true,
properties: {
chain_id: '0x2a',
@ -1717,7 +1719,7 @@ describe('Transaction Controller', function () {
gas_edit_type: 'none',
network: '42',
referrer: 'other',
source: 'dapp',
source: EVENT.SOURCE.TRANSACTION.DAPP,
type: TRANSACTION_TYPES.SIMPLE_SEND,
account_type: 'MetaMask',
asset_type: ASSET_TYPES.NATIVE,
@ -1789,7 +1791,7 @@ describe('Transaction Controller', function () {
successEvent: 'Transaction Approved',
failureEvent: 'Transaction Rejected',
uniqueIdentifier: 'transaction-added-1',
category: 'Transactions',
category: EVENT.CATEGORIES.TRANSACTIONS,
persist: true,
properties: {
chain_id: '0x2a',
@ -1798,7 +1800,7 @@ describe('Transaction Controller', function () {
gas_edit_type: 'none',
network: '42',
referrer: 'other',
source: 'dapp',
source: EVENT.SOURCE.TRANSACTION.DAPP,
type: TRANSACTION_TYPES.SIMPLE_SEND,
account_type: 'MetaMask',
asset_type: ASSET_TYPES.NATIVE,
@ -1853,11 +1855,11 @@ describe('Transaction Controller', function () {
failureEvent: 'Transaction Rejected',
uniqueIdentifier: 'transaction-added-1',
persist: true,
category: 'Transactions',
category: EVENT.CATEGORIES.TRANSACTIONS,
properties: {
network: '42',
referrer: 'other',
source: 'dapp',
source: EVENT.SOURCE.TRANSACTION.DAPP,
type: TRANSACTION_TYPES.SIMPLE_SEND,
chain_id: '0x2a',
eip_1559_version: '0',
@ -1926,7 +1928,7 @@ describe('Transaction Controller', function () {
failureEvent: 'Transaction Rejected',
uniqueIdentifier: 'transaction-added-1',
persist: true,
category: 'Transactions',
category: EVENT.CATEGORIES.TRANSACTIONS,
properties: {
chain_id: '0x2a',
eip_1559_version: '1',
@ -1934,7 +1936,7 @@ describe('Transaction Controller', function () {
gas_edit_type: 'none',
network: '42',
referrer: 'other',
source: 'dapp',
source: EVENT.SOURCE.TRANSACTION.DAPP,
type: TRANSACTION_TYPES.SIMPLE_SEND,
account_type: 'MetaMask',
asset_type: ASSET_TYPES.NATIVE,
@ -2243,4 +2245,125 @@ describe('Transaction Controller', function () {
assert.equal(result.destinationTokenAddress, VALID_ADDRESS_TWO); // not updated even though it's passed in to update
});
});
describe('updateEditableParams', function () {
let txStateManager;
beforeEach(function () {
txStateManager = txController.txStateManager;
txStateManager.addTransaction({
id: '1',
status: TRANSACTION_STATUSES.UNAPPROVED,
metamaskNetworkId: currentNetworkId,
txParams: {
gas: '0x001',
gasPrice: '0x002',
// max fees can not be mixed with gasPrice
// maxPriorityFeePerGas: '0x003',
// maxFeePerGas: '0x004',
to: VALID_ADDRESS,
from: VALID_ADDRESS,
},
estimateUsed: '0x005',
estimatedBaseFee: '0x006',
decEstimatedBaseFee: '6',
type: 'simpleSend',
userEditedGasLimit: '0x008',
userFeeLevel: 'medium',
});
});
it('updates editible params when type changes from simple send to token transfer', async function () {
// test update gasFees
await txController.updateEditableParams('1', {
data:
'0xa9059cbb000000000000000000000000e18035bf8712672935fdb4e5e431b1a0183d2dfc0000000000000000000000000000000000000000000000000de0b6b3a7640000',
});
const result = txStateManager.getTransaction('1');
assert.equal(
result.txParams.data,
'0xa9059cbb000000000000000000000000e18035bf8712672935fdb4e5e431b1a0183d2dfc0000000000000000000000000000000000000000000000000de0b6b3a7640000',
);
assert.equal(result.type, TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER);
});
it('updates editible params when type changes from token transfer to simple send', async function () {
// test update gasFees
txStateManager.addTransaction({
id: '2',
status: TRANSACTION_STATUSES.UNAPPROVED,
metamaskNetworkId: currentNetworkId,
txParams: {
gas: '0x001',
gasPrice: '0x002',
// max fees can not be mixed with gasPrice
// maxPriorityFeePerGas: '0x003',
// maxFeePerGas: '0x004',
to: VALID_ADDRESS,
from: VALID_ADDRESS,
data:
'0xa9059cbb000000000000000000000000e18035bf8712672935fdb4e5e431b1a0183d2dfc0000000000000000000000000000000000000000000000000de0b6b3a7640000',
},
estimateUsed: '0x005',
estimatedBaseFee: '0x006',
decEstimatedBaseFee: '6',
type: TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
userEditedGasLimit: '0x008',
userFeeLevel: 'medium',
});
await txController.updateEditableParams('2', {
data: '0x',
});
const result = txStateManager.getTransaction('2');
assert.equal(result.txParams.data, '0x');
assert.equal(result.type, TRANSACTION_TYPES.SIMPLE_SEND);
});
it('updates editible params when type changes from simpleSend to contract interaction', async function () {
// test update gasFees
txStateManager.addTransaction({
id: '3',
status: TRANSACTION_STATUSES.UNAPPROVED,
metamaskNetworkId: currentNetworkId,
txParams: {
gas: '0x001',
gasPrice: '0x002',
// max fees can not be mixed with gasPrice
// maxPriorityFeePerGas: '0x003',
// maxFeePerGas: '0x004',
to: VALID_ADDRESS,
from: VALID_ADDRESS,
},
estimateUsed: '0x005',
estimatedBaseFee: '0x006',
decEstimatedBaseFee: '6',
type: TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
userEditedGasLimit: '0x008',
userFeeLevel: 'medium',
});
providerResultStub.eth_getCode = '0x5';
await txController.updateEditableParams('3', {
data: '0x123',
});
const result = txStateManager.getTransaction('3');
assert.equal(result.txParams.data, '0x123');
assert.equal(result.type, TRANSACTION_TYPES.CONTRACT_INTERACTION);
});
it('updates editible params when type does not change', async function () {
// test update gasFees
await txController.updateEditableParams('1', {
data: '0x123',
gas: '0xabc',
from: VALID_ADDRESS_TWO,
});
const result = txStateManager.getTransaction('1');
assert.equal(result.txParams.data, '0x123');
assert.equal(result.txParams.gas, '0xabc');
assert.equal(result.txParams.from, VALID_ADDRESS_TWO);
assert.equal(result.txParams.to, VALID_ADDRESS);
assert.equal(result.txParams.gasPrice, '0x002');
assert.equal(result.type, TRANSACTION_TYPES.SIMPLE_SEND);
});
});
});

View File

@ -6,6 +6,7 @@ import createId from '../../../../shared/modules/random-id';
import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction';
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
import { transactionMatchesNetwork } from '../../../../shared/modules/transaction.utils';
import { ORIGIN_METAMASK } from '../../../../shared/constants/app';
import {
generateHistoryEntry,
replayHistory,
@ -92,7 +93,7 @@ export default class TransactionStateManager extends EventEmitter {
if (
opts.txParams &&
typeof opts.origin === 'string' &&
opts.origin !== 'metamask'
opts.origin !== ORIGIN_METAMASK
) {
if (typeof opts.txParams.gasPrice !== 'undefined') {
dappSuggestedGasFees = {
@ -126,6 +127,7 @@ export default class TransactionStateManager extends EventEmitter {
chainId,
loadingDefaults: true,
dappSuggestedGasFees,
sendFlowHistory: [],
...opts,
};
}

View File

@ -11,6 +11,7 @@ import {
KOVAN_NETWORK_ID,
} from '../../../../shared/constants/network';
import { GAS_LIMITS } from '../../../../shared/constants/gas';
import { ORIGIN_METAMASK } from '../../../../shared/constants/app';
import TxStateManager from './tx-state-manager';
import { snapshotFromTxMeta } from './lib/tx-state-history-helpers';
@ -1188,7 +1189,7 @@ describe('TransactionStateManager', function () {
};
const generatedTransaction = txStateManager.generateTxMeta({
txParams,
origin: 'metamask',
origin: ORIGIN_METAMASK,
});
assert.ok(generatedTransaction);
assert.strictEqual(generatedTransaction.dappSuggestedGasFees, null);

View File

@ -0,0 +1,55 @@
/**
* Sets up two-way communication between the
* mainline version of extension and Flask build
* in order to detect & warn if there are two different
* versions running simultaneously.
*/
import browser from 'webextension-polyfill';
import {
PLATFORM_CHROME,
PLATFORM_FIREFOX,
CHROME_BUILD_IDS,
FIREFOX_BUILD_IDS,
} from '../../shared/constants/app';
import { getPlatform } from './lib/util';
const MESSAGE_TEXT = 'isRunning';
const showWarning = () =>
console.warn('Warning! You have multiple instances of MetaMask running!');
/**
* Handles the ping message sent from other extension.
* Displays console warning if it's active.
*
* @param message - The message received from the other extension
*/
export const onMessageReceived = (message) => {
if (message === MESSAGE_TEXT) {
showWarning();
}
};
/**
* Sends the ping message sent to other extensions to detect whether it's active or not.
*/
export const checkForMultipleVersionsRunning = async () => {
if (getPlatform() !== PLATFORM_CHROME && getPlatform() !== PLATFORM_FIREFOX) {
return;
}
const buildIds =
getPlatform() === PLATFORM_CHROME ? CHROME_BUILD_IDS : FIREFOX_BUILD_IDS;
const thisBuild = browser.runtime.id;
for (const id of buildIds) {
if (id !== thisBuild) {
try {
await browser.runtime.sendMessage(id, MESSAGE_TEXT);
} catch (error) {
// Should do nothing if receiving end was not reached (no other instances running)
}
}
}
};

View File

@ -0,0 +1,104 @@
import { strict as assert } from 'assert';
import browser from 'webextension-polyfill';
import sinon from 'sinon';
import {
PLATFORM_CHROME,
PLATFORM_EDGE,
METAMASK_BETA_CHROME_ID,
METAMASK_PROD_CHROME_ID,
METAMASK_FLASK_CHROME_ID,
} from '../../shared/constants/app';
import {
checkForMultipleVersionsRunning,
onMessageReceived,
} from './detect-multiple-instances';
import * as util from './lib/util';
describe('multiple instances running detector', function () {
const PING_MESSAGE = 'isRunning';
let sendMessageStub = sinon.stub();
beforeEach(async function () {
sinon.replace(browser, 'runtime', {
sendMessage: sendMessageStub,
id: METAMASK_BETA_CHROME_ID,
});
sinon.stub(util, 'getPlatform').callsFake((_) => {
return PLATFORM_CHROME;
});
});
afterEach(function () {
sinon.restore();
});
describe('checkForMultipleVersionsRunning', function () {
it('should send ping message to multiple instances', async function () {
await checkForMultipleVersionsRunning();
assert(sendMessageStub.calledTwice);
assert(
sendMessageStub
.getCall(0)
.calledWithExactly(METAMASK_PROD_CHROME_ID, PING_MESSAGE),
);
assert(
sendMessageStub
.getCall(1)
.calledWithExactly(METAMASK_FLASK_CHROME_ID, PING_MESSAGE),
);
});
it('should not send ping message if platform is not Chrome or Firefox', async function () {
util.getPlatform.restore();
sendMessageStub = sinon.stub();
sinon.stub(util, 'getPlatform').callsFake((_) => {
return PLATFORM_EDGE;
});
await checkForMultipleVersionsRunning();
assert(sendMessageStub.notCalled);
});
it('should not expose an error outside if sendMessage throws', async function () {
sinon.restore();
sinon.replace(browser, 'runtime', {
sendMessage: sinon.stub().throws(),
id: METAMASK_BETA_CHROME_ID,
});
const spy = sinon.spy(checkForMultipleVersionsRunning);
await checkForMultipleVersionsRunning();
assert(!spy.threw());
});
});
describe('onMessageReceived', function () {
beforeEach(function () {
sinon.spy(console, 'warn');
});
it('should print warning message to on ping message received', async function () {
onMessageReceived(PING_MESSAGE);
assert(
console.warn.calledWithExactly(
'Warning! You have multiple instances of MetaMask running!',
),
);
});
it('should not print warning message if wrong message received', async function () {
onMessageReceived(PING_MESSAGE.concat('wrong'));
assert(console.warn.notCalled);
});
});
});

View File

@ -7,24 +7,32 @@ import {
MAINNET_CHAIN_ID,
RINKEBY_CHAIN_ID,
ROPSTEN_CHAIN_ID,
MAINNET_NETWORK_ID,
BUYABLE_CHAINS_MAP,
} from '../../../shared/constants/network';
import { SECOND } from '../../../shared/constants/time';
import getFetchWithTimeout from '../../../shared/modules/fetch-with-timeout';
import { TRANSAK_API_KEY, MOONPAY_API_KEY } from '../constants/on-ramp';
import {
TRANSAK_API_KEY,
MOONPAY_API_KEY,
COINBASEPAY_API_KEY,
} from '../constants/on-ramp';
const fetchWithTimeout = getFetchWithTimeout(SECOND * 30);
/**
* Create a Wyre purchase URL.
*
* @param {string} address - Ethereum destination address
* @param {string} walletAddress - Ethereum destination address
* @param {string} chainId - Current chain ID
* @returns String
*/
const createWyrePurchaseUrl = async (address) => {
const fiatOnRampUrlApi = `${SWAPS_API_V2_BASE_URL}/networks/${MAINNET_NETWORK_ID}/fiatOnRampUrl?serviceName=wyre&destinationAddress=${address}`;
const wyrePurchaseUrlFallback = `https://pay.sendwyre.com/purchase?dest=ethereum:${address}&destCurrency=ETH&accountId=AC-7AG3W4XH4N2&paymentMethod=debit-card`;
const createWyrePurchaseUrl = async (walletAddress, chainId) => {
const { wyre = {} } = BUYABLE_CHAINS_MAP[chainId];
const { srn, currencyCode } = wyre;
const networkId = parseInt(chainId, 16);
const fiatOnRampUrlApi = `${SWAPS_API_V2_BASE_URL}/networks/${networkId}/fiatOnRampUrl?serviceName=wyre&destinationAddress=${walletAddress}`;
const wyrePurchaseUrlFallback = `https://pay.sendwyre.com/purchase?dest=${srn}:${walletAddress}&destCurrency=${currencyCode}&accountId=AC-7AG3W4XH4N2&paymentMethod=debit-card`;
try {
const response = await fetchWithTimeout(fiatOnRampUrlApi, {
method: 'GET',
@ -108,6 +116,28 @@ const createMoonPayUrl = async (walletAddress, chainId) => {
return '';
};
/**
* Create a Coinbase Pay Checkout URL.
*
* @param {string} walletAddress - Ethereum destination address
* @param {string} chainId - Current chain ID
* @returns String
*/
const createCoinbasePayUrl = (walletAddress, chainId) => {
const { coinbasePayCurrencies } = BUYABLE_CHAINS_MAP[chainId];
const queryParams = new URLSearchParams({
appId: COINBASEPAY_API_KEY,
attribution: 'extension',
destinationWallets: JSON.stringify([
{
address: walletAddress,
assets: coinbasePayCurrencies,
},
]),
});
return `https://pay.coinbase.com/buy?${queryParams}`;
};
/**
* Gives the caller a url at which the user can acquire eth, depending on the network they are in
*
@ -127,11 +157,13 @@ export default async function getBuyUrl({ chainId, address, service }) {
switch (service) {
case 'wyre':
return await createWyrePurchaseUrl(address);
return await createWyrePurchaseUrl(address, chainId);
case 'transak':
return createTransakUrl(address, chainId);
case 'moonpay':
return createMoonPayUrl(address, chainId);
case 'coinbase':
return createCoinbasePayUrl(address, chainId);
case 'metamask-faucet':
return 'https://faucet.metamask.io/';
case 'rinkeby-faucet':

View File

@ -1,4 +1,4 @@
import { EVENT_NAMES } from '../../../shared/constants/metametrics';
import { EVENT, EVENT_NAMES } from '../../../shared/constants/metametrics';
import { SECOND } from '../../../shared/constants/time';
const USER_PROMPTED_EVENT_NAME_MAP = {
@ -46,7 +46,7 @@ export default function createRPCMethodTrackingMiddleware({
const userRejected = res.error?.code === 4001;
trackEvent({
event: USER_PROMPTED_EVENT_NAME_MAP[req.method],
category: 'inpage_provider',
category: EVENT.CATEGORIES.INPAGE_PROVIDER,
referrer: {
url: origin,
},
@ -62,7 +62,7 @@ export default function createRPCMethodTrackingMiddleware({
} else if (typeof samplingTimeouts[req.method] === 'undefined') {
trackEvent({
event: 'Provider Method Called',
category: 'inpage_provider',
category: EVENT.CATEGORIES.INPAGE_PROVIDER,
referrer: {
url: origin,
},

View File

@ -4,6 +4,7 @@ import { bufferToHex, stripHexPrefix } from 'ethereumjs-util';
import { ethErrors } from 'eth-rpc-errors';
import log from 'loglevel';
import { MESSAGE_TYPE } from '../../../shared/constants/app';
import { EVENT } from '../../../shared/constants/metametrics';
import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller';
import createId from '../../../shared/modules/random-id';
import { addHexPrefix } from './util';
@ -227,7 +228,7 @@ export default class DecryptMessageManager extends EventEmitter {
if (reason) {
this.metricsEvent({
event: reason,
category: 'Messages',
category: EVENT.CATEGORIES.MESSAGES,
properties: {
action: 'Decrypt Message Request',
},

View File

@ -3,6 +3,7 @@ import { ObservableStore } from '@metamask/obs-store';
import { ethErrors } from 'eth-rpc-errors';
import log from 'loglevel';
import { MESSAGE_TYPE } from '../../../shared/constants/app';
import { EVENT } from '../../../shared/constants/metametrics';
import { METAMASK_CONTROLLER_EVENTS } from '../metamask-controller';
import createId from '../../../shared/modules/random-id';
@ -216,7 +217,7 @@ export default class EncryptionPublicKeyManager extends EventEmitter {
if (reason) {
this.metricsEvent({
event: reason,
category: 'Messages',
category: EVENT.CATEGORIES.MESSAGES,
properties: {
action: 'Encryption public key Request',
},

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