commit
e283648d60
|
@ -31,14 +31,6 @@ workflows:
|
||||||
- test-unit:
|
- test-unit:
|
||||||
requires:
|
requires:
|
||||||
- prep-deps-npm
|
- prep-deps-npm
|
||||||
# - test-integration-mascara-chrome:
|
|
||||||
# requires:
|
|
||||||
# - prep-deps-npm
|
|
||||||
# - prep-scss
|
|
||||||
# - test-integration-mascara-firefox:
|
|
||||||
# requires:
|
|
||||||
# - prep-deps-npm
|
|
||||||
# - prep-scss
|
|
||||||
- test-integration-flat-chrome:
|
- test-integration-flat-chrome:
|
||||||
requires:
|
requires:
|
||||||
- prep-deps-npm
|
- prep-deps-npm
|
||||||
|
@ -53,8 +45,6 @@ workflows:
|
||||||
- test-unit
|
- test-unit
|
||||||
- test-e2e-chrome
|
- test-e2e-chrome
|
||||||
# - test-e2e-firefox
|
# - test-e2e-firefox
|
||||||
# - test-integration-mascara-chrome
|
|
||||||
# - test-integration-mascara-firefox
|
|
||||||
- test-integration-flat-chrome
|
- test-integration-flat-chrome
|
||||||
- test-integration-flat-firefox
|
- test-integration-flat-firefox
|
||||||
- job-screens:
|
- job-screens:
|
||||||
|
@ -292,9 +282,6 @@ jobs:
|
||||||
key: build-cache-{{ .Revision }}
|
key: build-cache-{{ .Revision }}
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: job-screens-{{ .Revision }}
|
key: job-screens-{{ .Revision }}
|
||||||
- store_artifacts:
|
|
||||||
path: dist/mascara
|
|
||||||
destination: builds/mascara
|
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: dist/sourcemaps
|
path: dist/sourcemaps
|
||||||
destination: builds/sourcemaps
|
destination: builds/sourcemaps
|
||||||
|
@ -380,37 +367,6 @@ jobs:
|
||||||
name: test:integration:flat
|
name: test:integration:flat
|
||||||
command: npm run test:flat
|
command: npm run test:flat
|
||||||
|
|
||||||
test-integration-mascara-firefox:
|
|
||||||
environment:
|
|
||||||
browsers: '["Firefox"]'
|
|
||||||
docker:
|
|
||||||
- image: circleci/node:10.19.0-browsers
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- restore_cache:
|
|
||||||
key: dependency-cache-firefox-{{ .Revision }}
|
|
||||||
- run:
|
|
||||||
name: Install firefox
|
|
||||||
command: ./.circleci/scripts/firefox-install
|
|
||||||
- restore_cache:
|
|
||||||
key: dependency-cache-{{ .Revision }}
|
|
||||||
- run:
|
|
||||||
name: test:integration:mascara
|
|
||||||
command: npm run test:mascara
|
|
||||||
|
|
||||||
test-integration-mascara-chrome:
|
|
||||||
environment:
|
|
||||||
browsers: '["Chrome"]'
|
|
||||||
docker:
|
|
||||||
- image: circleci/node:10.19.0-browsers
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- restore_cache:
|
|
||||||
key: dependency-cache-{{ .Revision }}
|
|
||||||
- run:
|
|
||||||
name: test:integration:mascara
|
|
||||||
command: npm run test:mascara
|
|
||||||
|
|
||||||
all-tests-pass:
|
all-tests-pass:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:10.19.0-browsers
|
- image: circleci/node:10.19.0-browsers
|
||||||
|
|
|
@ -15,9 +15,6 @@ app/vendor/**
|
||||||
|
|
||||||
ui/lib/blockies.js
|
ui/lib/blockies.js
|
||||||
|
|
||||||
mascara/src/app/first-time/spinner.js
|
|
||||||
mascara/test/jquery-3.1.0.min.js
|
|
||||||
|
|
||||||
test/integration/bundle.js
|
test/integration/bundle.js
|
||||||
test/integration/jquery-3.1.0.min.js
|
test/integration/jquery-3.1.0.min.js
|
||||||
test/integration/helpers.js
|
test/integration/helpers.js
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
; Extra environment variables
|
; Extra environment variables
|
||||||
ETH_MAINNET_RPC_ENDPOINT=00000000000
|
ETH_MAINNET_RPC_ENDPOINT=00000000000
|
||||||
|
INFURA_PROJECT_ID=00000000000
|
||||||
|
|
|
@ -4,7 +4,6 @@ dist/
|
||||||
docs/
|
docs/
|
||||||
fonts/
|
fonts/
|
||||||
images/
|
images/
|
||||||
mascara/
|
|
||||||
node_modules/
|
node_modules/
|
||||||
notices/
|
notices/
|
||||||
test/
|
test/
|
||||||
|
|
|
@ -2,6 +2,12 @@
|
||||||
|
|
||||||
## Current Master
|
## Current Master
|
||||||
|
|
||||||
|
## 5.2.4 Sat Feb 27 2021
|
||||||
|
|
||||||
|
- [#451](https://github.com/poanetwork/nifty-wallet/pull/451) - (Fix) Fix export private key when switching between chains
|
||||||
|
- [#450](https://github.com/poanetwork/nifty-wallet/pull/450) - (Chore) Remove deprecated Infura network status check
|
||||||
|
- [#443](https://github.com/poanetwork/nifty-wallet/pull/443) - (Fix) Fire 'confirmation', 'receipt' events
|
||||||
|
|
||||||
## 5.2.3 Fri Jan 15 2021
|
## 5.2.3 Fri Jan 15 2021
|
||||||
|
|
||||||
- [#441](https://github.com/poanetwork/nifty-wallet/pull/441) - Replace Infura Mainnet endpoint with custom one
|
- [#441](https://github.com/poanetwork/nifty-wallet/pull/441) - Replace Infura Mainnet endpoint with custom one
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "__MSG_appName__",
|
"name": "__MSG_appName__",
|
||||||
"short_name": "__MSG_appName__",
|
"short_name": "__MSG_appName__",
|
||||||
"version": "5.2.3",
|
"version": "5.2.4",
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"author": "POA Network",
|
"author": "POA Network",
|
||||||
"description": "__MSG_appDescription__",
|
"description": "__MSG_appDescription__",
|
||||||
|
|
|
@ -10,22 +10,19 @@ import pump from 'pump'
|
||||||
import debounce from 'debounce-stream'
|
import debounce from 'debounce-stream'
|
||||||
import log from 'loglevel'
|
import log from 'loglevel'
|
||||||
import extension from 'extensionizer'
|
import extension from 'extensionizer'
|
||||||
const LocalStorageStore = require('obs-store/lib/localStorage')
|
import LocalStore from './lib/local-store'
|
||||||
const LocalStore = require('./lib/local-store')
|
import { storeAsStream, storeTransformStream } from '@metamask/obs-store'
|
||||||
const storeTransform = require('obs-store/lib/transform')
|
import ExtensionPlatform from './platforms/extension'
|
||||||
const asStream = require('obs-store/lib/asStream')
|
import migrations from './migrations'
|
||||||
const ExtensionPlatform = require('./platforms/extension')
|
import Migrator from './lib/migrator'
|
||||||
const Migrator = require('./lib/migrator/')
|
import PortStream from 'extension-port-stream'
|
||||||
const migrations = require('./migrations/')
|
import createStreamSink from './lib/createStreamSink'
|
||||||
const PortStream = require('extension-port-stream')
|
|
||||||
const createStreamSink = require('./lib/createStreamSink')
|
|
||||||
import NotificationManager from './lib/notification-manager.js'
|
import NotificationManager from './lib/notification-manager.js'
|
||||||
const MetamaskController = require('./metamask-controller')
|
const MetamaskController = require('./metamask-controller')
|
||||||
const rawFirstTimeState = require('./first-time-state')
|
import rawFirstTimeState from './first-time-state'
|
||||||
const setupRaven = require('./lib/setupRaven')
|
const setupRaven = require('./lib/setupRaven')
|
||||||
const reportFailedTxToSentry = require('./lib/reportFailedTxToSentry')
|
import getFirstPreferredLangCode from './lib/get-first-preferred-lang-code'
|
||||||
const getFirstPreferredLangCode = require('./lib/get-first-preferred-lang-code')
|
import getObjStructure from './lib/getObjStructure'
|
||||||
const getObjStructure = require('./lib/getObjStructure')
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
ENVIRONMENT_TYPE_POPUP,
|
ENVIRONMENT_TYPE_POPUP,
|
||||||
|
@ -36,12 +33,12 @@ const {
|
||||||
// METAMASK_TEST_CONFIG is used in e2e tests to set the default network to localhost
|
// METAMASK_TEST_CONFIG is used in e2e tests to set the default network to localhost
|
||||||
const firstTimeState = Object.assign({}, rawFirstTimeState, global.METAMASK_TEST_CONFIG)
|
const firstTimeState = Object.assign({}, rawFirstTimeState, global.METAMASK_TEST_CONFIG)
|
||||||
|
|
||||||
const STORAGE_KEY = 'metamask-config'
|
|
||||||
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
|
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
|
||||||
|
|
||||||
log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn')
|
log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn')
|
||||||
|
|
||||||
const platform = new ExtensionPlatform()
|
const platform = new ExtensionPlatform()
|
||||||
|
|
||||||
const notificationManager = new NotificationManager()
|
const notificationManager = new NotificationManager()
|
||||||
global.METAMASK_NOTIFIER = notificationManager
|
global.METAMASK_NOTIFIER = notificationManager
|
||||||
|
|
||||||
|
@ -55,7 +52,6 @@ const openMetamaskTabsIDs = {}
|
||||||
const requestAccountTabIds = {}
|
const requestAccountTabIds = {}
|
||||||
|
|
||||||
// state persistence
|
// state persistence
|
||||||
const diskStore = new LocalStorageStore({ storageKey: STORAGE_KEY })
|
|
||||||
const localStore = new LocalStore()
|
const localStore = new LocalStore()
|
||||||
let versionedData
|
let versionedData
|
||||||
|
|
||||||
|
@ -74,9 +70,6 @@ initialize().catch(log.error)
|
||||||
* @property {boolean} loadingDefaults - TODO: Document
|
* @property {boolean} loadingDefaults - TODO: Document
|
||||||
* @property {Object} txParams - The tx params as passed to the network provider.
|
* @property {Object} txParams - The tx params as passed to the network provider.
|
||||||
* @property {Object[]} history - A history of mutations to this TransactionMeta object.
|
* @property {Object[]} history - A history of mutations to this TransactionMeta object.
|
||||||
* @property {boolean} gasPriceSpecified - True if the suggesting dapp specified a gas price, prevents auto-estimation.
|
|
||||||
* @property {boolean} gasLimitSpecified - True if the suggesting dapp specified a gas limit, prevents auto-estimation.
|
|
||||||
* @property {string} estimatedGas - A hex string represented the estimated gas limit required to complete the transaction.
|
|
||||||
* @property {string} origin - A string representing the interface that suggested the transaction.
|
* @property {string} origin - A string representing the interface that suggested the transaction.
|
||||||
* @property {Object} nonceDetails - A metadata object containing information used to derive the suggested nonce, useful for debugging nonce issues.
|
* @property {Object} nonceDetails - A metadata object containing information used to derive the suggested nonce, useful for debugging nonce issues.
|
||||||
* @property {string} rawTx - A hex string of the final signed transaction, ready to submit to the network.
|
* @property {string} rawTx - A hex string of the final signed transaction, ready to submit to the network.
|
||||||
|
@ -90,23 +83,15 @@ initialize().catch(log.error)
|
||||||
* @property {boolean} isInitialized - Whether the first vault has been created.
|
* @property {boolean} isInitialized - Whether the first vault has been created.
|
||||||
* @property {boolean} isUnlocked - Whether the vault is currently decrypted and accounts are available for selection.
|
* @property {boolean} isUnlocked - Whether the vault is currently decrypted and accounts are available for selection.
|
||||||
* @property {boolean} isAccountMenuOpen - Represents whether the main account selection UI is currently displayed.
|
* @property {boolean} isAccountMenuOpen - Represents whether the main account selection UI is currently displayed.
|
||||||
* @property {boolean} isMascara - True if the current context is the extensionless MetaMascara project.
|
|
||||||
* @property {boolean} isPopup - Returns true if the current view is an externally-triggered notification.
|
|
||||||
* @property {string} rpcTarget - DEPRECATED - The URL of the current RPC provider.
|
|
||||||
* @property {Object} identities - An object matching lower-case hex addresses to Identity objects with "address" and "name" (nickname) keys.
|
* @property {Object} identities - An object matching lower-case hex addresses to Identity objects with "address" and "name" (nickname) keys.
|
||||||
* @property {Object} unapprovedTxs - An object mapping transaction hashes to unapproved transactions.
|
* @property {Object} unapprovedTxs - An object mapping transaction hashes to unapproved transactions.
|
||||||
* @property {boolean} noActiveNotices - False if there are notices the user should confirm before using the application.
|
|
||||||
* @property {Array} frequentRpcList - A list of frequently used RPCs, including custom user-provided ones.
|
* @property {Array} frequentRpcList - A list of frequently used RPCs, including custom user-provided ones.
|
||||||
* @property {Array} addressBook - A list of previously sent to addresses.
|
* @property {Array} addressBook - A list of previously sent to addresses.
|
||||||
* @property {address} selectedTokenAddress - Used to indicate if a token is globally selected. Should be deprecated in favor of UI-centric token selection.
|
* @property {Object} contractExchangeRates - Info about current token prices.
|
||||||
* @property {Object} tokenExchangeRates - Info about current token prices.
|
|
||||||
* @property {Array} tokens - Tokens held by the current user, including their balances.
|
* @property {Array} tokens - Tokens held by the current user, including their balances.
|
||||||
* @property {Object} send - TODO: Document
|
* @property {Object} send - TODO: Document
|
||||||
* @property {Object} coinOptions - TODO: Document
|
|
||||||
* @property {boolean} useBlockie - Indicates preferred user identicon format. True for blockie, false for Jazzicon.
|
* @property {boolean} useBlockie - Indicates preferred user identicon format. True for blockie, false for Jazzicon.
|
||||||
* @property {Object} featureFlags - An object for optional feature flags.
|
* @property {Object} featureFlags - An object for optional feature flags.
|
||||||
* @property {string} networkEndpointType - TODO: Document
|
|
||||||
* @property {boolean} isRevealingSeedWords - True if seed words are currently being recovered, and should be shown to user.
|
|
||||||
* @property {boolean} welcomeScreen - True if welcome screen should be shown.
|
* @property {boolean} welcomeScreen - True if welcome screen should be shown.
|
||||||
* @property {string} currentLocale - A locale string matching the user's preferred display language.
|
* @property {string} currentLocale - A locale string matching the user's preferred display language.
|
||||||
* @property {Object} provider - The current selected network provider.
|
* @property {Object} provider - The current selected network provider.
|
||||||
|
@ -116,24 +101,24 @@ initialize().catch(log.error)
|
||||||
* @property {string} dPath - A path to derive accounts.
|
* @property {string} dPath - A path to derive accounts.
|
||||||
* @property {Object} accounts - An object mapping lower-case hex addresses to objects with "balance" and "address" keys, both storing hex string values.
|
* @property {Object} accounts - An object mapping lower-case hex addresses to objects with "balance" and "address" keys, both storing hex string values.
|
||||||
* @property {hex} currentBlockGasLimit - The most recently seen block gas limit, in a lower case hex prefixed string.
|
* @property {hex} currentBlockGasLimit - The most recently seen block gas limit, in a lower case hex prefixed string.
|
||||||
* @property {TransactionMeta[]} selectedAddressTxList - An array of transactions associated with the currently selected account.
|
* @property {TransactionMeta[]} currentNetworkTxList - An array of transactions associated with the currently selected network.
|
||||||
* @property {Object} unapprovedMsgs - An object of messages associated with the currently selected account, mapping a unique ID to the options.
|
* @property {Object} unapprovedMsgs - An object of messages pending approval, mapping a unique ID to the options.
|
||||||
* @property {number} unapprovedMsgCount - The number of messages in unapprovedMsgs.
|
* @property {number} unapprovedMsgCount - The number of messages in unapprovedMsgs.
|
||||||
* @property {Object} unapprovedPersonalMsgs - An object of messages associated with the currently selected account, mapping a unique ID to the options.
|
* @property {Object} unapprovedPersonalMsgs - An object of messages pending approval, mapping a unique ID to the options.
|
||||||
* @property {number} unapprovedPersonalMsgCount - The number of messages in unapprovedPersonalMsgs.
|
* @property {number} unapprovedPersonalMsgCount - The number of messages in unapprovedPersonalMsgs.
|
||||||
* @property {Object} unapprovedTypedMsgs - An object of messages associated with the currently selected account, mapping a unique ID to the options.
|
* @property {Object} unapprovedEncryptionPublicKeyMsgs - An object of messages pending approval, mapping a unique ID to the options.
|
||||||
|
* @property {number} unapprovedEncryptionPublicKeyMsgCount - The number of messages in EncryptionPublicKeyMsgs.
|
||||||
|
* @property {Object} unapprovedDecryptMsgs - An object of messages pending approval, mapping a unique ID to the options.
|
||||||
|
* @property {number} unapprovedDecryptMsgCount - The number of messages in unapprovedDecryptMsgs.
|
||||||
|
* @property {Object} unapprovedTypedMsgs - An object of messages pending approval, mapping a unique ID to the options.
|
||||||
* @property {number} unapprovedTypedMsgCount - The number of messages in unapprovedTypedMsgs.
|
* @property {number} unapprovedTypedMsgCount - The number of messages in unapprovedTypedMsgs.
|
||||||
|
* @property {number} pendingApprovalCount - The number of pending request in the approval controller.
|
||||||
* @property {string[]} keyringTypes - An array of unique keyring identifying strings, representing available strategies for creating accounts.
|
* @property {string[]} keyringTypes - An array of unique keyring identifying strings, representing available strategies for creating accounts.
|
||||||
* @property {Keyring[]} keyrings - An array of keyring descriptions, summarizing the accounts that are available for use, and what keyrings they belong to.
|
* @property {Keyring[]} keyrings - An array of keyring descriptions, summarizing the accounts that are available for use, and what keyrings they belong to.
|
||||||
* @property {Object} computedBalances - Maps accounts to their balances, accounting for balance changes from pending transactions.
|
|
||||||
* @property {string} currentAccountTab - A view identifying string for displaying the current displayed view, allows user to have a preferred tab in the old UI (between tokens and history).
|
|
||||||
* @property {string} selectedAddress - A lower case hex string of the currently selected address.
|
* @property {string} selectedAddress - A lower case hex string of the currently selected address.
|
||||||
* @property {string} currentCurrency - A string identifying the user's preferred display currency, for use in showing conversion rates.
|
* @property {string} currentCurrency - A string identifying the user's preferred display currency, for use in showing conversion rates.
|
||||||
* @property {number} conversionRate - A number representing the current exchange rate from the user's preferred currency to Ether.
|
* @property {number} conversionRate - A number representing the current exchange rate from the user's preferred currency to Ether.
|
||||||
* @property {number} conversionDate - A unix epoch date (ms) for the time the current conversion rate was last retrieved.
|
* @property {number} conversionDate - A unix epoch date (ms) for the time the current conversion rate was last retrieved.
|
||||||
* @property {Object} infuraNetworkStatus - An object of infura network status checks.
|
|
||||||
* @property {Block[]} recentBlocks - An array of recent blocks, used to calculate an effective but cheap gas price.
|
|
||||||
* @property {Array} shapeShiftTxList - An array of objects describing shapeshift exchange attempts.
|
|
||||||
* @property {boolean} forgottenPassword - Returns true if the user has initiated the password recovery screen, is recovering from seed phrase.
|
* @property {boolean} forgottenPassword - Returns true if the user has initiated the password recovery screen, is recovering from seed phrase.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -170,31 +155,18 @@ async function loadStateFromPersistence () {
|
||||||
|
|
||||||
// read from disk
|
// read from disk
|
||||||
// first from preferred, async API:
|
// first from preferred, async API:
|
||||||
versionedData = (await localStore.get()) ||
|
versionedData =
|
||||||
diskStore.getState() ||
|
(await localStore.get()) || migrator.generateInitialState(firstTimeState)
|
||||||
migrator.generateInitialState(firstTimeState)
|
|
||||||
|
|
||||||
// check if somehow state is empty
|
// check if somehow state is empty
|
||||||
// this should never happen but new error reporting suggests that it has
|
// this should never happen but new error reporting suggests that it has
|
||||||
// for a small number of users
|
// for a small number of users
|
||||||
// https://github.com/metamask/metamask-extension/issues/3919
|
// https://github.com/metamask/metamask-extension/issues/3919
|
||||||
if (versionedData && !versionedData.data) {
|
if (versionedData && !versionedData.data) {
|
||||||
// try to recover from diskStore incase only localStore is bad
|
|
||||||
const diskStoreState = diskStore.getState()
|
|
||||||
if (diskStoreState && diskStoreState.data) {
|
|
||||||
// we were able to recover (though it might be old)
|
|
||||||
versionedData = diskStoreState
|
|
||||||
const vaultStructure = getObjStructure(versionedData)
|
|
||||||
raven.captureMessage('Nifty Wallet - Empty vault found - recovered from diskStore', {
|
|
||||||
// "extra" key is required by Sentry
|
|
||||||
extra: { vaultStructure },
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// unable to recover, clear state
|
// unable to recover, clear state
|
||||||
versionedData = migrator.generateInitialState(firstTimeState)
|
versionedData = migrator.generateInitialState(firstTimeState)
|
||||||
raven.captureMessage('Nifty Wallet - Empty vault found - unable to recover')
|
raven.captureMessage('Nifty Wallet - Empty vault found - unable to recover')
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// report migration errors to sentry
|
// report migration errors to sentry
|
||||||
migrator.on('error', (err) => {
|
migrator.on('error', (err) => {
|
||||||
|
@ -242,6 +214,7 @@ function setupController (initState, initLangCode) {
|
||||||
//
|
//
|
||||||
|
|
||||||
const controller = new MetamaskController({
|
const controller = new MetamaskController({
|
||||||
|
infuraProjectId: process.env.INFURA_PROJECT_ID,
|
||||||
ethMainnetRpcEndpoint: process.env.ETH_MAINNET_RPC_ENDPOINT,
|
ethMainnetRpcEndpoint: process.env.ETH_MAINNET_RPC_ENDPOINT,
|
||||||
// User confirmation callbacks:
|
// User confirmation callbacks:
|
||||||
showUnconfirmedMessage: triggerUi,
|
showUnconfirmedMessage: triggerUi,
|
||||||
|
@ -254,6 +227,7 @@ function setupController (initState, initLangCode) {
|
||||||
initLangCode,
|
initLangCode,
|
||||||
// platform specific api
|
// platform specific api
|
||||||
platform,
|
platform,
|
||||||
|
extension,
|
||||||
getRequestAccountTabIds: () => {
|
getRequestAccountTabIds: () => {
|
||||||
return requestAccountTabIds
|
return requestAccountTabIds
|
||||||
},
|
},
|
||||||
|
@ -263,22 +237,11 @@ function setupController (initState, initLangCode) {
|
||||||
})
|
})
|
||||||
global.metamaskController = controller
|
global.metamaskController = controller
|
||||||
|
|
||||||
// report failed transactions to Sentry
|
|
||||||
controller.txController.on(`tx:status-update`, (txId, status) => {
|
|
||||||
if (status !== 'failed') return
|
|
||||||
const txMeta = controller.txController.txStateManager.getTx(txId)
|
|
||||||
try {
|
|
||||||
reportFailedTxToSentry({ raven, txMeta })
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// setup state persistence
|
// setup state persistence
|
||||||
pump(
|
pump(
|
||||||
asStream(controller.store),
|
storeAsStream(controller.store),
|
||||||
debounce(1000),
|
debounce(1000),
|
||||||
storeTransform(versionifyData),
|
storeTransformStream(versionifyData),
|
||||||
createStreamSink(persistData),
|
createStreamSink(persistData),
|
||||||
(error) => {
|
(error) => {
|
||||||
log.error('Nifty Wallet - Persistence pipeline failed', error)
|
log.error('Nifty Wallet - Persistence pipeline failed', error)
|
||||||
|
@ -295,6 +258,8 @@ function setupController (initState, initLangCode) {
|
||||||
return versionedData
|
return versionedData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let dataPersistenceFailing = false
|
||||||
|
|
||||||
async function persistData (state) {
|
async function persistData (state) {
|
||||||
if (!state) {
|
if (!state) {
|
||||||
throw new Error('Nifty Wallet - updated state is missing')
|
throw new Error('Nifty Wallet - updated state is missing')
|
||||||
|
@ -305,8 +270,14 @@ function setupController (initState, initLangCode) {
|
||||||
if (localStore.isSupported) {
|
if (localStore.isSupported) {
|
||||||
try {
|
try {
|
||||||
await localStore.set(state)
|
await localStore.set(state)
|
||||||
|
if (dataPersistenceFailing) {
|
||||||
|
dataPersistenceFailing = false
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// log error so we dont break the pipeline
|
// log error so we dont break the pipeline
|
||||||
|
if (!dataPersistenceFailing) {
|
||||||
|
dataPersistenceFailing = true
|
||||||
|
}
|
||||||
log.error('error setting state in local store:', err)
|
log.error('error setting state in local store:', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -324,7 +295,7 @@ function setupController (initState, initLangCode) {
|
||||||
[ENVIRONMENT_TYPE_FULLSCREEN]: true,
|
[ENVIRONMENT_TYPE_FULLSCREEN]: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
const metamaskBlacklistedPorts = [
|
const metamaskBlockedPorts = [
|
||||||
'trezor-connect',
|
'trezor-connect',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -348,8 +319,8 @@ function setupController (initState, initLangCode) {
|
||||||
const processName = remotePort.name
|
const processName = remotePort.name
|
||||||
const isMetaMaskInternalProcess = metamaskInternalProcessHash[processName]
|
const isMetaMaskInternalProcess = metamaskInternalProcessHash[processName]
|
||||||
|
|
||||||
if (metamaskBlacklistedPorts.includes(remotePort.name)) {
|
if (metamaskBlockedPorts.includes(remotePort.name)) {
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMetaMaskInternalProcess) {
|
if (isMetaMaskInternalProcess) {
|
||||||
|
@ -389,7 +360,7 @@ function setupController (initState, initLangCode) {
|
||||||
if (remotePort.sender && remotePort.sender.tab && remotePort.sender.url) {
|
if (remotePort.sender && remotePort.sender.tab && remotePort.sender.url) {
|
||||||
const tabId = remotePort.sender.tab.id
|
const tabId = remotePort.sender.tab.id
|
||||||
const url = new URL(remotePort.sender.url)
|
const url = new URL(remotePort.sender.url)
|
||||||
const origin = url.hostname
|
const { origin } = url
|
||||||
|
|
||||||
remotePort.onMessage.addListener((msg) => {
|
remotePort.onMessage.addListener((msg) => {
|
||||||
if (msg.data && msg.data.method === 'eth_requestAccounts') {
|
if (msg.data && msg.data.method === 'eth_requestAccounts') {
|
||||||
|
@ -426,11 +397,13 @@ function setupController (initState, initLangCode) {
|
||||||
function updateBadge () {
|
function updateBadge () {
|
||||||
let label = ''
|
let label = ''
|
||||||
const unapprovedTxCount = controller.txController.getUnapprovedTxCount()
|
const unapprovedTxCount = controller.txController.getUnapprovedTxCount()
|
||||||
const unapprovedMsgCount = controller.messageManager.unapprovedMsgCount
|
const { unapprovedMsgCount } = controller.messageManager
|
||||||
const unapprovedPersonalMsgCount = controller.personalMessageManager.unapprovedPersonalMsgCount
|
const { unapprovedPersonalMsgCount } = controller.personalMessageManager
|
||||||
const unapprovedDecryptMsgCount = controller.decryptMessageManager.unapprovedDecryptMsgCount
|
const { unapprovedDecryptMsgCount } = controller.decryptMessageManager
|
||||||
const unapprovedEncryptionPublicKeyMsgCount = controller.encryptionPublicKeyManager.unapprovedEncryptionPublicKeyMsgCount
|
const {
|
||||||
const unapprovedTypedMessagesCount = controller.typedMessageManager.unapprovedTypedMessagesCount
|
unapprovedEncryptionPublicKeyMsgCount,
|
||||||
|
} = controller.encryptionPublicKeyManager
|
||||||
|
const { unapprovedTypedMessagesCount } = controller.typedMessageManager
|
||||||
const count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgCount + unapprovedDecryptMsgCount + unapprovedEncryptionPublicKeyMsgCount +
|
const count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgCount + unapprovedDecryptMsgCount + unapprovedEncryptionPublicKeyMsgCount +
|
||||||
unapprovedTypedMessagesCount
|
unapprovedTypedMessagesCount
|
||||||
if (count) {
|
if (count) {
|
||||||
|
@ -452,14 +425,16 @@ function setupController (initState, initLangCode) {
|
||||||
*/
|
*/
|
||||||
async function triggerUi () {
|
async function triggerUi () {
|
||||||
const tabs = await platform.getActiveTabs()
|
const tabs = await platform.getActiveTabs()
|
||||||
const currentlyActiveMetamaskTab = Boolean(tabs.find((tab) => openMetamaskTabsIDs[tab.id]))
|
const currentlyActiveMetamaskTab = Boolean(
|
||||||
/**
|
tabs.find((tab) => openMetamaskTabsIDs[tab.id]),
|
||||||
* https://github.com/poanetwork/metamask-extension/issues/19
|
)
|
||||||
* !notificationIsOpen was removed from the check, because notification can be opened, but it can be behind the DApp
|
// Vivaldi is not closing port connection on popup close, so popupIsOpen does not work correctly
|
||||||
* for some reasons. For example, if notification popup was opened, but user moved focus to DApp.
|
// To be reviewed in the future if this behaviour is fixed - also the way we determine isVivaldi variable might change at some point
|
||||||
* New transaction, in this case, will not appear in front of DApp.
|
const isVivaldi =
|
||||||
*/
|
tabs.length > 0 &&
|
||||||
if (!popupIsOpen && !currentlyActiveMetamaskTab) {
|
tabs[0].extData &&
|
||||||
|
tabs[0].extData.indexOf('vivaldi_tab') > -1
|
||||||
|
if ((isVivaldi || !popupIsOpen) && !currentlyActiveMetamaskTab) {
|
||||||
await notificationManager.showPopup()
|
await notificationManager.showPopup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -470,14 +445,12 @@ async function triggerUi () {
|
||||||
*/
|
*/
|
||||||
async function openPopup () {
|
async function openPopup () {
|
||||||
await triggerUi()
|
await triggerUi()
|
||||||
await new Promise(
|
await new Promise((resolve) => {
|
||||||
(resolve) => {
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
if (!notificationIsOpen) {
|
if (!notificationIsOpen) {
|
||||||
clearInterval(interval)
|
clearInterval(interval)
|
||||||
resolve()
|
resolve()
|
||||||
}
|
}
|
||||||
}, 1000)
|
}, 1000)
|
||||||
},
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,123 +1,226 @@
|
||||||
|
import querystring from 'querystring'
|
||||||
|
import pump from 'pump'
|
||||||
|
import LocalMessageDuplexStream from 'post-message-stream'
|
||||||
|
import ObjectMultiplex from 'obj-multiplex'
|
||||||
|
import extension from 'extensionizer'
|
||||||
|
import PortStream from 'extension-port-stream'
|
||||||
|
import { obj as createThoughStream } from 'through2'
|
||||||
|
|
||||||
|
// These require calls need to use require to be statically recognized by browserify
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const pump = require('pump')
|
|
||||||
const querystring = require('querystring')
|
|
||||||
const LocalMessageDuplexStream = require('post-message-stream')
|
|
||||||
const PongStream = require('ping-pong-stream/pong')
|
|
||||||
const ObjectMultiplex = require('obj-multiplex')
|
|
||||||
const extension = require('extensionizer')
|
|
||||||
const PortStream = require('extension-port-stream')
|
|
||||||
|
|
||||||
const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js'), 'utf8').toString()
|
const inpageContent = fs.readFileSync(
|
||||||
const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('inpage.js') + '\n'
|
path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js'),
|
||||||
|
'utf8',
|
||||||
|
)
|
||||||
|
const inpageSuffix = `//# sourceURL=${extension.runtime.getURL('inpage.js')}\n`
|
||||||
const inpageBundle = inpageContent + inpageSuffix
|
const inpageBundle = inpageContent + inpageSuffix
|
||||||
|
|
||||||
// Eventually this streaming injection could be replaced with:
|
const CONTENT_SCRIPT = 'nifty-contentscript'
|
||||||
// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction
|
const INPAGE = 'nifty-inpage'
|
||||||
//
|
const PROVIDER = 'metamask-provider'
|
||||||
// But for now that is only Firefox
|
|
||||||
// If we create a FireFox-only code path using that API,
|
|
||||||
// MetaMask will be much faster loading and performant on Firefox.
|
|
||||||
|
|
||||||
if (shouldInjectWeb3()) {
|
// TODO:LegacyProvider: Delete
|
||||||
setupInjection()
|
const LEGACY_CONTENT_SCRIPT = 'contentscript'
|
||||||
|
const LEGACY_INPAGE = 'inpage'
|
||||||
|
const LEGACY_PROVIDER = 'provider'
|
||||||
|
const LEGACY_PUBLIC_CONFIG = 'publicConfig'
|
||||||
|
|
||||||
|
if (shouldInjectProvider()) {
|
||||||
|
injectScript(inpageBundle)
|
||||||
setupStreams()
|
setupStreams()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a script tag that injects inpage.js
|
* Injects a script tag into the current document
|
||||||
|
*
|
||||||
|
* @param {string} content - Code to be executed in the current document
|
||||||
*/
|
*/
|
||||||
function setupInjection () {
|
function injectScript (content) {
|
||||||
try {
|
try {
|
||||||
// inject in-page script
|
|
||||||
const scriptTag = document.createElement('script')
|
|
||||||
scriptTag.textContent = inpageBundle
|
|
||||||
scriptTag.onload = function () { this.parentNode.removeChild(this) }
|
|
||||||
const container = document.head || document.documentElement
|
const container = document.head || document.documentElement
|
||||||
// append as first child
|
const scriptTag = document.createElement('script')
|
||||||
|
scriptTag.setAttribute('async', 'false')
|
||||||
|
scriptTag.textContent = content
|
||||||
container.insertBefore(scriptTag, container.children[0])
|
container.insertBefore(scriptTag, container.children[0])
|
||||||
} catch (e) {
|
container.removeChild(scriptTag)
|
||||||
console.error('Nifty Wallet injection failed.', e)
|
} catch (error) {
|
||||||
|
console.error('Nifty Wallet injection failed.', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up two-way communication streams between the
|
* Sets up two-way communication streams between the
|
||||||
* browser extension and local per-page browser context
|
* browser extension and local per-page browser context.
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
function setupStreams () {
|
async function setupStreams () {
|
||||||
// setup communication to page and plugin
|
// the transport-specific streams for communication between inpage and background
|
||||||
const pageStream = new LocalMessageDuplexStream({
|
const pageStream = new LocalMessageDuplexStream({
|
||||||
name: 'nifty-contentscript',
|
name: CONTENT_SCRIPT,
|
||||||
target: 'nifty-inpage',
|
target: INPAGE,
|
||||||
|
})
|
||||||
|
const extensionPort = extension.runtime.connect({ name: CONTENT_SCRIPT })
|
||||||
|
const extensionStream = new PortStream(extensionPort)
|
||||||
|
|
||||||
|
// create and connect channel muxers
|
||||||
|
// so we can handle the channels individually
|
||||||
|
const pageMux = new ObjectMultiplex()
|
||||||
|
pageMux.setMaxListeners(25)
|
||||||
|
const extensionMux = new ObjectMultiplex()
|
||||||
|
extensionMux.setMaxListeners(25)
|
||||||
|
extensionMux.ignoreStream(LEGACY_PUBLIC_CONFIG) // TODO:LegacyProvider: Delete
|
||||||
|
|
||||||
|
pump(pageMux, pageStream, pageMux, (err) =>
|
||||||
|
logStreamDisconnectWarning('Nifty Wallet Inpage Multiplex', err),
|
||||||
|
)
|
||||||
|
pump(extensionMux, extensionStream, extensionMux, (err) => {
|
||||||
|
logStreamDisconnectWarning('Nifty Wallet Background Multiplex', err)
|
||||||
|
notifyInpageOfStreamFailure()
|
||||||
})
|
})
|
||||||
const pluginPort = extension.runtime.connect({ name: 'contentscript' })
|
|
||||||
const pluginStream = new PortStream(pluginPort)
|
|
||||||
|
|
||||||
// forward communication plugin->inpage
|
// forward communication plugin->inpage
|
||||||
pump(
|
pump(
|
||||||
pageStream,
|
pageStream,
|
||||||
pluginStream,
|
extensionStream,
|
||||||
pageStream,
|
pageStream,
|
||||||
(err) => logStreamDisconnectWarning('Nifty Wallet Contentscript Forwarding', err),
|
(err) => logStreamDisconnectWarning('Nifty Wallet Contentscript Forwarding', err),
|
||||||
)
|
)
|
||||||
|
|
||||||
// setup local multistream channels
|
|
||||||
const mux = new ObjectMultiplex()
|
|
||||||
mux.setMaxListeners(25)
|
|
||||||
|
|
||||||
pump(
|
// connect "phishing" channel to warning system
|
||||||
mux,
|
const phishingStream = extensionMux.createStream('phishing')
|
||||||
pageStream,
|
|
||||||
mux,
|
|
||||||
(err) => logStreamDisconnectWarning('Nifty Wallet Inpage', err),
|
|
||||||
)
|
|
||||||
pump(
|
|
||||||
mux,
|
|
||||||
pluginStream,
|
|
||||||
mux,
|
|
||||||
(err) => logStreamDisconnectWarning('Nifty Wallet Background', err),
|
|
||||||
)
|
|
||||||
|
|
||||||
// connect ping stream
|
|
||||||
const pongStream = new PongStream({ objectMode: true })
|
|
||||||
pump(
|
|
||||||
mux,
|
|
||||||
pongStream,
|
|
||||||
mux,
|
|
||||||
(err) => logStreamDisconnectWarning('Nifty Wallet PingPongStream', err),
|
|
||||||
)
|
|
||||||
|
|
||||||
// connect phishing warning stream
|
|
||||||
const phishingStream = mux.createStream('phishing')
|
|
||||||
phishingStream.once('data', redirectToPhishingWarning)
|
phishingStream.once('data', redirectToPhishingWarning)
|
||||||
|
|
||||||
// ignore unused channels (handled by background, inpage)
|
// TODO:LegacyProvider: Delete
|
||||||
mux.ignoreStream('provider')
|
// handle legacy provider
|
||||||
mux.ignoreStream('publicConfig')
|
const legacyPageStream = new LocalMessageDuplexStream({
|
||||||
|
name: LEGACY_CONTENT_SCRIPT,
|
||||||
|
target: LEGACY_INPAGE,
|
||||||
|
})
|
||||||
|
|
||||||
|
const legacyPageMux = new ObjectMultiplex()
|
||||||
|
legacyPageMux.setMaxListeners(25)
|
||||||
|
const legacyExtensionMux = new ObjectMultiplex()
|
||||||
|
legacyExtensionMux.setMaxListeners(25)
|
||||||
|
|
||||||
|
pump(legacyPageMux, legacyPageStream, legacyPageMux, (err) =>
|
||||||
|
logStreamDisconnectWarning('Nifty Wallet Legacy Inpage Multiplex', err),
|
||||||
|
)
|
||||||
|
pump(
|
||||||
|
legacyExtensionMux,
|
||||||
|
extensionStream,
|
||||||
|
getNotificationTransformStream(),
|
||||||
|
legacyExtensionMux,
|
||||||
|
(err) => {
|
||||||
|
logStreamDisconnectWarning('Nifty Wallet Background Legacy Multiplex', err)
|
||||||
|
notifyInpageOfStreamFailure()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
forwardNamedTrafficBetweenMuxes(
|
||||||
|
LEGACY_PROVIDER,
|
||||||
|
PROVIDER,
|
||||||
|
legacyPageMux,
|
||||||
|
legacyExtensionMux,
|
||||||
|
)
|
||||||
|
forwardTrafficBetweenMuxes(
|
||||||
|
LEGACY_PUBLIC_CONFIG,
|
||||||
|
legacyPageMux,
|
||||||
|
legacyExtensionMux,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function forwardTrafficBetweenMuxes (channelName, muxA, muxB) {
|
||||||
|
const channelA = muxA.createStream(channelName)
|
||||||
|
const channelB = muxB.createStream(channelName)
|
||||||
|
pump(channelA, channelB, channelA, (error) =>
|
||||||
|
console.debug(
|
||||||
|
`MetaMask: Muxed traffic for channel "${channelName}" failed.`,
|
||||||
|
error,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
// TODO:LegacyProvider: Delete
|
||||||
* Error handler for page to plugin stream disconnections
|
function forwardNamedTrafficBetweenMuxes (
|
||||||
*
|
channelAName,
|
||||||
* @param {string} remoteLabel Remote stream name
|
channelBName,
|
||||||
* @param {Error} err Stream connection error
|
muxA,
|
||||||
*/
|
muxB,
|
||||||
function logStreamDisconnectWarning (remoteLabel, err) {
|
) {
|
||||||
let warningMsg = `MetamaskContentscript - lost connection to ${remoteLabel}`
|
const channelA = muxA.createStream(channelAName)
|
||||||
if (err) warningMsg += '\n' + err.stack
|
const channelB = muxB.createStream(channelBName)
|
||||||
console.warn(warningMsg)
|
pump(channelA, channelB, channelA, (error) =>
|
||||||
|
console.debug(
|
||||||
|
`MetaMask: Muxed traffic between channels "${channelAName}" and "${channelBName}" failed.`,
|
||||||
|
error,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:LegacyProvider: Delete
|
||||||
|
function getNotificationTransformStream () {
|
||||||
|
return createThoughStream((chunk, _, cb) => {
|
||||||
|
if (chunk?.name === PROVIDER) {
|
||||||
|
if (chunk.data?.method === 'metamask_accountsChanged') {
|
||||||
|
chunk.data.method = 'wallet_accountsChanged'
|
||||||
|
chunk.data.result = chunk.data.params
|
||||||
|
delete chunk.data.params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cb(null, chunk)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if Web3 should be injected
|
* Error handler for page to extension stream disconnections
|
||||||
*
|
*
|
||||||
* @returns {boolean} {@code true} if Web3 should be injected
|
* @param {string} remoteLabel - Remote stream name
|
||||||
|
* @param {Error} error - Stream connection error
|
||||||
*/
|
*/
|
||||||
function shouldInjectWeb3 () {
|
function logStreamDisconnectWarning (remoteLabel, error) {
|
||||||
return doctypeCheck() && suffixCheck() &&
|
console.debug(
|
||||||
documentElementCheck() && !blacklistedDomainCheck()
|
`MetaMask: Content script lost connection to "${remoteLabel}".`,
|
||||||
|
error,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function must ONLY be called in pump destruction/close callbacks.
|
||||||
|
* Notifies the inpage context that streams have failed, via window.postMessage.
|
||||||
|
* Relies on obj-multiplex and post-message-stream implementation details.
|
||||||
|
*/
|
||||||
|
function notifyInpageOfStreamFailure () {
|
||||||
|
window.postMessage(
|
||||||
|
{
|
||||||
|
target: INPAGE, // the post-message-stream "target"
|
||||||
|
data: {
|
||||||
|
// this object gets passed to obj-multiplex
|
||||||
|
name: PROVIDER, // the obj-multiplex channel name
|
||||||
|
data: {
|
||||||
|
jsonrpc: '2.0',
|
||||||
|
method: 'METAMASK_STREAM_FAILURE',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
window.location.origin,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the provider should be injected
|
||||||
|
*
|
||||||
|
* @returns {boolean} {@code true} Whether the provider should be injected
|
||||||
|
*/
|
||||||
|
function shouldInjectProvider () {
|
||||||
|
return (
|
||||||
|
doctypeCheck() &&
|
||||||
|
suffixCheck() &&
|
||||||
|
documentElementCheck() &&
|
||||||
|
!blockedDomainCheck()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -126,28 +229,24 @@ function shouldInjectWeb3 () {
|
||||||
* @returns {boolean} {@code true} if the doctype is html or if none exists
|
* @returns {boolean} {@code true} if the doctype is html or if none exists
|
||||||
*/
|
*/
|
||||||
function doctypeCheck () {
|
function doctypeCheck () {
|
||||||
const doctype = window.document.doctype
|
const { doctype } = window.document
|
||||||
if (doctype) {
|
if (doctype) {
|
||||||
return doctype.name === 'html'
|
return doctype.name === 'html'
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether or not the extension (suffix) of the current document is prohibited
|
* Returns whether or not the extension (suffix) of the current document is prohibited
|
||||||
*
|
*
|
||||||
* This checks {@code window.location.pathname} against a set of file extensions
|
* This checks {@code window.location.pathname} against a set of file extensions
|
||||||
* that should not have web3 injected into them. This check is indifferent of query parameters
|
* that we should not inject the provider into. This check is indifferent of
|
||||||
* in the location.
|
* query parameters in the location.
|
||||||
*
|
*
|
||||||
* @returns {boolean} whether or not the extension of the current document is prohibited
|
* @returns {boolean} whether or not the extension of the current document is prohibited
|
||||||
*/
|
*/
|
||||||
function suffixCheck () {
|
function suffixCheck () {
|
||||||
const prohibitedTypes = [
|
const prohibitedTypes = [/\.xml$/u, /\.pdf$/u]
|
||||||
/\.xml$/,
|
|
||||||
/\.pdf$/,
|
|
||||||
]
|
|
||||||
const currentUrl = window.location.pathname
|
const currentUrl = window.location.pathname
|
||||||
for (let i = 0; i < prohibitedTypes.length; i++) {
|
for (let i = 0; i < prohibitedTypes.length; i++) {
|
||||||
if (prohibitedTypes[i].test(currentUrl)) {
|
if (prohibitedTypes[i].test(currentUrl)) {
|
||||||
|
@ -171,12 +270,12 @@ function documentElementCheck () {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the current domain is blacklisted
|
* Checks if the current domain is blocked
|
||||||
*
|
*
|
||||||
* @returns {boolean} {@code true} if the current domain is blacklisted
|
* @returns {boolean} {@code true} if the current domain is blocked
|
||||||
*/
|
*/
|
||||||
function blacklistedDomainCheck () {
|
function blockedDomainCheck () {
|
||||||
const blacklistedDomains = [
|
const blockedDomains = [
|
||||||
'uscourts.gov',
|
'uscourts.gov',
|
||||||
'dropbox.com',
|
'dropbox.com',
|
||||||
'webbyawards.com',
|
'webbyawards.com',
|
||||||
|
@ -186,12 +285,16 @@ function blacklistedDomainCheck () {
|
||||||
'harbourair.com',
|
'harbourair.com',
|
||||||
'ani.gamer.com.tw',
|
'ani.gamer.com.tw',
|
||||||
'blueskybooking.com',
|
'blueskybooking.com',
|
||||||
|
'sharefile.com',
|
||||||
]
|
]
|
||||||
const currentUrl = window.location.href
|
const currentUrl = window.location.href
|
||||||
let currentRegex
|
let currentRegex
|
||||||
for (let i = 0; i < blacklistedDomains.length; i++) {
|
for (let i = 0; i < blockedDomains.length; i++) {
|
||||||
const blacklistedDomain = blacklistedDomains[i].replace('.', '\\.')
|
const blockedDomain = blockedDomains[i].replace('.', '\\.')
|
||||||
currentRegex = new RegExp(`(?:https?:\\/\\/)(?:(?!${blacklistedDomain}).)*$`)
|
currentRegex = new RegExp(
|
||||||
|
`(?:https?:\\/\\/)(?:(?!${blockedDomain}).)*$`,
|
||||||
|
'u',
|
||||||
|
)
|
||||||
if (!currentRegex.test(currentUrl)) {
|
if (!currentRegex.test(currentUrl)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const ObservableStore = require('obs-store')
|
import { ObservableStore } from '@metamask/obs-store'
|
||||||
const extend = require('xtend')
|
const extend = require('xtend')
|
||||||
|
|
||||||
class AddressBookController {
|
class AddressBookController {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import ObservableStore from 'obs-store'
|
import { ObservableStore } from '@metamask/obs-store'
|
||||||
import PendingBalanceCalculator from '../lib/pending-balance-calculator'
|
import PendingBalanceCalculator from '../lib/pending-balance-calculator'
|
||||||
import { BN } from 'ethereumjs-util'
|
import { BN } from 'ethereumjs-util'
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const ObservableStore = require('obs-store')
|
import { ObservableStore } from '@metamask/obs-store'
|
||||||
const extend = require('xtend')
|
const extend = require('xtend')
|
||||||
const PhishingDetector = require('eth-phishing-detect/src/detector')
|
const PhishingDetector = require('eth-phishing-detect/src/detector')
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const ObservableStore = require('obs-store')
|
import { ObservableStore } from '@metamask/obs-store'
|
||||||
const extend = require('xtend')
|
const extend = require('xtend')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const ObservableStore = require('obs-store')
|
import { ObservableStore } from '@metamask/obs-store'
|
||||||
const extend = require('xtend')
|
const extend = require('xtend')
|
||||||
const BalanceController = require('./balance')
|
const BalanceController = require('./balance')
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const ObservableStore = require('obs-store')
|
import { ObservableStore } from '@metamask/obs-store'
|
||||||
const extend = require('xtend')
|
const extend = require('xtend')
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import Web3 from 'web3'
|
import Web3 from 'web3'
|
||||||
import contractsETH from 'eth-contract-metadata'
|
import contractsETH from '@metamask/contract-metadata'
|
||||||
import contractsPOA from 'poa-contract-metadata'
|
import contractsPOA from 'poa-contract-metadata'
|
||||||
import contractsRSK from '@rsksmart/rsk-contract-metadata'
|
import contractsRSK from '@rsksmart/rsk-contract-metadata'
|
||||||
import contractsRSKTest from '@rsksmart/rsk-testnet-contract-metadata'
|
import contractsRSKTest from '@rsksmart/rsk-testnet-contract-metadata'
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
const ObservableStore = require('obs-store')
|
|
||||||
const extend = require('xtend')
|
|
||||||
const log = require('loglevel')
|
|
||||||
|
|
||||||
// every ten minutes
|
|
||||||
const POLLING_INTERVAL = 10 * 60 * 1000
|
|
||||||
|
|
||||||
class InfuraController {
|
|
||||||
|
|
||||||
constructor (opts = {}) {
|
|
||||||
const initState = extend({
|
|
||||||
infuraNetworkStatus: {},
|
|
||||||
}, opts.initState)
|
|
||||||
this.store = new ObservableStore(initState)
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// PUBLIC METHODS
|
|
||||||
//
|
|
||||||
|
|
||||||
// Responsible for retrieving the status of Infura's nodes. Can return either
|
|
||||||
// ok, degraded, or down.
|
|
||||||
async checkInfuraNetworkStatus () {
|
|
||||||
const response = await fetch('https://api.infura.io/v1/status/metamask')
|
|
||||||
const parsedResponse = await response.json()
|
|
||||||
this.store.updateState({
|
|
||||||
infuraNetworkStatus: parsedResponse,
|
|
||||||
})
|
|
||||||
return parsedResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
scheduleInfuraNetworkCheck () {
|
|
||||||
if (this.conversionInterval) {
|
|
||||||
clearInterval(this.conversionInterval)
|
|
||||||
}
|
|
||||||
this.conversionInterval = setInterval(() => {
|
|
||||||
this.checkInfuraNetworkStatus().catch(log.warn)
|
|
||||||
}, POLLING_INTERVAL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = InfuraController
|
|
|
@ -1,5 +1,4 @@
|
||||||
import mergeMiddleware from 'json-rpc-engine/src/mergeMiddleware'
|
import { createScaffoldMiddleware, mergeMiddleware} from 'json-rpc-engine'
|
||||||
import createScaffoldMiddleware from 'json-rpc-engine/src/createScaffoldMiddleware'
|
|
||||||
import createBlockReRefMiddleware from 'eth-json-rpc-middleware/block-ref'
|
import createBlockReRefMiddleware from 'eth-json-rpc-middleware/block-ref'
|
||||||
import createRetryOnEmptyMiddleware from 'eth-json-rpc-middleware/retryOnEmpty'
|
import createRetryOnEmptyMiddleware from 'eth-json-rpc-middleware/retryOnEmpty'
|
||||||
import createBlockCacheMiddleware from 'eth-json-rpc-middleware/block-cache'
|
import createBlockCacheMiddleware from 'eth-json-rpc-middleware/block-cache'
|
||||||
|
@ -11,8 +10,13 @@ import BlockTracker from 'eth-block-tracker'
|
||||||
|
|
||||||
export default createInfuraClient
|
export default createInfuraClient
|
||||||
|
|
||||||
function createInfuraClient ({ network }) {
|
function createInfuraClient ({ network, projectId }) {
|
||||||
const infuraMiddleware = createInfuraMiddleware({ network, maxAttempts: 5, source: 'metamask' })
|
const infuraMiddleware = createInfuraMiddleware({
|
||||||
|
network,
|
||||||
|
projectId,
|
||||||
|
maxAttempts: 5,
|
||||||
|
source: 'metamask',
|
||||||
|
})
|
||||||
const infuraProvider = providerFromMiddleware(infuraMiddleware)
|
const infuraProvider = providerFromMiddleware(infuraMiddleware)
|
||||||
const blockTracker = new BlockTracker({ provider: infuraProvider })
|
const blockTracker = new BlockTracker({ provider: infuraProvider })
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import mergeMiddleware from 'json-rpc-engine/src/mergeMiddleware'
|
import { mergeMiddleware } from 'json-rpc-engine'
|
||||||
import createFetchMiddleware from 'eth-json-rpc-middleware/fetch'
|
import createFetchMiddleware from 'eth-json-rpc-middleware/fetch'
|
||||||
import createBlockRefRewriteMiddleware from 'eth-json-rpc-middleware/block-ref-rewrite'
|
import createBlockRefRewriteMiddleware from 'eth-json-rpc-middleware/block-ref-rewrite'
|
||||||
import createBlockCacheMiddleware from 'eth-json-rpc-middleware/block-cache'
|
import createBlockCacheMiddleware from 'eth-json-rpc-middleware/block-cache'
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import mergeMiddleware from 'json-rpc-engine/src/mergeMiddleware'
|
import { createAsyncMiddleware, mergeMiddleware } from 'json-rpc-engine'
|
||||||
import createFetchMiddleware from 'eth-json-rpc-middleware/fetch'
|
import createFetchMiddleware from 'eth-json-rpc-middleware/fetch'
|
||||||
import createBlockRefRewriteMiddleware from 'eth-json-rpc-middleware/block-ref-rewrite'
|
import createBlockRefRewriteMiddleware from 'eth-json-rpc-middleware/block-ref-rewrite'
|
||||||
import createBlockTrackerInspectorMiddleware from 'eth-json-rpc-middleware/block-tracker-inspector'
|
import createBlockTrackerInspectorMiddleware from 'eth-json-rpc-middleware/block-tracker-inspector'
|
||||||
import createAsyncMiddleware from 'json-rpc-engine/src/createAsyncMiddleware'
|
|
||||||
import providerFromMiddleware from 'eth-json-rpc-middleware/providerFromMiddleware'
|
import providerFromMiddleware from 'eth-json-rpc-middleware/providerFromMiddleware'
|
||||||
import BlockTracker from 'eth-block-tracker'
|
import BlockTracker from 'eth-block-tracker'
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import mergeMiddleware from 'json-rpc-engine/src/mergeMiddleware'
|
import { createScaffoldMiddleware, mergeMiddleware } from 'json-rpc-engine'
|
||||||
import createScaffoldMiddleware from 'json-rpc-engine/src/createScaffoldMiddleware'
|
|
||||||
import createWalletSubprovider from 'eth-json-rpc-middleware/wallet'
|
import createWalletSubprovider from 'eth-json-rpc-middleware/wallet'
|
||||||
import { createPendingNonceMiddleware } from './middleware/pending'
|
import { createPendingNonceMiddleware } from './middleware/pending'
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware')
|
const { createScaffoldMiddleware, mergeMiddleware } = require('json-rpc-engine')
|
||||||
const createScaffoldMiddleware = require('json-rpc-engine/src/createScaffoldMiddleware')
|
|
||||||
const createBlockReRefMiddleware = require('eth-json-rpc-middleware/block-ref')
|
const createBlockReRefMiddleware = require('eth-json-rpc-middleware/block-ref')
|
||||||
const createRetryOnEmptyMiddleware = require('eth-json-rpc-middleware/retryOnEmpty')
|
const createRetryOnEmptyMiddleware = require('eth-json-rpc-middleware/retryOnEmpty')
|
||||||
const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache')
|
const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache')
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
const { formatTxMetaForRpcResult } = require('../util')
|
import { createAsyncMiddleware } from 'json-rpc-engine'
|
||||||
import createAsyncMiddleware from 'json-rpc-engine/src/createAsyncMiddleware'
|
import { formatTxMetaForRpcResult } from '../util'
|
||||||
|
|
||||||
export function createPendingNonceMiddleware ({ getPendingNonce }) {
|
export function createPendingNonceMiddleware ({ getPendingNonce }) {
|
||||||
return createAsyncMiddleware(async (req, res, next) => {
|
return createAsyncMiddleware(async (req, res, next) => {
|
||||||
const { method, params } = req
|
const { method, params } = req
|
||||||
if (method !== 'eth_getTransactionCount') {
|
if (method !== 'eth_getTransactionCount') {
|
||||||
return next()
|
next()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
const [param, blockRef] = params
|
const [param, blockRef] = params
|
||||||
if (blockRef !== 'pending') {
|
if (blockRef !== 'pending') {
|
||||||
return next()
|
next()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
res.result = await getPendingNonce(param)
|
res.result = await getPendingNonce(param)
|
||||||
})
|
})
|
||||||
|
@ -19,12 +21,14 @@ export function createPendingTxMiddleware ({ getPendingTransactionByHash }) {
|
||||||
return createAsyncMiddleware(async (req, res, next) => {
|
return createAsyncMiddleware(async (req, res, next) => {
|
||||||
const { method, params } = req
|
const { method, params } = req
|
||||||
if (method !== 'eth_getTransactionByHash') {
|
if (method !== 'eth_getTransactionByHash') {
|
||||||
return next()
|
next()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
const [hash] = params
|
const [hash] = params
|
||||||
const txMeta = getPendingTransactionByHash(hash)
|
const txMeta = getPendingTransactionByHash(hash)
|
||||||
if (!txMeta) {
|
if (!txMeta) {
|
||||||
return next()
|
next()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
res.result = formatTxMetaForRpcResult(txMeta)
|
res.result = formatTxMetaForRpcResult(txMeta)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import EventEmitter from 'events'
|
import EventEmitter from 'events'
|
||||||
import ObservableStore from 'obs-store'
|
import { ComposedStore, ObservableStore } from '@metamask/obs-store'
|
||||||
import ComposedStore from 'obs-store/lib/composed'
|
|
||||||
import EthQuery from 'eth-query'
|
import EthQuery from 'eth-query'
|
||||||
import JsonRpcEngine from 'json-rpc-engine'
|
import { JsonRpcEngine } from 'json-rpc-engine'
|
||||||
import providerFromEngine from 'eth-json-rpc-middleware/providerFromEngine'
|
import providerFromEngine from 'eth-json-rpc-middleware/providerFromEngine'
|
||||||
import log from 'loglevel'
|
import log from 'loglevel'
|
||||||
import createMetamaskMiddleware from './createMetamaskMiddleware'
|
import createMetamaskMiddleware from './createMetamaskMiddleware'
|
||||||
|
@ -87,6 +86,21 @@ module.exports = class NetworkController extends EventEmitter {
|
||||||
this._blockTrackerProxy = null
|
this._blockTrackerProxy = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the Infura project ID
|
||||||
|
*
|
||||||
|
* @param {string} projectId - The Infura project ID
|
||||||
|
* @throws {Error} if the project ID is not a valid string
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
setInfuraProjectId (projectId) {
|
||||||
|
if (!projectId || typeof projectId !== 'string') {
|
||||||
|
throw new Error('Invalid Infura project ID')
|
||||||
|
}
|
||||||
|
|
||||||
|
this._infuraProjectId = projectId
|
||||||
|
}
|
||||||
|
|
||||||
initializeProvider (providerParams) {
|
initializeProvider (providerParams) {
|
||||||
this._baseProviderParams = providerParams
|
this._baseProviderParams = providerParams
|
||||||
const { type, rpcTarget, chainId, ticker, nickname } = this.providerStore.getState()
|
const { type, rpcTarget, chainId, ticker, nickname } = this.providerStore.getState()
|
||||||
|
@ -232,7 +246,7 @@ module.exports = class NetworkController extends EventEmitter {
|
||||||
if (isPocket && this.dProviderStore.getState().dProvider) {
|
if (isPocket && this.dProviderStore.getState().dProvider) {
|
||||||
this._configurePocketProvider(opts)
|
this._configurePocketProvider(opts)
|
||||||
} else if (isInfura) {
|
} else if (isInfura) {
|
||||||
this._configureInfuraProvider(opts)
|
this._configureInfuraProvider(type, this._infuraProjectId)
|
||||||
// other type-based rpc endpoints
|
// other type-based rpc endpoints
|
||||||
} else if (type === MAINNET) {
|
} else if (type === MAINNET) {
|
||||||
this._configureStandardProvider({ rpcUrl: this._ethMainnetRpcEndpoint, chainId, ticker, nickname })
|
this._configureStandardProvider({ rpcUrl: this._ethMainnetRpcEndpoint, chainId, ticker, nickname })
|
||||||
|
@ -275,17 +289,13 @@ module.exports = class NetworkController extends EventEmitter {
|
||||||
this._ethMainnetRpcEndpoint = endpoint
|
this._ethMainnetRpcEndpoint = endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
_configureInfuraProvider ({ type }) {
|
_configureInfuraProvider (type, projectId) {
|
||||||
log.info('NetworkController - configureInfuraProvider', type)
|
log.info('NetworkController - configureInfuraProvider', type)
|
||||||
const networkClient = createInfuraClient({
|
const networkClient = createInfuraClient({
|
||||||
network: type,
|
network: type,
|
||||||
|
projectId,
|
||||||
})
|
})
|
||||||
this._setNetworkClient(networkClient)
|
this._setNetworkClient(networkClient)
|
||||||
// setup networkConfig
|
|
||||||
const settings = {
|
|
||||||
ticker: 'ETH',
|
|
||||||
}
|
|
||||||
this.networkConfig.putState(settings)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_configurePocketProvider ({ type }) {
|
_configurePocketProvider ({ type }) {
|
||||||
|
|
|
@ -171,20 +171,22 @@ const getNetworkDisplayName = key => networks[key].displayName
|
||||||
|
|
||||||
function formatTxMetaForRpcResult (txMeta) {
|
function formatTxMetaForRpcResult (txMeta) {
|
||||||
return {
|
return {
|
||||||
'blockHash': txMeta.txReceipt ? txMeta.txReceipt.blockHash : null,
|
blockHash: txMeta.txReceipt ? txMeta.txReceipt.blockHash : null,
|
||||||
'blockNumber': txMeta.txReceipt ? txMeta.txReceipt.blockNumber : null,
|
blockNumber: txMeta.txReceipt ? txMeta.txReceipt.blockNumber : null,
|
||||||
'from': txMeta.txParams.from,
|
from: txMeta.txParams.from,
|
||||||
'gas': txMeta.txParams.gas,
|
gas: txMeta.txParams.gas,
|
||||||
'gasPrice': txMeta.txParams.gasPrice,
|
gasPrice: txMeta.txParams.gasPrice,
|
||||||
'hash': txMeta.hash,
|
hash: txMeta.hash,
|
||||||
'input': txMeta.txParams.data || '0x',
|
input: txMeta.txParams.data || '0x',
|
||||||
'nonce': txMeta.txParams.nonce,
|
nonce: txMeta.txParams.nonce,
|
||||||
'to': txMeta.txParams.to,
|
to: txMeta.txParams.to,
|
||||||
'transactionIndex': txMeta.txReceipt ? txMeta.txReceipt.transactionIndex : null,
|
transactionIndex: txMeta.txReceipt
|
||||||
'value': txMeta.txParams.value || '0x0',
|
? txMeta.txReceipt.transactionIndex
|
||||||
'v': txMeta.v,
|
: null,
|
||||||
'r': txMeta.r,
|
value: txMeta.txParams.value || '0x0',
|
||||||
's': txMeta.s,
|
v: txMeta.v,
|
||||||
|
r: txMeta.r,
|
||||||
|
s: txMeta.s,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
export const APPROVAL_TYPE = 'wallet_requestPermissions'
|
||||||
|
|
||||||
export const WALLET_PREFIX = 'wallet_'
|
export const WALLET_PREFIX = 'wallet_'
|
||||||
|
|
||||||
|
@ -7,16 +8,27 @@ export const LOG_STORE_KEY = 'permissionsLog'
|
||||||
|
|
||||||
export const METADATA_STORE_KEY = 'domainMetadata'
|
export const METADATA_STORE_KEY = 'domainMetadata'
|
||||||
|
|
||||||
|
export const METADATA_CACHE_MAX_SIZE = 100
|
||||||
|
|
||||||
export const CAVEAT_NAMES = {
|
export const CAVEAT_NAMES = {
|
||||||
exposedAccounts: 'exposedAccounts',
|
exposedAccounts: 'exposedAccounts',
|
||||||
|
primaryAccountOnly: 'primaryAccountOnly',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CAVEAT_TYPES = {
|
||||||
|
limitResponseLength: 'limitResponseLength',
|
||||||
|
filterResponse: 'filterResponse',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NOTIFICATION_NAMES = {
|
export const NOTIFICATION_NAMES = {
|
||||||
accountsChanged: 'wallet_accountsChanged',
|
accountsChanged: 'metamask_accountsChanged',
|
||||||
|
unlockStateChanged: 'metamask_unlockStateChanged',
|
||||||
|
chainChanged: 'metamask_chainChanged',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LOG_IGNORE_METHODS = [
|
export const LOG_IGNORE_METHODS = [
|
||||||
'wallet_sendDomainMetadata',
|
'wallet_registerOnboarding',
|
||||||
|
'wallet_watchAsset',
|
||||||
]
|
]
|
||||||
|
|
||||||
export const LOG_METHOD_TYPES = {
|
export const LOG_METHOD_TYPES = {
|
||||||
|
@ -27,14 +39,11 @@ export const LOG_METHOD_TYPES = {
|
||||||
export const LOG_LIMIT = 100
|
export const LOG_LIMIT = 100
|
||||||
|
|
||||||
export const SAFE_METHODS = [
|
export const SAFE_METHODS = [
|
||||||
'web3_sha3',
|
|
||||||
'net_listening',
|
|
||||||
'net_peerCount',
|
|
||||||
'net_version',
|
|
||||||
'eth_blockNumber',
|
'eth_blockNumber',
|
||||||
'eth_call',
|
'eth_call',
|
||||||
'eth_chainId',
|
'eth_chainId',
|
||||||
'eth_coinbase',
|
'eth_coinbase',
|
||||||
|
'eth_decrypt',
|
||||||
'eth_estimateGas',
|
'eth_estimateGas',
|
||||||
'eth_gasPrice',
|
'eth_gasPrice',
|
||||||
'eth_getBalance',
|
'eth_getBalance',
|
||||||
|
@ -43,9 +52,11 @@ export const SAFE_METHODS = [
|
||||||
'eth_getBlockTransactionCountByHash',
|
'eth_getBlockTransactionCountByHash',
|
||||||
'eth_getBlockTransactionCountByNumber',
|
'eth_getBlockTransactionCountByNumber',
|
||||||
'eth_getCode',
|
'eth_getCode',
|
||||||
|
'eth_getEncryptionPublicKey',
|
||||||
'eth_getFilterChanges',
|
'eth_getFilterChanges',
|
||||||
'eth_getFilterLogs',
|
'eth_getFilterLogs',
|
||||||
'eth_getLogs',
|
'eth_getLogs',
|
||||||
|
'eth_getProof',
|
||||||
'eth_getStorageAt',
|
'eth_getStorageAt',
|
||||||
'eth_getTransactionByBlockHashAndIndex',
|
'eth_getTransactionByBlockHashAndIndex',
|
||||||
'eth_getTransactionByBlockNumberAndIndex',
|
'eth_getTransactionByBlockNumberAndIndex',
|
||||||
|
@ -66,8 +77,6 @@ export const SAFE_METHODS = [
|
||||||
'eth_sendRawTransaction',
|
'eth_sendRawTransaction',
|
||||||
'eth_sendTransaction',
|
'eth_sendTransaction',
|
||||||
'eth_sign',
|
'eth_sign',
|
||||||
'personal_sign',
|
|
||||||
'personal_ecRecover',
|
|
||||||
'eth_signTypedData',
|
'eth_signTypedData',
|
||||||
'eth_signTypedData_v1',
|
'eth_signTypedData_v1',
|
||||||
'eth_signTypedData_v3',
|
'eth_signTypedData_v3',
|
||||||
|
@ -76,9 +85,14 @@ export const SAFE_METHODS = [
|
||||||
'eth_submitWork',
|
'eth_submitWork',
|
||||||
'eth_syncing',
|
'eth_syncing',
|
||||||
'eth_uninstallFilter',
|
'eth_uninstallFilter',
|
||||||
|
'metamask_getProviderState',
|
||||||
'metamask_watchAsset',
|
'metamask_watchAsset',
|
||||||
|
'net_listening',
|
||||||
|
'net_peerCount',
|
||||||
|
'net_version',
|
||||||
|
'personal_ecRecover',
|
||||||
|
'personal_sign',
|
||||||
'wallet_watchAsset',
|
'wallet_watchAsset',
|
||||||
'eth_getEncryptionPublicKey',
|
'web3_clientVersion',
|
||||||
'eth_decrypt',
|
'web3_sha3',
|
||||||
'eth_accounts',
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import JsonRpcEngine from 'json-rpc-engine'
|
import { JsonRpcEngine } from 'json-rpc-engine'
|
||||||
import asMiddleware from 'json-rpc-engine/src/asMiddleware'
|
import { ObservableStore } from '@metamask/obs-store'
|
||||||
import ObservableStore from 'obs-store'
|
|
||||||
import log from 'loglevel'
|
import log from 'loglevel'
|
||||||
import { CapabilitiesController as RpcCap } from 'rpc-cap'
|
import { CapabilitiesController as RpcCap } from 'rpc-cap'
|
||||||
import { ethErrors } from 'eth-json-rpc-errors'
|
import { ethErrors } from 'eth-json-rpc-errors'
|
||||||
|
@ -90,7 +89,7 @@ export class PermissionsController {
|
||||||
this.permissions, { origin },
|
this.permissions, { origin },
|
||||||
))
|
))
|
||||||
|
|
||||||
return asMiddleware(engine)
|
return engine.asMiddleware(engine)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import createAsyncMiddleware from 'json-rpc-engine/src/createAsyncMiddleware'
|
import { createAsyncMiddleware } from 'json-rpc-engine'
|
||||||
import { ethErrors } from 'eth-json-rpc-errors'
|
import { ethErrors } from 'eth-json-rpc-errors'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const ObservableStore = require('obs-store')
|
import { ObservableStore } from '@metamask/obs-store'
|
||||||
const normalizeAddress = require('eth-sig-util').normalize
|
const normalizeAddress = require('eth-sig-util').normalize
|
||||||
const { isValidAddress } = require('ethereumjs-util')
|
const { isValidAddress } = require('ethereumjs-util')
|
||||||
const extend = require('xtend')
|
const extend = require('xtend')
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const ObservableStore = require('obs-store')
|
import { ObservableStore } from '@metamask/obs-store'
|
||||||
const extend = require('xtend')
|
const extend = require('xtend')
|
||||||
const EthQuery = require('eth-query')
|
const EthQuery = require('eth-query')
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const ObservableStore = require('obs-store')
|
import { ObservableStore } from '@metamask/obs-store'
|
||||||
const extend = require('xtend')
|
const extend = require('xtend')
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const ObservableStore = require('obs-store')
|
import { ObservableStore } from '@metamask/obs-store'
|
||||||
|
|
||||||
import log from 'loglevel'
|
import log from 'loglevel'
|
||||||
import { normalize as normalizeAddress } from 'eth-sig-util'
|
import { normalize as normalizeAddress } from 'eth-sig-util'
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import EventEmitter from 'safe-event-emitter'
|
import EventEmitter from 'safe-event-emitter'
|
||||||
import ObservableStore from 'obs-store'
|
import { ObservableStore } from '@metamask/obs-store'
|
||||||
import ethUtil from 'ethereumjs-util'
|
import ethUtil from 'ethereumjs-util'
|
||||||
import Transaction from 'ethereumjs-tx'
|
import Transaction from 'ethereumjs-tx'
|
||||||
import EthQuery from 'ethjs-query'
|
import EthQuery from 'ethjs-query'
|
||||||
|
import { ethErrors } from 'eth-json-rpc-errors'
|
||||||
import abi from 'human-standard-token-abi'
|
import abi from 'human-standard-token-abi'
|
||||||
import abiDecoder from 'abi-decoder'
|
import abiDecoder from 'abi-decoder'
|
||||||
|
|
||||||
|
@ -14,14 +14,15 @@ import TxGasUtil from './tx-gas-utils'
|
||||||
const PendingTransactionTracker = require('./pending-tx-tracker')
|
const PendingTransactionTracker = require('./pending-tx-tracker')
|
||||||
import NonceTracker from 'nonce-tracker'
|
import NonceTracker from 'nonce-tracker'
|
||||||
import * as txUtils from './lib/util'
|
import * as txUtils from './lib/util'
|
||||||
|
import {
|
||||||
|
TRANSACTION_STATUSES,
|
||||||
|
TRANSACTION_TYPES,
|
||||||
|
} from '../../../../shared/constants/transaction'
|
||||||
import cleanErrorStack from '../../lib/cleanErrorStack'
|
import cleanErrorStack from '../../lib/cleanErrorStack'
|
||||||
import log from 'loglevel'
|
import log from 'loglevel'
|
||||||
const recipientBlacklistChecker = require('./lib/recipient-blacklist-checker')
|
const recipientBlacklistChecker = require('./lib/recipient-blacklist-checker')
|
||||||
const {
|
const {
|
||||||
TRANSACTION_TYPE_CANCEL,
|
|
||||||
TRANSACTION_TYPE_RETRY,
|
TRANSACTION_TYPE_RETRY,
|
||||||
TRANSACTION_TYPE_STANDARD,
|
|
||||||
TRANSACTION_STATUS_APPROVED,
|
|
||||||
} = require('./enums')
|
} = require('./enums')
|
||||||
|
|
||||||
const { hexToBn, bnToHex, BnMultiplyByFraction } = require('../../lib/util')
|
const { hexToBn, bnToHex, BnMultiplyByFraction } = require('../../lib/util')
|
||||||
|
@ -40,17 +41,16 @@ const { hexToBn, bnToHex, BnMultiplyByFraction } = require('../../lib/util')
|
||||||
<br>- nonceTracker
|
<br>- nonceTracker
|
||||||
calculating nonces
|
calculating nonces
|
||||||
|
|
||||||
|
|
||||||
@class
|
@class
|
||||||
@param {object} - opts
|
@param {Object} opts
|
||||||
@param {object} opts.initState - initial transaction list default is an empty array
|
@param {Object} opts.initState - initial transaction list default is an empty array
|
||||||
@param {Object} opts.networkStore - an observable store for network number
|
@param {Object} opts.networkStore - an observable store for network number
|
||||||
@param {Object} opts.blockTracker - An instance of eth-blocktracker
|
@param {Object} opts.blockTracker - An instance of eth-blocktracker
|
||||||
@param {Object} opts.provider - A network provider.
|
@param {Object} opts.provider - A network provider.
|
||||||
@param {Function} opts.signTransaction - function the signs an ethereumjs-tx
|
@param {Function} opts.signTransaction - function the signs an ethereumjs-tx
|
||||||
@param {Function} [opts.getGasPrice] - optional gas price calculator
|
@param {Object} opts.getPermittedAccounts - get accounts that an origin has permissions for
|
||||||
@param {Function} opts.signTransaction - ethTx signer that returns a rawTx
|
@param {Function} opts.signTransaction - ethTx signer that returns a rawTx
|
||||||
@param {Number} [opts.txHistoryLimit] - number *optional* for limiting how many transactions are in state
|
@param {number} [opts.txHistoryLimit] - number *optional* for limiting how many transactions are in state
|
||||||
@param {Object} opts.preferencesStore
|
@param {Object} opts.preferencesStore
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -104,7 +104,12 @@ class TransactionController extends EventEmitter {
|
||||||
this._updatePendingTxsAfterFirstBlock()
|
this._updatePendingTxsAfterFirstBlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {number} the chainId*/
|
/**
|
||||||
|
* Gets the current chainId in the network store as a number, returning 0 if
|
||||||
|
* the chainId parses to NaN.
|
||||||
|
*
|
||||||
|
* @returns {number} The numerical chainId.
|
||||||
|
*/
|
||||||
getChainId () {
|
getChainId () {
|
||||||
const networkState = this.networkStore.getState()
|
const networkState = this.networkStore.getState()
|
||||||
const getChainId = parseInt(networkState)
|
const getChainId = parseInt(networkState)
|
||||||
|
@ -133,42 +138,66 @@ class TransactionController extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
add a new unapproved transaction to the pipeline
|
* Add a new unapproved transaction to the pipeline
|
||||||
|
*
|
||||||
@returns {Promise<string>} the hash of the transaction after being submitted to the network
|
* @returns {Promise<string>} the hash of the transaction after being submitted to the network
|
||||||
@param txParams {object} - txParams for the transaction
|
* @param {Object} txParams - txParams for the transaction
|
||||||
@param opts {object} - with the key origin to put the origin on the txMeta
|
* @param {Object} opts - with the key origin to put the origin on the txMeta
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async newUnapprovedTransaction (txParams, opts = {}) {
|
async newUnapprovedTransaction (txParams, opts = {}) {
|
||||||
log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`)
|
log.debug(
|
||||||
const initialTxMeta = await this.addUnapprovedTransaction(txParams)
|
`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`,
|
||||||
initialTxMeta.origin = opts.origin
|
)
|
||||||
this.txStateManager.updateTx(initialTxMeta, '#newUnapprovedTransaction - adding the origin')
|
|
||||||
|
const initialTxMeta = await this.addUnapprovedTransaction(
|
||||||
|
txParams,
|
||||||
|
opts.origin,
|
||||||
|
)
|
||||||
|
|
||||||
// listen for tx completion (success, fail)
|
// listen for tx completion (success, fail)
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => {
|
this.txStateManager.once(
|
||||||
|
`${initialTxMeta.id}:finished`,
|
||||||
|
(finishedTxMeta) => {
|
||||||
switch (finishedTxMeta.status) {
|
switch (finishedTxMeta.status) {
|
||||||
case 'submitted':
|
case TRANSACTION_STATUSES.SUBMITTED:
|
||||||
return resolve(finishedTxMeta.hash)
|
return resolve(finishedTxMeta.hash)
|
||||||
case 'rejected':
|
case TRANSACTION_STATUSES.REJECTED:
|
||||||
return reject(cleanErrorStack(new Error('Nifty Wallet Tx Signature: User denied transaction signature.')))
|
return reject(
|
||||||
case 'failed':
|
cleanErrorStack(
|
||||||
return reject(cleanErrorStack(new Error(finishedTxMeta.err.message)))
|
ethErrors.provider.userRejectedRequest(
|
||||||
|
'MetaMask Tx Signature: User denied transaction signature.',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
case TRANSACTION_STATUSES.FAILED:
|
||||||
|
return reject(
|
||||||
|
cleanErrorStack(
|
||||||
|
ethErrors.rpc.internal(finishedTxMeta.err.message),
|
||||||
|
),
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return reject(cleanErrorStack(new Error(`Nifty Wallet Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`)))
|
return reject(
|
||||||
|
cleanErrorStack(
|
||||||
|
ethErrors.rpc.internal(
|
||||||
|
`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(
|
||||||
|
finishedTxMeta.txParams,
|
||||||
|
)}`,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Validates and generates a txMeta with defaults and puts it in txStateManager
|
* Validates and generates a txMeta with defaults and puts it in txStateManager
|
||||||
store
|
* store.
|
||||||
|
*
|
||||||
@returns {txMeta}
|
* @returns {txMeta}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async addUnapprovedTransaction (txParams) {
|
async addUnapprovedTransaction (txParams) {
|
||||||
// validate
|
// validate
|
||||||
const normalizedTxParams = txUtils.normalizeTxParams(txParams)
|
const normalizedTxParams = txUtils.normalizeTxParams(txParams)
|
||||||
|
@ -180,7 +209,7 @@ class TransactionController extends EventEmitter {
|
||||||
// construct txMeta
|
// construct txMeta
|
||||||
let txMeta = this.txStateManager.generateTxMeta({
|
let txMeta = this.txStateManager.generateTxMeta({
|
||||||
txParams: normalizedTxParams,
|
txParams: normalizedTxParams,
|
||||||
type: TRANSACTION_TYPE_STANDARD,
|
type: TRANSACTION_TYPES.STANDARD,
|
||||||
})
|
})
|
||||||
this.addTx(txMeta)
|
this.addTx(txMeta)
|
||||||
this.emit('newUnapprovedTx', txMeta)
|
this.emit('newUnapprovedTx', txMeta)
|
||||||
|
@ -201,10 +230,11 @@ class TransactionController extends EventEmitter {
|
||||||
|
|
||||||
return txMeta
|
return txMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
adds the tx gas defaults: gas && gasPrice
|
* Adds the tx gas defaults: gas && gasPrice
|
||||||
@param txMeta {Object} - the txMeta object
|
* @param {Object} txMeta - the txMeta object
|
||||||
@returns {Promise<object>} resolves with txMeta
|
* @returns {Promise<object>} resolves with txMeta
|
||||||
*/
|
*/
|
||||||
async addTxGasDefaults (txMeta) {
|
async addTxGasDefaults (txMeta) {
|
||||||
const txParams = txMeta.txParams
|
const txParams = txMeta.txParams
|
||||||
|
@ -247,7 +277,7 @@ class TransactionController extends EventEmitter {
|
||||||
* new transaction contains the same nonce as the previous, is a basic ETH transfer of 0x value to
|
* new transaction contains the same nonce as the previous, is a basic ETH transfer of 0x value to
|
||||||
* the sender's address, and has a higher gasPrice than that of the previous transaction.
|
* the sender's address, and has a higher gasPrice than that of the previous transaction.
|
||||||
* @param {number} originalTxId - the id of the txMeta that you want to attempt to cancel
|
* @param {number} originalTxId - the id of the txMeta that you want to attempt to cancel
|
||||||
* @param {string=} customGasPrice - the hex value to use for the cancel transaction
|
* @param {string} [customGasPrice] - the hex value to use for the cancel transaction
|
||||||
* @returns {txMeta}
|
* @returns {txMeta}
|
||||||
*/
|
*/
|
||||||
async createCancelTransaction (originalTxId, customGasPrice) {
|
async createCancelTransaction (originalTxId, customGasPrice) {
|
||||||
|
@ -267,8 +297,8 @@ class TransactionController extends EventEmitter {
|
||||||
},
|
},
|
||||||
lastGasPrice,
|
lastGasPrice,
|
||||||
loadingDefaults: false,
|
loadingDefaults: false,
|
||||||
status: TRANSACTION_STATUS_APPROVED,
|
status: TRANSACTION_STATUSES.APPROVED,
|
||||||
type: TRANSACTION_TYPE_CANCEL,
|
type: TRANSACTION_TYPES.CANCEL,
|
||||||
})
|
})
|
||||||
|
|
||||||
this.addTx(newTxMeta)
|
this.addTx(newTxMeta)
|
||||||
|
@ -278,7 +308,7 @@ class TransactionController extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
updates the txMeta in the txStateManager
|
updates the txMeta in the txStateManager
|
||||||
@param txMeta {Object} - the updated txMeta
|
@param {Object} txMeta - the updated txMeta
|
||||||
*/
|
*/
|
||||||
async updateTransaction (txMeta) {
|
async updateTransaction (txMeta) {
|
||||||
this.txStateManager.updateTx(txMeta, 'confTx: user updated transaction')
|
this.txStateManager.updateTx(txMeta, 'confTx: user updated transaction')
|
||||||
|
@ -286,7 +316,7 @@ class TransactionController extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
updates and approves the transaction
|
updates and approves the transaction
|
||||||
@param txMeta {Object}
|
@param {Object} txMeta
|
||||||
*/
|
*/
|
||||||
async updateAndApproveTransaction (txMeta) {
|
async updateAndApproveTransaction (txMeta) {
|
||||||
this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction')
|
this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction')
|
||||||
|
@ -300,7 +330,7 @@ class TransactionController extends EventEmitter {
|
||||||
signs the transaction
|
signs the transaction
|
||||||
publishes the transaction
|
publishes the transaction
|
||||||
if any of these steps fails the tx status will be set to failed
|
if any of these steps fails the tx status will be set to failed
|
||||||
@param txId {number} - the tx's Id
|
@param {number} txId - the tx's Id
|
||||||
*/
|
*/
|
||||||
async approveTransaction (txId, customNonce) {
|
async approveTransaction (txId, customNonce) {
|
||||||
let nonceLock
|
let nonceLock
|
||||||
|
@ -338,10 +368,11 @@ class TransactionController extends EventEmitter {
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
adds the chain id and signs the transaction and set the status to signed
|
adds the chain id and signs the transaction and set the status to signed
|
||||||
@param txId {number} - the tx's Id
|
@param {number} txId - the tx's Id
|
||||||
@returns - rawTx {string}
|
@returns {string} rawTx
|
||||||
*/
|
*/
|
||||||
async signTransaction (txId) {
|
async signTransaction (txId) {
|
||||||
const txMeta = this.txStateManager.getTx(txId)
|
const txMeta = this.txStateManager.getTx(txId)
|
||||||
|
@ -360,8 +391,8 @@ class TransactionController extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
publishes the raw tx and sets the txMeta to submitted
|
publishes the raw tx and sets the txMeta to submitted
|
||||||
@param txId {number} - the tx's Id
|
@param {number} txId - the tx's Id
|
||||||
@param rawTx {string} - the hex string of the serialized signed transaction
|
@param {string} rawTx - the hex string of the serialized signed transaction
|
||||||
@returns {Promise<void>}
|
@returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async publishTransaction (txId, rawTx) {
|
async publishTransaction (txId, rawTx) {
|
||||||
|
@ -413,7 +444,7 @@ class TransactionController extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Convenience method for the ui thats sets the transaction to rejected
|
Convenience method for the ui thats sets the transaction to rejected
|
||||||
@param txId {number} - the tx's Id
|
@param {number} txId - the tx's Id
|
||||||
@returns {Promise<void>}
|
@returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
async cancelTransaction (txId) {
|
async cancelTransaction (txId) {
|
||||||
|
@ -422,8 +453,8 @@ class TransactionController extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Sets the txHas on the txMeta
|
Sets the txHas on the txMeta
|
||||||
@param txId {number} - the tx's Id
|
@param {number} txId - the tx's Id
|
||||||
@param txHash {string} - the hash for the txMeta
|
@param {string} txHash - the hash for the txMeta
|
||||||
*/
|
*/
|
||||||
setTxHash (txId, txHash) {
|
setTxHash (txId, txHash) {
|
||||||
// Add the tx hash to the persisted meta-tx object
|
// Add the tx hash to the persisted meta-tx object
|
||||||
|
@ -469,23 +500,39 @@ class TransactionController extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
_onBootCleanUp () {
|
_onBootCleanUp () {
|
||||||
this.txStateManager.getFilteredTxList({
|
this.txStateManager
|
||||||
status: 'unapproved',
|
.getFilteredTxList({
|
||||||
|
status: TRANSACTION_STATUSES.UNAPPROVED,
|
||||||
loadingDefaults: true,
|
loadingDefaults: true,
|
||||||
}).forEach((tx) => {
|
})
|
||||||
|
.forEach((tx) => {
|
||||||
this.addTxGasDefaults(tx)
|
this.addTxGasDefaults(tx)
|
||||||
.then((txMeta) => {
|
.then((txMeta) => {
|
||||||
txMeta.loadingDefaults = false
|
txMeta.loadingDefaults = false
|
||||||
this.txStateManager.updateTx(txMeta, 'transactions: gas estimation for tx on boot')
|
this.txStateManager.updateTx(
|
||||||
}).catch((error) => {
|
txMeta,
|
||||||
this.txStateManager.setTxStatusFailed(tx.id, error)
|
'transactions: gas estimation for tx on boot',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
const txMeta = this.txStateManager.getTx(tx.id)
|
||||||
|
txMeta.loadingDefaults = false
|
||||||
|
this.txStateManager.updateTx(
|
||||||
|
txMeta,
|
||||||
|
'failed to estimate gas during boot cleanup.',
|
||||||
|
)
|
||||||
|
this.txStateManager.setTxStatusFailed(txMeta.id, error)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
this.txStateManager.getFilteredTxList({
|
this.txStateManager
|
||||||
status: TRANSACTION_STATUS_APPROVED,
|
.getFilteredTxList({
|
||||||
}).forEach((txMeta) => {
|
status: TRANSACTION_STATUSES.APPROVED,
|
||||||
const txSignError = new Error('Transaction found as "approved" during boot - possibly stuck during signing')
|
})
|
||||||
|
.forEach((txMeta) => {
|
||||||
|
const txSignError = new Error(
|
||||||
|
'Transaction found as "approved" during boot - possibly stuck during signing',
|
||||||
|
)
|
||||||
this.txStateManager.setTxStatusFailed(txMeta.id, txSignError)
|
this.txStateManager.setTxStatusFailed(txMeta.id, txSignError)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -495,23 +542,46 @@ class TransactionController extends EventEmitter {
|
||||||
and blockTracker
|
and blockTracker
|
||||||
*/
|
*/
|
||||||
_setupListeners () {
|
_setupListeners () {
|
||||||
this.txStateManager.on('tx:status-update', this.emit.bind(this, 'tx:status-update'))
|
this.txStateManager.on(
|
||||||
|
'tx:status-update',
|
||||||
|
this.emit.bind(this, 'tx:status-update'),
|
||||||
|
)
|
||||||
this._setupBlockTrackerListener()
|
this._setupBlockTrackerListener()
|
||||||
this.pendingTxTracker.on('tx:warning', (txMeta) => {
|
this.pendingTxTracker.on('tx:warning', (txMeta) => {
|
||||||
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning')
|
this.txStateManager.updateTx(
|
||||||
|
txMeta,
|
||||||
|
'transactions/pending-tx-tracker#event: tx:warning',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager))
|
this.pendingTxTracker.on(
|
||||||
this.pendingTxTracker.on('tx:confirmed', (txId) => this.confirmTransaction(txId))
|
'tx:failed',
|
||||||
|
this.txStateManager.setTxStatusFailed.bind(this.txStateManager),
|
||||||
|
)
|
||||||
|
this.pendingTxTracker.on('tx:confirmed', (txId, transactionReceipt) =>
|
||||||
|
this.confirmTransaction(txId, transactionReceipt),
|
||||||
|
)
|
||||||
|
this.pendingTxTracker.on(
|
||||||
|
'tx:dropped',
|
||||||
|
this.txStateManager.setTxStatusDropped.bind(this.txStateManager),
|
||||||
|
)
|
||||||
this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => {
|
this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => {
|
||||||
if (!txMeta.firstRetryBlockNumber) {
|
if (!txMeta.firstRetryBlockNumber) {
|
||||||
txMeta.firstRetryBlockNumber = latestBlockNumber
|
txMeta.firstRetryBlockNumber = latestBlockNumber
|
||||||
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:block-update')
|
this.txStateManager.updateTx(
|
||||||
|
txMeta,
|
||||||
|
'transactions/pending-tx-tracker#event: tx:block-update',
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.pendingTxTracker.on('tx:retry', (txMeta) => {
|
this.pendingTxTracker.on('tx:retry', (txMeta) => {
|
||||||
if (!('retryCount' in txMeta)) txMeta.retryCount = 0
|
if (!('retryCount' in txMeta)) {
|
||||||
txMeta.retryCount++
|
txMeta.retryCount = 0
|
||||||
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:retry')
|
}
|
||||||
|
txMeta.retryCount += 1
|
||||||
|
this.txStateManager.updateTx(
|
||||||
|
txMeta,
|
||||||
|
'transactions/pending-tx-tracker#event: tx:retry',
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -519,7 +589,7 @@ class TransactionController extends EventEmitter {
|
||||||
Sets other txMeta statuses to dropped if the txMeta that has been confirmed has other transactions
|
Sets other txMeta statuses to dropped if the txMeta that has been confirmed has other transactions
|
||||||
in the list have the same nonce
|
in the list have the same nonce
|
||||||
|
|
||||||
@param txId {Number} - the txId of the transaction that has been confirmed in a block
|
@param {number} txId - the txId of the transaction that has been confirmed in a block
|
||||||
*/
|
*/
|
||||||
_markNonceDuplicatesDropped (txId) {
|
_markNonceDuplicatesDropped (txId) {
|
||||||
// get the confirmed transactions nonce and from address
|
// get the confirmed transactions nonce and from address
|
||||||
|
@ -540,8 +610,7 @@ class TransactionController extends EventEmitter {
|
||||||
_setupBlockTrackerListener () {
|
_setupBlockTrackerListener () {
|
||||||
let listenersAreActive = false
|
let listenersAreActive = false
|
||||||
const latestBlockHandler = this._onLatestBlock.bind(this)
|
const latestBlockHandler = this._onLatestBlock.bind(this)
|
||||||
const blockTracker = this.blockTracker
|
const { blockTracker, txStateManager } = this
|
||||||
const txStateManager = this.txStateManager
|
|
||||||
|
|
||||||
txStateManager.on('tx:status-update', updateSubscription)
|
txStateManager.on('tx:status-update', updateSubscription)
|
||||||
updateSubscription()
|
updateSubscription()
|
||||||
|
|
|
@ -1,20 +1,12 @@
|
||||||
import jsonDiffer from 'fast-json-patch'
|
import jsonDiffer from 'fast-json-patch'
|
||||||
import { cloneDeep } from 'lodash'
|
import { cloneDeep } from 'lodash'
|
||||||
|
|
||||||
/** @module*/
|
|
||||||
export default {
|
|
||||||
generateHistoryEntry,
|
|
||||||
replayHistory,
|
|
||||||
snapshotFromTxMeta,
|
|
||||||
migrateFromSnapshotsToDiffs,
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
converts non-initial history entries into diffs
|
converts non-initial history entries into diffs
|
||||||
@param {array} longHistory
|
@param {Array} longHistory
|
||||||
@returns {array}
|
@returns {Array}
|
||||||
*/
|
*/
|
||||||
function migrateFromSnapshotsToDiffs (longHistory) {
|
export function migrateFromSnapshotsToDiffs (longHistory) {
|
||||||
return (
|
return (
|
||||||
longHistory
|
longHistory
|
||||||
// convert non-initial history entries into diffs
|
// convert non-initial history entries into diffs
|
||||||
|
@ -31,15 +23,15 @@ function migrateFromSnapshotsToDiffs (longHistory) {
|
||||||
Generates an array of history objects sense the previous state.
|
Generates an array of history objects sense the previous state.
|
||||||
The object has the keys
|
The object has the keys
|
||||||
op (the operation performed),
|
op (the operation performed),
|
||||||
path (the key and if a nested object then each key will be seperated with a `/`)
|
path (the key and if a nested object then each key will be separated with a `/`)
|
||||||
value
|
value
|
||||||
with the first entry having the note and a timestamp when the change took place
|
with the first entry having the note and a timestamp when the change took place
|
||||||
@param {Object} previousState - the previous state of the object
|
@param {Object} previousState - the previous state of the object
|
||||||
@param {Object} newState - the update object
|
@param {Object} newState - the update object
|
||||||
@param {string} [note] - a optional note for the state change
|
@param {string} [note] - a optional note for the state change
|
||||||
@returns {array}
|
@returns {Array}
|
||||||
*/
|
*/
|
||||||
function generateHistoryEntry (previousState, newState, note) {
|
export function generateHistoryEntry (previousState, newState, note) {
|
||||||
const entry = jsonDiffer.compare(previousState, newState)
|
const entry = jsonDiffer.compare(previousState, newState)
|
||||||
// Add a note to the first op, since it breaks if we append it to the entry
|
// Add a note to the first op, since it breaks if we append it to the entry
|
||||||
if (entry[0]) {
|
if (entry[0]) {
|
||||||
|
@ -56,19 +48,20 @@ function generateHistoryEntry (previousState, newState, note) {
|
||||||
Recovers previous txMeta state obj
|
Recovers previous txMeta state obj
|
||||||
@returns {Object}
|
@returns {Object}
|
||||||
*/
|
*/
|
||||||
function replayHistory (_shortHistory) {
|
export function replayHistory (_shortHistory) {
|
||||||
const shortHistory = cloneDeep(_shortHistory)
|
const shortHistory = cloneDeep(_shortHistory)
|
||||||
return shortHistory.reduce((val, entry) => jsonDiffer.applyPatch(val, entry).newDocument)
|
return shortHistory.reduce(
|
||||||
|
(val, entry) => jsonDiffer.applyPatch(val, entry).newDocument,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param {Object} txMeta
|
* Snapshot {@code txMeta}
|
||||||
@returns {Object} - a clone object of the txMeta with out history
|
* @param {Object} txMeta - the tx metadata object
|
||||||
|
* @returns {Object} a deep clone without history
|
||||||
*/
|
*/
|
||||||
function snapshotFromTxMeta (txMeta) {
|
export function snapshotFromTxMeta (txMeta) {
|
||||||
// create txMeta snapshot for history
|
const shallow = { ...txMeta }
|
||||||
const snapshot = cloneDeep(txMeta)
|
delete shallow.history
|
||||||
// dont include previous history in this snapshot
|
return cloneDeep(shallow)
|
||||||
delete snapshot.history
|
|
||||||
return snapshot
|
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
const EventEmitter = require('events')
|
import EventEmitter from 'safe-event-emitter'
|
||||||
const log = require('loglevel')
|
import log from 'loglevel'
|
||||||
const EthQuery = require('ethjs-query')
|
import EthQuery from 'ethjs-query'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
||||||
|
|
|
@ -1,82 +1,110 @@
|
||||||
import EventEmitter from 'safe-event-emitter'
|
import EventEmitter from 'safe-event-emitter'
|
||||||
import ObservableStore from 'obs-store'
|
import { ObservableStore } from '@metamask/obs-store'
|
||||||
import log from 'loglevel'
|
import log from 'loglevel'
|
||||||
import txStateHistoryHelper from './lib/tx-state-history-helper'
|
|
||||||
import createId from '../../lib/random-id'
|
import createId from '../../lib/random-id'
|
||||||
|
import { TRANSACTION_STATUSES } from '../../../../shared/constants/transaction'
|
||||||
|
import {
|
||||||
|
generateHistoryEntry,
|
||||||
|
replayHistory,
|
||||||
|
snapshotFromTxMeta,
|
||||||
|
} from './lib/tx-state-history-helpers'
|
||||||
import { getFinalStates, normalizeTxParams } from './lib/util'
|
import { getFinalStates, normalizeTxParams } from './lib/util'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
TransactionStateManager is responsible for the state of a transaction and
|
* TransactionStatuses reimported from the shared transaction constants file
|
||||||
storing the transaction
|
* @typedef {import('../../../../shared/constants/transaction').TransactionStatuses} TransactionStatuses
|
||||||
it also has some convenience methods for finding subsets of transactions
|
*/
|
||||||
*
|
|
||||||
*STATUS METHODS
|
/**
|
||||||
<br>statuses:
|
* TransactionStateManager is responsible for the state of a transaction and
|
||||||
<br> - `'unapproved'` the user has not responded
|
* storing the transaction. It also has some convenience methods for finding
|
||||||
<br> - `'rejected'` the user has responded no!
|
* subsets of transactions.
|
||||||
<br> - `'approved'` the user has approved the tx
|
* @param {Object} opts
|
||||||
<br> - `'signed'` the tx is signed
|
* @param {Object} [opts.initState={ transactions: [] }] - initial transactions list with the key transaction {Array}
|
||||||
<br> - `'submitted'` the tx is sent to a server
|
* @param {number} [opts.txHistoryLimit] - limit for how many finished
|
||||||
<br> - `'confirmed'` the tx has been included in a block.
|
* transactions can hang around in state
|
||||||
<br> - `'failed'` the tx failed for some reason, included on tx data.
|
* @param {Function} opts.getNetwork - return network number
|
||||||
<br> - `'dropped'` the tx nonce was already used
|
* @class
|
||||||
@param {Object} opts
|
|
||||||
@param {Object} [opts.initState={ transactions: [] }] initial transactions list with the key transaction {array}
|
|
||||||
@param {number} [opts.txHistoryLimit] limit for how many finished
|
|
||||||
transactions can hang around in state
|
|
||||||
@param {function} opts.getNetwork return network number
|
|
||||||
@class
|
|
||||||
*/
|
*/
|
||||||
class TransactionStateManager extends EventEmitter {
|
class TransactionStateManager extends EventEmitter {
|
||||||
constructor ({ initState, txHistoryLimit, getNetwork }) {
|
constructor ({ initState, txHistoryLimit, getNetwork }) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this.store = new ObservableStore(
|
this.store = new ObservableStore({ transactions: [], ...initState })
|
||||||
Object.assign({
|
|
||||||
transactions: [],
|
|
||||||
}, initState))
|
|
||||||
this.txHistoryLimit = txHistoryLimit
|
this.txHistoryLimit = txHistoryLimit
|
||||||
this.getNetwork = getNetwork
|
this.getNetwork = getNetwork
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param {Object} opts - the object to use when overwriting defaults
|
* @param {Object} opts - the object to use when overwriting defaults
|
||||||
@returns {txMeta} - the default txMeta object
|
* @returns {txMeta} the default txMeta object
|
||||||
*/
|
*/
|
||||||
generateTxMeta (opts) {
|
generateTxMeta (opts) {
|
||||||
const netId = this.getNetwork()
|
const netId = this.getNetwork()
|
||||||
if (netId === 'loading') {
|
if (netId === 'loading') {
|
||||||
throw new Error('MetaMask is having trouble connecting to the network')
|
throw new Error('MetaMask is having trouble connecting to the network')
|
||||||
}
|
}
|
||||||
return Object.assign({
|
return {
|
||||||
id: createId(),
|
id: createId(),
|
||||||
time: (new Date()).getTime(),
|
time: new Date().getTime(),
|
||||||
status: 'unapproved',
|
status: TRANSACTION_STATUSES.UNAPPROVED,
|
||||||
metamaskNetworkId: netId,
|
metamaskNetworkId: netId,
|
||||||
loadingDefaults: true,
|
loadingDefaults: true,
|
||||||
}, opts)
|
...opts,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@returns {array} - of txMetas that have been filtered for only the current network
|
* Returns the full tx list for the current network
|
||||||
|
*
|
||||||
|
* The list is iterated backwards as new transactions are pushed onto it.
|
||||||
|
*
|
||||||
|
* @param {number} [limit] - a limit for the number of transactions to return
|
||||||
|
* @returns {Object[]} The {@code txMeta}s, filtered to the current network
|
||||||
*/
|
*/
|
||||||
getTxList () {
|
getTxList (limit) {
|
||||||
const network = this.getNetwork()
|
const network = this.getNetwork()
|
||||||
const fullTxList = this.getFullTxList()
|
const fullTxList = this.getFullTxList()
|
||||||
return fullTxList.filter((txMeta) => txMeta.metamaskNetworkId === network)
|
|
||||||
|
const nonces = new Set()
|
||||||
|
const txs = []
|
||||||
|
for (let i = fullTxList.length - 1; i > -1; i--) {
|
||||||
|
const txMeta = fullTxList[i]
|
||||||
|
if (txMeta.metamaskNetworkId !== network) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (limit !== undefined) {
|
||||||
|
const { nonce } = txMeta.txParams
|
||||||
|
if (!nonces.has(nonce)) {
|
||||||
|
if (nonces.size < limit) {
|
||||||
|
nonces.add(nonce)
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
txs.unshift(txMeta)
|
||||||
|
}
|
||||||
|
return txs
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@returns {array} - of all the txMetas in store
|
* @returns {Array} of all the txMetas in store
|
||||||
*/
|
*/
|
||||||
getFullTxList () {
|
getFullTxList () {
|
||||||
return this.store.getState().transactions
|
return this.store.getState().transactions
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@returns {array} - the tx list whos status is unapproved
|
* @returns {Array} the tx list with unapproved status
|
||||||
*/
|
*/
|
||||||
getUnapprovedTxList () {
|
getUnapprovedTxList () {
|
||||||
const txList = this.getTxsByMetaData('status', 'unapproved')
|
const txList = this.getTxsByMetaData(
|
||||||
|
'status',
|
||||||
|
TRANSACTION_STATUSES.UNAPPROVED,
|
||||||
|
)
|
||||||
return txList.reduce((result, tx) => {
|
return txList.reduce((result, tx) => {
|
||||||
result[tx.id] = tx
|
result[tx.id] = tx
|
||||||
return result
|
return result
|
||||||
|
@ -84,12 +112,12 @@ class TransactionStateManager extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param [address] {string} - hex prefixed address to sort the txMetas for [optional]
|
* @param {string} [address] - hex prefixed address to sort the txMetas for [optional]
|
||||||
@returns {array} - the tx list whos status is approved if no address is provide
|
* @returns {Array} the tx list with approved status if no address is provide
|
||||||
returns all txMetas who's status is approved for the current network
|
* returns all txMetas with approved statuses for the current network
|
||||||
*/
|
*/
|
||||||
getApprovedTransactions (address) {
|
getApprovedTransactions (address) {
|
||||||
const opts = { status: 'approved' }
|
const opts = { status: TRANSACTION_STATUSES.APPROVED }
|
||||||
if (address) {
|
if (address) {
|
||||||
opts.from = address
|
opts.from = address
|
||||||
}
|
}
|
||||||
|
@ -97,12 +125,12 @@ class TransactionStateManager extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param [address] {string} - hex prefixed address to sort the txMetas for [optional]
|
* @param {string} [address] - hex prefixed address to sort the txMetas for [optional]
|
||||||
@returns {array} - the tx list whos status is submitted if no address is provide
|
* @returns {Array} the tx list submitted status if no address is provide
|
||||||
returns all txMetas who's status is submitted for the current network
|
* returns all txMetas with submitted statuses for the current network
|
||||||
*/
|
*/
|
||||||
getPendingTransactions (address) {
|
getPendingTransactions (address) {
|
||||||
const opts = { status: 'submitted' }
|
const opts = { status: TRANSACTION_STATUSES.SUBMITTED }
|
||||||
if (address) {
|
if (address) {
|
||||||
opts.from = address
|
opts.from = address
|
||||||
}
|
}
|
||||||
|
@ -110,12 +138,12 @@ class TransactionStateManager extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param [address] {string} - hex prefixed address to sort the txMetas for [optional]
|
@param {string} [address] - hex prefixed address to sort the txMetas for [optional]
|
||||||
@returns {array} - the tx list whos status is confirmed if no address is provide
|
@returns {Array} the tx list whose status is confirmed if no address is provide
|
||||||
returns all txMetas who's status is confirmed for the current network
|
returns all txMetas who's status is confirmed for the current network
|
||||||
*/
|
*/
|
||||||
getConfirmedTransactions (address) {
|
getConfirmedTransactions (address) {
|
||||||
const opts = { status: 'confirmed' }
|
const opts = { status: TRANSACTION_STATUSES.CONFIRMED }
|
||||||
if (address) {
|
if (address) {
|
||||||
opts.from = address
|
opts.from = address
|
||||||
}
|
}
|
||||||
|
@ -123,13 +151,13 @@ class TransactionStateManager extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Adds the txMeta to the list of transactions in the store.
|
* Adds the txMeta to the list of transactions in the store.
|
||||||
if the list is over txHistoryLimit it will remove a transaction that
|
* if the list is over txHistoryLimit it will remove a transaction that
|
||||||
is in its final state
|
* is in its final state.
|
||||||
it will allso add the key `history` to the txMeta with the snap shot of the original
|
* it will also add the key `history` to the txMeta with the snap shot of
|
||||||
object
|
* the original object
|
||||||
@param {Object} txMeta
|
* @param {Object} txMeta
|
||||||
@returns {Object} - the txMeta
|
* @returns {Object} the txMeta
|
||||||
*/
|
*/
|
||||||
addTx (txMeta) {
|
addTx (txMeta) {
|
||||||
// normalize and validate txParams if present
|
// normalize and validate txParams if present
|
||||||
|
@ -137,21 +165,21 @@ class TransactionStateManager extends EventEmitter {
|
||||||
txMeta.txParams = this.normalizeAndValidateTxParams(txMeta.txParams)
|
txMeta.txParams = this.normalizeAndValidateTxParams(txMeta.txParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.once(`${txMeta.id}:signed`, function () {
|
this.once(`${txMeta.id}:signed`, () => {
|
||||||
this.removeAllListeners(`${txMeta.id}:rejected`)
|
this.removeAllListeners(`${txMeta.id}:rejected`)
|
||||||
})
|
})
|
||||||
this.once(`${txMeta.id}:rejected`, function () {
|
this.once(`${txMeta.id}:rejected`, () => {
|
||||||
this.removeAllListeners(`${txMeta.id}:signed`)
|
this.removeAllListeners(`${txMeta.id}:signed`)
|
||||||
})
|
})
|
||||||
// initialize history
|
// initialize history
|
||||||
txMeta.history = []
|
txMeta.history = []
|
||||||
// capture initial snapshot of txMeta for history
|
// capture initial snapshot of txMeta for history
|
||||||
const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta)
|
const snapshot = snapshotFromTxMeta(txMeta)
|
||||||
txMeta.history.push(snapshot)
|
txMeta.history.push(snapshot)
|
||||||
|
|
||||||
const transactions = this.getFullTxList()
|
const transactions = this.getFullTxList()
|
||||||
const txCount = transactions.length
|
const txCount = transactions.length
|
||||||
const txHistoryLimit = this.txHistoryLimit
|
const { txHistoryLimit } = this
|
||||||
|
|
||||||
// checks if the length of the tx history is
|
// checks if the length of the tx history is
|
||||||
// longer then desired persistence limit
|
// longer then desired persistence limit
|
||||||
|
@ -166,8 +194,9 @@ class TransactionStateManager extends EventEmitter {
|
||||||
transactions.splice(index, 1)
|
transactions.splice(index, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const newTxIndex = transactions
|
const newTxIndex = transactions.findIndex(
|
||||||
.findIndex((currentTxMeta) => currentTxMeta.time > txMeta.time)
|
(currentTxMeta) => currentTxMeta.time > txMeta.time,
|
||||||
|
)
|
||||||
|
|
||||||
newTxIndex === -1
|
newTxIndex === -1
|
||||||
? transactions.push(txMeta)
|
? transactions.push(txMeta)
|
||||||
|
@ -175,10 +204,11 @@ class TransactionStateManager extends EventEmitter {
|
||||||
this._saveTxList(transactions)
|
this._saveTxList(transactions)
|
||||||
return txMeta
|
return txMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param {number} txId
|
* @param {number} txId
|
||||||
@returns {Object} - the txMeta who matches the given id if none found
|
* @returns {Object} the txMeta who matches the given id if none found
|
||||||
for the network returns undefined
|
* for the network returns undefined
|
||||||
*/
|
*/
|
||||||
getTx (txId) {
|
getTx (txId) {
|
||||||
const txMeta = this.getTxsByMetaData('id', txId)[0]
|
const txMeta = this.getTxsByMetaData('id', txId)[0]
|
||||||
|
@ -186,9 +216,9 @@ class TransactionStateManager extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
updates the txMeta in the list and adds a history entry
|
* updates the txMeta in the list and adds a history entry
|
||||||
@param {Object} txMeta - the txMeta to update
|
* @param {Object} txMeta - the txMeta to update
|
||||||
@param {string} [note] - a note about the update for history
|
* @param {string} [note] - a note about the update for history
|
||||||
*/
|
*/
|
||||||
updateTx (txMeta, note) {
|
updateTx (txMeta, note) {
|
||||||
// normalize and validate txParams if present
|
// normalize and validate txParams if present
|
||||||
|
@ -197,12 +227,14 @@ class TransactionStateManager extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create txMeta snapshot for history
|
// create txMeta snapshot for history
|
||||||
const currentState = txStateHistoryHelper.snapshotFromTxMeta(txMeta)
|
const currentState = snapshotFromTxMeta(txMeta)
|
||||||
// recover previous tx state obj
|
// recover previous tx state obj
|
||||||
const previousState = txStateHistoryHelper.replayHistory(txMeta.history)
|
const previousState = replayHistory(txMeta.history)
|
||||||
// generate history entry and add to history
|
// generate history entry and add to history
|
||||||
const entry = txStateHistoryHelper.generateHistoryEntry(previousState, currentState, note)
|
const entry = generateHistoryEntry(previousState, currentState, note)
|
||||||
|
if (entry.length) {
|
||||||
txMeta.history.push(entry)
|
txMeta.history.push(entry)
|
||||||
|
}
|
||||||
|
|
||||||
// commit txMeta to state
|
// commit txMeta to state
|
||||||
const txId = txMeta.id
|
const txId = txMeta.id
|
||||||
|
@ -212,12 +244,11 @@ class TransactionStateManager extends EventEmitter {
|
||||||
this._saveTxList(txList)
|
this._saveTxList(txList)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
merges txParams obj onto txMeta.txParams
|
* merges txParams obj onto txMeta.txParams use extend to ensure
|
||||||
use extend to ensure that all fields are filled
|
* that all fields are filled
|
||||||
@param {number} txId - the id of the txMeta
|
* @param {number} txId - the id of the txMeta
|
||||||
@param {Object} txParams - the updated txParams
|
* @param {Object} txParams - the updated txParams
|
||||||
*/
|
*/
|
||||||
updateTxParams (txId, txParams) {
|
updateTxParams (txId, txParams) {
|
||||||
const txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
|
@ -233,14 +264,15 @@ class TransactionStateManager extends EventEmitter {
|
||||||
if (typeof txParams.data === 'undefined') {
|
if (typeof txParams.data === 'undefined') {
|
||||||
delete txParams.data
|
delete txParams.data
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
txParams = normalizeTxParams(txParams, false)
|
txParams = normalizeTxParams(txParams, false)
|
||||||
this.validateTxParams(txParams)
|
this.validateTxParams(txParams)
|
||||||
return txParams
|
return txParams
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
validates txParams members by type
|
* validates txParams members by type
|
||||||
@param {Object} txParams - txParams to validate
|
* @param {Object} txParams - txParams to validate
|
||||||
*/
|
*/
|
||||||
validateTxParams (txParams) {
|
validateTxParams (txParams) {
|
||||||
Object.keys(txParams).forEach((key) => {
|
Object.keys(txParams).forEach((key) => {
|
||||||
|
@ -249,12 +281,16 @@ class TransactionStateManager extends EventEmitter {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'chainId':
|
case 'chainId':
|
||||||
if (typeof value !== 'number' && typeof value !== 'string') {
|
if (typeof value !== 'number' && typeof value !== 'string') {
|
||||||
throw new Error(`${key} in txParams is not a Number or hex string. got: (${value})`)
|
throw new Error(
|
||||||
|
`${key} in txParams is not a Number or hex string. got: (${value})`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
if (typeof value !== 'string') {
|
if (typeof value !== 'string') {
|
||||||
throw new Error(`${key} in txParams is not a string. got: (${value})`)
|
throw new Error(
|
||||||
|
`${key} in txParams is not a string. got: (${value})`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -271,8 +307,8 @@ class TransactionStateManager extends EventEmitter {
|
||||||
}<br></code>
|
}<br></code>
|
||||||
optionally the values of the keys can be functions for situations like where
|
optionally the values of the keys can be functions for situations like where
|
||||||
you want all but one status.
|
you want all but one status.
|
||||||
@param [initialList=this.getTxList()]
|
@param {Array} [initialList=this.getTxList()]
|
||||||
@returns {array} - array of txMeta with all
|
@returns {Array} array of txMeta with all
|
||||||
options matching
|
options matching
|
||||||
*/
|
*/
|
||||||
/*
|
/*
|
||||||
|
@ -296,13 +332,13 @@ class TransactionStateManager extends EventEmitter {
|
||||||
})
|
})
|
||||||
return filteredTxList
|
return filteredTxList
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
|
|
||||||
@param {string} key - the key to check
|
/**
|
||||||
@param value - the value your looking for can also be a function that returns a bool
|
* @param {string} key - the key to check
|
||||||
@param [txList=this.getTxList()] {array} - the list to search. default is the txList
|
* @param {any} value - the value your looking for can also be a function that returns a bool
|
||||||
from txStateManager#getTxList
|
* @param {Array} [txList=this.getTxList()] - the list to search. default is the txList
|
||||||
@returns {array} - a list of txMetas who matches the search params
|
* from txStateManager#getTxList
|
||||||
|
* @returns {Array} a list of txMetas who matches the search params
|
||||||
*/
|
*/
|
||||||
getTxsByMetaData (key, value, txList = this.getTxList()) {
|
getTxsByMetaData (key, value, txList = this.getTxList()) {
|
||||||
const filter = typeof value === 'function' ? value : (v) => v === value
|
const filter = typeof value === 'function' ? value : (v) => v === value
|
||||||
|
@ -310,17 +346,16 @@ class TransactionStateManager extends EventEmitter {
|
||||||
return txList.filter((txMeta) => {
|
return txList.filter((txMeta) => {
|
||||||
if (key in txMeta.txParams) {
|
if (key in txMeta.txParams) {
|
||||||
return filter(txMeta.txParams[key])
|
return filter(txMeta.txParams[key])
|
||||||
} else {
|
|
||||||
return filter(txMeta[key])
|
|
||||||
}
|
}
|
||||||
|
return filter(txMeta[key])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// get::set status
|
// get::set status
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param {number} txId - the txMeta Id
|
* @param {number} txId - the txMeta Id
|
||||||
@returns {string} - the status of the tx.
|
* @returns {string} the status of the tx.
|
||||||
*/
|
*/
|
||||||
getTxStatus (txId) {
|
getTxStatus (txId) {
|
||||||
const txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
|
@ -328,8 +363,8 @@ class TransactionStateManager extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'rejected'.
|
* Update the status of the tx to 'rejected'.
|
||||||
@param {number} txId - the txMeta Id
|
* @param {number} txId - the txMeta Id
|
||||||
*/
|
*/
|
||||||
setTxStatusRejected (txId) {
|
setTxStatusRejected (txId) {
|
||||||
this._setTxStatus(txId, 'rejected')
|
this._setTxStatus(txId, 'rejected')
|
||||||
|
@ -337,65 +372,64 @@ class TransactionStateManager extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'unapproved'.
|
* Update the status of the tx to 'unapproved'.
|
||||||
@param {number} txId - the txMeta Id
|
* @param {number} txId - the txMeta Id
|
||||||
*/
|
*/
|
||||||
setTxStatusUnapproved (txId) {
|
setTxStatusUnapproved (txId) {
|
||||||
this._setTxStatus(txId, 'unapproved')
|
this._setTxStatus(txId, TRANSACTION_STATUSES.UNAPPROVED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'approved'.
|
* Update the status of the tx to 'approved'.
|
||||||
@param {number} txId - the txMeta Id
|
* @param {number} txId - the txMeta Id
|
||||||
*/
|
*/
|
||||||
setTxStatusApproved (txId) {
|
setTxStatusApproved (txId) {
|
||||||
this._setTxStatus(txId, 'approved')
|
this._setTxStatus(txId, TRANSACTION_STATUSES.APPROVED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'signed'.
|
* Update the status of the tx to 'signed'.
|
||||||
@param {number} txId - the txMeta Id
|
* @param {number} txId - the txMeta Id
|
||||||
*/
|
*/
|
||||||
setTxStatusSigned (txId) {
|
setTxStatusSigned (txId) {
|
||||||
this._setTxStatus(txId, 'signed')
|
this._setTxStatus(txId, TRANSACTION_STATUSES.SIGNED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'submitted'.
|
* Update the status of the tx to 'submitted' and add a time stamp
|
||||||
and add a time stamp for when it was called
|
* for when it was called
|
||||||
@param {number} txId - the txMeta Id
|
* @param {number} txId - the txMeta Id
|
||||||
*/
|
*/
|
||||||
setTxStatusSubmitted (txId) {
|
setTxStatusSubmitted (txId) {
|
||||||
const txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
txMeta.submittedTime = (new Date()).getTime()
|
txMeta.submittedTime = new Date().getTime()
|
||||||
this.updateTx(txMeta, 'txStateManager - add submitted time stamp')
|
this.updateTx(txMeta, 'txStateManager - add submitted time stamp')
|
||||||
this._setTxStatus(txId, 'submitted')
|
this._setTxStatus(txId, TRANSACTION_STATUSES.SUBMITTED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'confirmed'.
|
* Update the status of the tx to 'confirmed'.
|
||||||
@param {number} txId - the txMeta Id
|
* @param {number} txId - the txMeta Id
|
||||||
*/
|
*/
|
||||||
setTxStatusConfirmed (txId) {
|
setTxStatusConfirmed (txId) {
|
||||||
this._setTxStatus(txId, 'confirmed')
|
this._setTxStatus(txId, TRANSACTION_STATUSES.CONFIRMED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'dropped'.
|
* Update the status of the tx to 'dropped'.
|
||||||
@param {number} txId - the txMeta Id
|
* @param {number} txId - the txMeta Id
|
||||||
*/
|
*/
|
||||||
setTxStatusDropped (txId) {
|
setTxStatusDropped (txId) {
|
||||||
this._setTxStatus(txId, 'dropped')
|
this._setTxStatus(txId, TRANSACTION_STATUSES.DROPPED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'failed'.
|
* Updates the status of the tx to 'failed' and put the error on the txMeta
|
||||||
and put the error on the txMeta
|
* @param {number} txId - the txMeta Id
|
||||||
@param {number} txId - the txMeta Id
|
* @param {erroObject} err - error object
|
||||||
@param {erroObject} err - error object
|
|
||||||
*/
|
*/
|
||||||
setTxStatusFailed (txId, err) {
|
setTxStatusFailed (txId, err) {
|
||||||
const error = !err ? new Error('Internal metamask failure') : err
|
const error = err || new Error('Internal metamask failure')
|
||||||
|
|
||||||
const txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
txMeta.err = {
|
txMeta.err = {
|
||||||
|
@ -404,13 +438,14 @@ class TransactionStateManager extends EventEmitter {
|
||||||
stack: error.stack,
|
stack: error.stack,
|
||||||
}
|
}
|
||||||
this.updateTx(txMeta, 'transactions:tx-state-manager#fail - add error')
|
this.updateTx(txMeta, 'transactions:tx-state-manager#fail - add error')
|
||||||
this._setTxStatus(txId, 'failed')
|
this._setTxStatus(txId, TRANSACTION_STATUSES.FAILED)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Removes transaction from the given address for the current network
|
* Removes transaction from the given address for the current network
|
||||||
from the txList
|
* from the txList
|
||||||
@param {string} address - hex string of the from address on the txParams to remove
|
* @param {string} address - hex string of the from address on the txParams
|
||||||
|
* to remove
|
||||||
*/
|
*/
|
||||||
wipeTransactions (address) {
|
wipeTransactions (address) {
|
||||||
// network only tx
|
// network only tx
|
||||||
|
@ -418,32 +453,28 @@ class TransactionStateManager extends EventEmitter {
|
||||||
const network = this.getNetwork()
|
const network = this.getNetwork()
|
||||||
|
|
||||||
// Filter out the ones from the current account and network
|
// Filter out the ones from the current account and network
|
||||||
const otherAccountTxs = txs.filter((txMeta) => !(txMeta.txParams.from === address && txMeta.metamaskNetworkId === network))
|
const otherAccountTxs = txs.filter(
|
||||||
|
(txMeta) =>
|
||||||
|
!(
|
||||||
|
txMeta.txParams.from === address &&
|
||||||
|
txMeta.metamaskNetworkId === network
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
this._saveTxList(otherAccountTxs)
|
this._saveTxList(otherAccountTxs)
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// PRIVATE METHODS
|
// PRIVATE METHODS
|
||||||
//
|
//
|
||||||
|
|
||||||
// STATUS METHODS
|
|
||||||
// statuses:
|
|
||||||
// - `'unapproved'` the user has not responded
|
|
||||||
// - `'rejected'` the user has responded no!
|
|
||||||
// - `'approved'` the user has approved the tx
|
|
||||||
// - `'signed'` the tx is signed
|
|
||||||
// - `'submitted'` the tx is sent to a server
|
|
||||||
// - `'confirmed'` the tx has been included in a block.
|
|
||||||
// - `'failed'` the tx failed for some reason, included on tx data.
|
|
||||||
// - `'dropped'` the tx nonce was already used
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param {number} txId - the txMeta Id
|
* @param {number} txId - the txMeta Id
|
||||||
@param {string} status - the status to set on the txMeta
|
* @param {TransactionStatuses[keyof TransactionStatuses]} status - the status to set on the txMeta
|
||||||
@emits tx:status-update - passes txId and status
|
* @emits tx:status-update - passes txId and status
|
||||||
@emits ${txMeta.id}:finished - if it is a finished state. Passes the txMeta
|
* @emits ${txMeta.id}:finished - if it is a finished state. Passes the txMeta
|
||||||
@emits update:badge
|
* @emits update:badge
|
||||||
*/
|
*/
|
||||||
_setTxStatus (txId, status) {
|
_setTxStatus (txId, status) {
|
||||||
const txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
|
@ -453,26 +484,29 @@ class TransactionStateManager extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
txMeta.status = status
|
txMeta.status = status
|
||||||
setTimeout(() => {
|
|
||||||
try {
|
try {
|
||||||
this.updateTx(txMeta, `txStateManager: setting status to ${status}`)
|
this.updateTx(txMeta, `txStateManager: setting status to ${status}`)
|
||||||
this.emit(`${txMeta.id}:${status}`, txId)
|
this.emit(`${txMeta.id}:${status}`, txId)
|
||||||
this.emit(`tx:status-update`, txId, status)
|
this.emit(`tx:status-update`, txId, status)
|
||||||
if (['submitted', 'rejected', 'failed'].includes(status)) {
|
if (
|
||||||
|
[
|
||||||
|
TRANSACTION_STATUSES.SUBMITTED,
|
||||||
|
TRANSACTION_STATUSES.REJECTED,
|
||||||
|
TRANSACTION_STATUSES.FAILED,
|
||||||
|
].includes(status)
|
||||||
|
) {
|
||||||
this.emit(`${txMeta.id}:finished`, txMeta)
|
this.emit(`${txMeta.id}:finished`, txMeta)
|
||||||
}
|
}
|
||||||
this.emit('update:badge')
|
this.emit('update:badge')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(error)
|
log.error(error)
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Saves the new/updated txList.
|
* Saves the new/updated txList. Intended only for internal use
|
||||||
@param {array} transactions - the list of transactions to save
|
* @param {Array} transactions - the list of transactions to save
|
||||||
*/
|
*/
|
||||||
// Function is intended only for internal use
|
|
||||||
_saveTxList (transactions) {
|
_saveTxList (transactions) {
|
||||||
this.store.updateState({ transactions })
|
this.store.updateState({ transactions })
|
||||||
}
|
}
|
||||||
|
@ -481,6 +515,17 @@ class TransactionStateManager extends EventEmitter {
|
||||||
const transactionList = this.getFullTxList()
|
const transactionList = this.getFullTxList()
|
||||||
this._saveTxList(transactionList.filter((txMeta) => txMeta.id !== txId))
|
this._saveTxList(transactionList.filter((txMeta) => txMeta.id !== txId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters out the unapproved transactions
|
||||||
|
*/
|
||||||
|
clearUnapprovedTxs () {
|
||||||
|
const transactions = this.getFullTxList()
|
||||||
|
const nonUnapprovedTxs = transactions.filter(
|
||||||
|
(tx) => tx.status !== TRANSACTION_STATUSES.UNAPPROVED,
|
||||||
|
)
|
||||||
|
this._saveTxList(nonUnapprovedTxs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TransactionStateManager
|
export default TransactionStateManager
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} FirstTimeState
|
* @typedef {Object} FirstTimeState
|
||||||
* @property {Object} config Initial configuration parameters
|
* @property {Object} config Initial configuration parameters
|
||||||
|
@ -10,6 +9,17 @@
|
||||||
*/
|
*/
|
||||||
const initialState = {
|
const initialState = {
|
||||||
config: {},
|
config: {},
|
||||||
|
PreferencesController: {
|
||||||
|
frequentRpcListDetail: [
|
||||||
|
{
|
||||||
|
rpcUrl: 'http://localhost:8545',
|
||||||
|
chainId: '0x539',
|
||||||
|
ticker: 'ETH',
|
||||||
|
nickname: 'Localhost 8545',
|
||||||
|
rpcPrefs: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = initialState
|
export default initialState
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
/*global Web3*/
|
|
||||||
|
|
||||||
// need to make sure we aren't affected by overlapping namespaces
|
// need to make sure we aren't affected by overlapping namespaces
|
||||||
// and that we dont affect the app with our namespace
|
// and that we dont affect the app with our namespace
|
||||||
// mostly a fix for web3's BigNumber if AMD's "define" is defined...
|
// mostly a fix for web3's BigNumber if AMD's "define" is defined...
|
||||||
|
@ -34,13 +32,11 @@ cleanContextForImports()
|
||||||
|
|
||||||
import log from 'loglevel'
|
import log from 'loglevel'
|
||||||
import LocalMessageDuplexStream from 'post-message-stream'
|
import LocalMessageDuplexStream from 'post-message-stream'
|
||||||
import MetamaskInpageProvider from 'nifty-wallet-inpage-provider'
|
import { MetaMaskInpageProvider } from 'nifty-wallet-inpage-provider'
|
||||||
|
|
||||||
// TODO:deprecate:Q1-2020
|
// TODO:deprecate:Q1-2020
|
||||||
import 'web3/dist/web3.min.js'
|
import 'web3/dist/web3.min.js'
|
||||||
|
|
||||||
import setupDappAutoReload from './lib/auto-reload.js'
|
|
||||||
|
|
||||||
restoreContextAfterImports()
|
restoreContextAfterImports()
|
||||||
|
|
||||||
log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn')
|
log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn')
|
||||||
|
@ -56,37 +52,10 @@ const metamaskStream = new LocalMessageDuplexStream({
|
||||||
})
|
})
|
||||||
|
|
||||||
// compose the inpage provider
|
// compose the inpage provider
|
||||||
const inpageProvider = new MetamaskInpageProvider(metamaskStream)
|
const inpageProvider = new MetaMaskInpageProvider(metamaskStream)
|
||||||
|
|
||||||
// set a high max listener count to avoid unnecesary warnings
|
|
||||||
inpageProvider.setMaxListeners(100)
|
|
||||||
|
|
||||||
// Work around for web3@1.0 deleting the bound `sendAsync` but not the unbound
|
|
||||||
// `sendAsync` method on the prototype, causing `this` reference issues
|
|
||||||
const proxiedInpageProvider = new Proxy(inpageProvider, {
|
|
||||||
// straight up lie that we deleted the property so that it doesnt
|
|
||||||
// throw an error in strict mode
|
|
||||||
deleteProperty: () => true,
|
|
||||||
})
|
|
||||||
|
|
||||||
//
|
|
||||||
// TODO:deprecate:Q1-2020
|
|
||||||
//
|
|
||||||
|
|
||||||
// setup web3
|
|
||||||
|
|
||||||
const web3 = new Web3(proxiedInpageProvider)
|
|
||||||
web3.setProvider = function () {
|
|
||||||
log.debug('Nifty Wallet - overrode web3.setProvider')
|
|
||||||
}
|
|
||||||
log.debug('Nifty Wallet - injected web3')
|
|
||||||
|
|
||||||
proxiedInpageProvider._web3Ref = web3.eth
|
|
||||||
|
|
||||||
setupDappAutoReload(web3, inpageProvider.publicConfigStore)
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// end deprecate:Q1-2020
|
// end deprecate:Q1-2020
|
||||||
//
|
//
|
||||||
|
|
||||||
window.ethereum = proxiedInpageProvider
|
window.ethereum = inpageProvider
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import ObservableStore from 'obs-store'
|
import { ObservableStore } from '@metamask/obs-store'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An ObservableStore that can composes a flat
|
* An ObservableStore that can composes a flat
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const EthQuery = require('eth-query')
|
const EthQuery = require('eth-query')
|
||||||
const ObservableStore = require('obs-store')
|
import { ObservableStore } from '@metamask/obs-store'
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
const pify = require('pify')
|
const pify = require('pify')
|
||||||
|
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
|
|
||||||
// TODO:deprecate:Q1-2020
|
|
||||||
|
|
||||||
module.exports = setupDappAutoReload
|
|
||||||
|
|
||||||
function setupDappAutoReload (web3, observable) {
|
|
||||||
// export web3 as a global, checking for usage
|
|
||||||
let reloadInProgress = false
|
|
||||||
let lastTimeUsed
|
|
||||||
let lastSeenNetwork
|
|
||||||
let hasBeenWarned = false
|
|
||||||
|
|
||||||
global.web3 = new Proxy(web3, {
|
|
||||||
get: (_web3, key) => {
|
|
||||||
// get the time of use
|
|
||||||
lastTimeUsed = Date.now()
|
|
||||||
// show warning once on web3 access
|
|
||||||
if (!hasBeenWarned && key !== 'currentProvider') {
|
|
||||||
console.warn(`MetaMask: In Q1 2020, MetaMask will no longer inject web3. For more information, see: https://medium.com/metamask/no-longer-injecting-web3-js-4a899ad6e59e`)
|
|
||||||
hasBeenWarned = true
|
|
||||||
}
|
|
||||||
// return value normally
|
|
||||||
return _web3[key]
|
|
||||||
},
|
|
||||||
set: (_web3, key, value) => {
|
|
||||||
// set value normally
|
|
||||||
_web3[key] = value
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
observable.subscribe(function (state) {
|
|
||||||
// if the auto refresh on network change is false do not
|
|
||||||
// do anything
|
|
||||||
if (!window.ethereum.autoRefreshOnNetworkChange) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// if reload in progress, no need to check reload logic
|
|
||||||
if (reloadInProgress) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentNetwork = state.networkVersion
|
|
||||||
|
|
||||||
// set the initial network
|
|
||||||
if (!lastSeenNetwork) {
|
|
||||||
lastSeenNetwork = currentNetwork
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip reload logic if web3 not used
|
|
||||||
if (!lastTimeUsed) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// if network did not change, exit
|
|
||||||
if (currentNetwork === lastSeenNetwork) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// initiate page reload
|
|
||||||
reloadInProgress = true
|
|
||||||
const timeSinceUse = Date.now() - lastTimeUsed
|
|
||||||
// if web3 was recently used then delay the reloading of the page
|
|
||||||
if (timeSinceUse > 500) {
|
|
||||||
triggerReset()
|
|
||||||
} else {
|
|
||||||
setTimeout(triggerReset, 500)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// reload the page
|
|
||||||
function triggerReset () {
|
|
||||||
global.location.reload()
|
|
||||||
}
|
|
|
@ -1,24 +1,22 @@
|
||||||
/**
|
/**
|
||||||
* Returns error without stack trace for better UI display
|
* Returns error without stack trace for better UI display
|
||||||
* @param {Error} err - error
|
* @param {Error} err - error
|
||||||
* @returns {Error} - Error with clean stack trace.
|
* @returns {Error} Error with clean stack trace.
|
||||||
*/
|
*/
|
||||||
function cleanErrorStack (err) {
|
export default function cleanErrorStack (err) {
|
||||||
let name = err.name
|
let { name } = err
|
||||||
name = (name === undefined) ? 'Error' : String(name)
|
name = name === undefined ? 'Error' : String(name)
|
||||||
|
|
||||||
let msg = err.message
|
let msg = err.message
|
||||||
msg = (msg === undefined) ? '' : String(msg)
|
msg = msg === undefined ? '' : String(msg)
|
||||||
|
|
||||||
if (name === '') {
|
if (name === '') {
|
||||||
err.stack = err.message
|
err.stack = err.message
|
||||||
} else if (msg === '') {
|
} else if (msg === '') {
|
||||||
err.stack = err.name
|
err.stack = err.name
|
||||||
} else {
|
} else {
|
||||||
err.stack = err.name + ': ' + err.message
|
err.stack = `${err.name}: ${err.message}`
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
export default cleanErrorStack
|
|
||||||
|
|
|
@ -1,24 +1,19 @@
|
||||||
const WritableStream = require('readable-stream').Writable
|
import { Writable as WritableStream } from 'readable-stream'
|
||||||
const promiseToCallback = require('promise-to-callback')
|
import promiseToCallback from 'promise-to-callback'
|
||||||
|
|
||||||
module.exports = createStreamSink
|
|
||||||
|
|
||||||
|
|
||||||
function createStreamSink (asyncWriteFn, _opts) {
|
|
||||||
return new AsyncWritableStream(asyncWriteFn, _opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
class AsyncWritableStream extends WritableStream {
|
class AsyncWritableStream extends WritableStream {
|
||||||
|
|
||||||
constructor (asyncWriteFn, _opts) {
|
constructor (asyncWriteFn, _opts) {
|
||||||
const opts = Object.assign({ objectMode: true }, _opts)
|
const opts = { objectMode: true, ..._opts }
|
||||||
super(opts)
|
super(opts)
|
||||||
this._asyncWriteFn = asyncWriteFn
|
this._asyncWriteFn = asyncWriteFn
|
||||||
}
|
}
|
||||||
|
|
||||||
// write from incomming stream to state
|
// write from incoming stream to state
|
||||||
_write (chunk, encoding, callback) {
|
_write (chunk, encoding, callback) {
|
||||||
promiseToCallback(this._asyncWriteFn(chunk, encoding))(callback)
|
promiseToCallback(this._asyncWriteFn(chunk, encoding))(callback)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function createStreamSink (asyncWriteFn, _opts) {
|
||||||
|
return new AsyncWritableStream(asyncWriteFn, _opts)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import EventEmitter from 'events'
|
import EventEmitter from 'events'
|
||||||
import ObservableStore from 'obs-store'
|
import { ObservableStore } from '@metamask/obs-store'
|
||||||
import ethUtil from 'ethereumjs-util'
|
import ethUtil from 'ethereumjs-util'
|
||||||
import { ethErrors } from 'eth-json-rpc-errors'
|
import { ethErrors } from 'eth-json-rpc-errors'
|
||||||
import log from 'loglevel'
|
import log from 'loglevel'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import EventEmitter from 'events'
|
import EventEmitter from 'events'
|
||||||
import ObservableStore from 'obs-store'
|
import { ObservableStore } from '@metamask/obs-store'
|
||||||
import { ethErrors } from 'eth-json-rpc-errors'
|
import { ethErrors } from 'eth-json-rpc-errors'
|
||||||
import log from 'loglevel'
|
import log from 'loglevel'
|
||||||
import createId from './random-id'
|
import createId from './random-id'
|
||||||
|
|
|
@ -1,28 +1,26 @@
|
||||||
const ethJsRpcSlug = 'Error: [ethjs-rpc] rpc error with payload '
|
const ethJsRpcSlug = 'Error: [ethjs-rpc] rpc error with payload '
|
||||||
const errorLabelPrefix = 'Error: '
|
const errorLabelPrefix = 'Error: '
|
||||||
|
|
||||||
export default extractEthjsErrorMessage
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the important part of an ethjs-rpc error message. If the passed error is not an isEthjsRpcError, the error
|
* Extracts the important part of an ethjs-rpc error message. If the passed error is not an isEthjsRpcError, the error
|
||||||
* is returned unchanged.
|
* is returned unchanged.
|
||||||
*
|
*
|
||||||
* @param {string} errorMessage - The error message to parse
|
* @param {string} errorMessage - The error message to parse
|
||||||
* @returns {string} - Returns an error message, either the same as was passed, or the ending message portion of an isEthjsRpcError
|
* @returns {string} Returns an error message, either the same as was passed, or the ending message portion of an isEthjsRpcError
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* // returns 'Transaction Failed: replacement transaction underpriced'
|
* // returns 'Transaction Failed: replacement transaction underpriced'
|
||||||
* extractEthjsErrorMessage(`Error: [ethjs-rpc] rpc error with payload {"id":3947817945380,"jsonrpc":"2.0","params":["0xf8eb8208708477359400830398539406012c8cf97bead5deae237070f9587f8e7a266d80b8843d7d3f5a0000000000000000000000000000000000000000000000000000000000081d1a000000000000000000000000000000000000000000000000001ff973cafa800000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000000000003f48025a04c32a9b630e0d9e7ff361562d850c86b7a884908135956a7e4a336fa0300d19ca06830776423f25218e8d19b267161db526e66895567147015b1f3fc47aef9a3c7"],"method":"eth_sendRawTransaction"} Error: replacement transaction underpriced`)
|
* extractEthjsErrorMessage(`Error: [ethjs-rpc] rpc error with payload {"id":3947817945380,"jsonrpc":"2.0","params":["0xf8eb8208708477359400830398539406012c8cf97bead5deae237070f9587f8e7a266d80b8843d7d3f5a0000000000000000000000000000000000000000000000000000000000081d1a000000000000000000000000000000000000000000000000001ff973cafa800000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000000000003f48025a04c32a9b630e0d9e7ff361562d850c86b7a884908135956a7e4a336fa0300d19ca06830776423f25218e8d19b267161db526e66895567147015b1f3fc47aef9a3c7"],"method":"eth_sendRawTransaction"} Error: replacement transaction underpriced`)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function extractEthjsErrorMessage (errorMessage) {
|
export default function extractEthjsErrorMessage (errorMessage) {
|
||||||
const isEthjsRpcError = errorMessage.includes(ethJsRpcSlug)
|
const isEthjsRpcError = errorMessage.includes(ethJsRpcSlug)
|
||||||
if (isEthjsRpcError) {
|
if (isEthjsRpcError) {
|
||||||
const payloadAndError = errorMessage.slice(ethJsRpcSlug.length)
|
const payloadAndError = errorMessage.slice(ethJsRpcSlug.length)
|
||||||
const originalError = payloadAndError.slice(payloadAndError.indexOf(errorLabelPrefix) + errorLabelPrefix.length)
|
const originalError = payloadAndError.slice(
|
||||||
|
payloadAndError.indexOf(errorLabelPrefix) + errorLabelPrefix.length,
|
||||||
|
)
|
||||||
return originalError
|
return originalError
|
||||||
} else {
|
}
|
||||||
return errorMessage
|
return errorMessage
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
const extension = require('extensionizer')
|
import extension from 'extensionizer'
|
||||||
const promisify = require('pify')
|
import promisify from 'pify'
|
||||||
const allLocales = require('../../_locales/index.json')
|
import allLocales from '../../_locales/index.json'
|
||||||
|
|
||||||
const getPreferredLocales = extension.i18n ? promisify(
|
const getPreferredLocales = extension.i18n
|
||||||
extension.i18n.getAcceptLanguages,
|
? promisify(extension.i18n.getAcceptLanguages, { errorFirst: false })
|
||||||
{ errorFirst: false },
|
: async () => []
|
||||||
) : async () => []
|
|
||||||
|
|
||||||
const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().replace('_', '-'))
|
// mapping some browsers return hyphen instead underscore in locale codes (e.g. zh_TW -> zh-tw)
|
||||||
|
const existingLocaleCodes = {}
|
||||||
|
allLocales.forEach((locale) => {
|
||||||
|
if (locale && locale.code) {
|
||||||
|
existingLocaleCodes[locale.code.toLowerCase().replace('_', '-')] =
|
||||||
|
locale.code
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a preferred language code, based on settings within the user's browser. If we have no translations for the
|
* Returns a preferred language code, based on settings within the user's browser. If we have no translations for the
|
||||||
|
@ -16,7 +22,7 @@ const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().r
|
||||||
* @returns {Promise<string>} Promises a locale code, either one from the user's preferred list that we have a translation for, or 'en'
|
* @returns {Promise<string>} Promises a locale code, either one from the user's preferred list that we have a translation for, or 'en'
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async function getFirstPreferredLangCode () {
|
export default async function getFirstPreferredLangCode () {
|
||||||
let userPreferredLocaleCodes
|
let userPreferredLocaleCodes
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -33,10 +39,10 @@ async function getFirstPreferredLangCode () {
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstPreferredLangCode = userPreferredLocaleCodes
|
const firstPreferredLangCode = userPreferredLocaleCodes
|
||||||
.map(code => code.toLowerCase())
|
.map((code) => code.toLowerCase().replace('_', '-'))
|
||||||
.find(code => existingLocaleCodes.includes(code))
|
.find((code) =>
|
||||||
return firstPreferredLangCode || 'en'
|
Object.prototype.hasOwnProperty.call(existingLocaleCodes, code),
|
||||||
|
)
|
||||||
|
|
||||||
|
return existingLocaleCodes[firstPreferredLangCode] || 'en'
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = getFirstPreferredLangCode
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
const clone = require('clone')
|
import { cloneDeep } from 'lodash'
|
||||||
|
|
||||||
module.exports = getObjStructure
|
|
||||||
|
|
||||||
// This will create an object that represents the structure of the given object
|
// This will create an object that represents the structure of the given object
|
||||||
// it replaces all values with the result of their type
|
// it replaces all values with the result of their type
|
||||||
|
@ -18,13 +16,13 @@ module.exports = getObjStructure
|
||||||
* Creates an object that represents the structure of the given object. It replaces all values with the result of their
|
* Creates an object that represents the structure of the given object. It replaces all values with the result of their
|
||||||
* type.
|
* type.
|
||||||
*
|
*
|
||||||
* @param {object} obj The object for which a 'structure' will be returned. Usually a plain object and not a class.
|
* @param {Object} obj - The object for which a 'structure' will be returned. Usually a plain object and not a class.
|
||||||
* @returns {object} The "mapped" version of a deep clone of the passed object, with each non-object property value
|
* @returns {Object} The "mapped" version of a deep clone of the passed object, with each non-object property value
|
||||||
* replaced with the javascript type of that value.
|
* replaced with the javascript type of that value.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function getObjStructure (obj) {
|
export default function getObjStructure (obj) {
|
||||||
const structure = clone(obj)
|
const structure = cloneDeep(obj)
|
||||||
return deepMap(structure, (value) => {
|
return deepMap(structure, (value) => {
|
||||||
return value === null ? 'null' : typeof value
|
return value === null ? 'null' : typeof value
|
||||||
})
|
})
|
||||||
|
@ -34,9 +32,9 @@ function getObjStructure (obj) {
|
||||||
* Modifies all the properties and deeply nested of a passed object. Iterates recursively over all nested objects and
|
* Modifies all the properties and deeply nested of a passed object. Iterates recursively over all nested objects and
|
||||||
* their properties, and covers the entire depth of the object. At each property value which is not an object is modified.
|
* their properties, and covers the entire depth of the object. At each property value which is not an object is modified.
|
||||||
*
|
*
|
||||||
* @param {object} target The object to modify
|
* @param {Object} target - The object to modify
|
||||||
* @param {Function} visit The modifier to apply to each non-object property value
|
* @param {Function} visit - The modifier to apply to each non-object property value
|
||||||
* @returns {object} The modified object
|
* @returns {Object} The modified object
|
||||||
*/
|
*/
|
||||||
function deepMap (target = {}, visit) {
|
function deepMap (target = {}, visit) {
|
||||||
Object.entries(target).forEach(([key, value]) => {
|
Object.entries(target).forEach(([key, value]) => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import EventEmitter from 'events'
|
import EventEmitter from 'events'
|
||||||
import ObservableStore from 'obs-store'
|
import { ObservableStore } from '@metamask/obs-store'
|
||||||
import ethUtil from 'ethereumjs-util'
|
import ethUtil from 'ethereumjs-util'
|
||||||
import { ethErrors } from 'eth-json-rpc-errors'
|
import { ethErrors } from 'eth-json-rpc-errors'
|
||||||
import createId from './random-id'
|
import createId from './random-id'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import EventEmitter from 'events'
|
import EventEmitter from 'events'
|
||||||
import ObservableStore from 'obs-store'
|
import { ObservableStore } from '@metamask/obs-store'
|
||||||
import ethUtil from 'ethereumjs-util'
|
import ethUtil from 'ethereumjs-util'
|
||||||
import { ethErrors } from 'eth-json-rpc-errors'
|
import { ethErrors } from 'eth-json-rpc-errors'
|
||||||
import log from 'loglevel'
|
import log from 'loglevel'
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
import extractEthjsErrorMessage from './extractEthjsErrorMessage'
|
|
||||||
|
|
||||||
module.exports = reportFailedTxToSentry
|
|
||||||
|
|
||||||
//
|
|
||||||
// utility for formatting failed transaction messages
|
|
||||||
// for sending to sentry
|
|
||||||
//
|
|
||||||
|
|
||||||
function reportFailedTxToSentry ({ raven, txMeta }) {
|
|
||||||
const errorMessage = 'Transaction Failed: ' + extractEthjsErrorMessage(txMeta.err.message)
|
|
||||||
raven.captureMessage(errorMessage, {
|
|
||||||
// "extra" key is required by Sentry
|
|
||||||
extra: txMeta,
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import handlers from './handlers'
|
||||||
|
|
||||||
|
const handlerMap = handlers.reduce((map, handler) => {
|
||||||
|
for (const methodName of handler.methodNames) {
|
||||||
|
map.set(methodName, handler.implementation)
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}, new Map())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a middleware that implements the RPC methods defined in the handlers
|
||||||
|
* directory.
|
||||||
|
*
|
||||||
|
* The purpose of this middleware is to create portable RPC method
|
||||||
|
* implementations that are decoupled from the rest of our background
|
||||||
|
* architecture.
|
||||||
|
*
|
||||||
|
* Handlers consume functions that hook into the background, and only depend
|
||||||
|
* on their signatures, not e.g. controller internals.
|
||||||
|
*
|
||||||
|
* Eventually, we'll want to extract this middleware into its own package.
|
||||||
|
*
|
||||||
|
* @param {Object} opts - The middleware options
|
||||||
|
* @param {Function} opts.sendMetrics - A function for sending a metrics event
|
||||||
|
* @returns {(req: Object, res: Object, next: Function, end: Function) => void}
|
||||||
|
*/
|
||||||
|
export default function createMethodMiddleware (opts) {
|
||||||
|
return function methodMiddleware (req, res, next, end) {
|
||||||
|
if (handlerMap.has(req.method)) {
|
||||||
|
return handlerMap.get(req.method)(req, res, next, end, opts)
|
||||||
|
}
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { MESSAGE_TYPE } from '../../../../../shared/constants/app'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This RPC method gets background state relevant to the provider.
|
||||||
|
* The background sends RPC notifications on state changes, but the provider
|
||||||
|
* first requests state on initialization.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const getProviderState = {
|
||||||
|
methodNames: [MESSAGE_TYPE.GET_PROVIDER_STATE],
|
||||||
|
implementation: getProviderStateHandler,
|
||||||
|
}
|
||||||
|
export default getProviderState
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} ProviderStateHandlerResult
|
||||||
|
* @property {string} chainId - The current chain ID.
|
||||||
|
* @property {boolean} isUnlocked - Whether the extension is unlocked or not.
|
||||||
|
* @property {string} networkVersion - The current network ID.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} ProviderStateHandlerOptions
|
||||||
|
* @property {() => ProviderStateHandlerResult} getProviderState - A function that
|
||||||
|
* gets the current provider state.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('json-rpc-engine').JsonRpcRequest<[]>} req - The JSON-RPC request object.
|
||||||
|
* @param {import('json-rpc-engine').JsonRpcResponse<ProviderStateHandlerResult>} res - The JSON-RPC response object.
|
||||||
|
* @param {Function} _next - The json-rpc-engine 'next' callback.
|
||||||
|
* @param {Function} end - The json-rpc-engine 'end' callback.
|
||||||
|
* @param {ProviderStateHandlerOptions} options
|
||||||
|
*/
|
||||||
|
async function getProviderStateHandler (
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
_next,
|
||||||
|
end,
|
||||||
|
{ getProviderState: _getProviderState },
|
||||||
|
) {
|
||||||
|
res.result = {
|
||||||
|
...(await _getProviderState(req.origin)),
|
||||||
|
}
|
||||||
|
return end()
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
import getProviderState from './get-provider-state'
|
||||||
|
import logWeb3ShimUsage from './log-web3-shim-usage'
|
||||||
|
import watchAsset from './watch-asset'
|
||||||
|
|
||||||
|
const handlers = [getProviderState, logWeb3ShimUsage, watchAsset]
|
||||||
|
export default handlers
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { MESSAGE_TYPE } from '../../../../../shared/constants/app'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This RPC method is called by the inpage provider whenever it detects the
|
||||||
|
* accessing of a non-existent property on our window.web3 shim.
|
||||||
|
* We collect this data to understand which sites are breaking due to the
|
||||||
|
* removal of our window.web3.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const logWeb3ShimUsage = {
|
||||||
|
methodNames: [MESSAGE_TYPE.LOG_WEB3_SHIM_USAGE],
|
||||||
|
implementation: logWeb3ShimUsageHandler,
|
||||||
|
}
|
||||||
|
export default logWeb3ShimUsage
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} LogWeb3ShimUsageOptions
|
||||||
|
* @property {Function} sendMetrics - A function that registers a metrics event.
|
||||||
|
* @property {Function} getWeb3ShimUsageState - A function that gets web3 shim
|
||||||
|
* usage state for the given origin.
|
||||||
|
* @property {Function} setWeb3ShimUsageRecorded - A function that records web3 shim
|
||||||
|
* usage for a particular origin.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('json-rpc-engine').JsonRpcRequest<unknown>} req - The JSON-RPC request object.
|
||||||
|
* @param {import('json-rpc-engine').JsonRpcResponse<true>} res - The JSON-RPC response object.
|
||||||
|
* @param {Function} _next - The json-rpc-engine 'next' callback.
|
||||||
|
* @param {Function} end - The json-rpc-engine 'end' callback.
|
||||||
|
* @param {LogWeb3ShimUsageOptions} options
|
||||||
|
*/
|
||||||
|
function logWeb3ShimUsageHandler (
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
_next,
|
||||||
|
end,
|
||||||
|
{ sendMetrics, getWeb3ShimUsageState, setWeb3ShimUsageRecorded },
|
||||||
|
) {
|
||||||
|
const { origin } = req
|
||||||
|
if (getWeb3ShimUsageState(origin) === undefined) {
|
||||||
|
setWeb3ShimUsageRecorded(origin)
|
||||||
|
|
||||||
|
sendMetrics({
|
||||||
|
event: `Website Accessed window.web3 Shim`,
|
||||||
|
category: 'inpage_provider',
|
||||||
|
eventContext: {
|
||||||
|
referrer: {
|
||||||
|
url: origin,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
excludeMetaMetricsId: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
res.result = true
|
||||||
|
return end()
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { MESSAGE_TYPE } from '../../../../../shared/constants/app'
|
||||||
|
|
||||||
|
const watchAsset = {
|
||||||
|
methodNames: [MESSAGE_TYPE.WATCH_ASSET, MESSAGE_TYPE.WATCH_ASSET_LEGACY],
|
||||||
|
implementation: watchAssetHandler,
|
||||||
|
}
|
||||||
|
export default watchAsset
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} WatchAssetOptions
|
||||||
|
* @property {Function} handleWatchAssetRequest - The wallet_watchAsset method implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} WatchAssetParam
|
||||||
|
* @property {string} type - The type of the asset to watch.
|
||||||
|
* @property {Object} options - Watch options for the asset.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import('json-rpc-engine').JsonRpcRequest<WatchAssetParam>} req - The JSON-RPC request object.
|
||||||
|
* @param {import('json-rpc-engine').JsonRpcResponse<true>} res - The JSON-RPC response object.
|
||||||
|
* @param {Function} _next - The json-rpc-engine 'next' callback.
|
||||||
|
* @param {Function} end - The json-rpc-engine 'end' callback.
|
||||||
|
* @param {WatchAssetOptions} options
|
||||||
|
*/
|
||||||
|
async function watchAssetHandler (
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
_next,
|
||||||
|
end,
|
||||||
|
{ handleWatchAssetRequest },
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
res.result = await handleWatchAssetRequest(req)
|
||||||
|
return end()
|
||||||
|
} catch (error) {
|
||||||
|
return end(error)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export { default } from './createMethodMiddleware'
|
|
@ -1,6 +1,6 @@
|
||||||
import EventEmitter from 'events'
|
import EventEmitter from 'events'
|
||||||
import assert from 'assert'
|
import assert from 'assert'
|
||||||
import ObservableStore from 'obs-store'
|
import { ObservableStore } from '@metamask/obs-store'
|
||||||
import { ethErrors } from 'eth-json-rpc-errors'
|
import { ethErrors } from 'eth-json-rpc-errors'
|
||||||
import { typedSignatureHash, TYPED_MESSAGE_SCHEMA } from 'eth-sig-util'
|
import { typedSignatureHash, TYPED_MESSAGE_SCHEMA } from 'eth-sig-util'
|
||||||
import { isValidAddress } from 'ethereumjs-util'
|
import { isValidAddress } from 'ethereumjs-util'
|
||||||
|
|
|
@ -1,36 +1,30 @@
|
||||||
/**
|
|
||||||
* @file The central metamask controller. Aggregates other controllers and exports an api.
|
|
||||||
* @copyright Copyright (c) 2018 MetaMask
|
|
||||||
* @license MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
import EventEmitter from 'events'
|
import EventEmitter from 'events'
|
||||||
|
|
||||||
import pump from 'pump'
|
import pump from 'pump'
|
||||||
import Dnode from 'dnode'
|
import Dnode from 'dnode'
|
||||||
import extension from 'extensionizer'
|
import { ObservableStore } from '@metamask/obs-store'
|
||||||
import ObservableStore from 'obs-store'
|
|
||||||
const ComposableObservableStore = require('./lib/ComposableObservableStore')
|
const ComposableObservableStore = require('./lib/ComposableObservableStore')
|
||||||
import asStream from 'obs-store/lib/asStream'
|
import { storeAsStream } from '@metamask/obs-store/dist/asStream'
|
||||||
const AccountTracker = require('./lib/account-tracker')
|
import { JsonRpcEngine } from 'json-rpc-engine'
|
||||||
import RpcEngine from 'json-rpc-engine'
|
|
||||||
import { debounce } from 'lodash'
|
import { debounce } from 'lodash'
|
||||||
const createEngineStream = require('json-rpc-middleware-stream/engineStream')
|
import createEngineStream from 'json-rpc-middleware-stream/engineStream'
|
||||||
const createFilterMiddleware = require('eth-json-rpc-filters')
|
import createFilterMiddleware from 'eth-json-rpc-filters'
|
||||||
const createSubscriptionManager = require('eth-json-rpc-filters/subscriptionManager')
|
import createSubscriptionManager from 'eth-json-rpc-filters/subscriptionManager'
|
||||||
|
import providerAsMiddleware from 'eth-json-rpc-middleware/providerAsMiddleware'
|
||||||
const createOriginMiddleware = require('./lib/createOriginMiddleware')
|
const createOriginMiddleware = require('./lib/createOriginMiddleware')
|
||||||
import createLoggerMiddleware from './lib/createLoggerMiddleware'
|
import createLoggerMiddleware from './lib/createLoggerMiddleware'
|
||||||
import createTabIdMiddleware from './lib/createTabIdMiddleware'
|
import createTabIdMiddleware from './lib/createTabIdMiddleware'
|
||||||
import providerAsMiddleware from 'eth-json-rpc-middleware/providerAsMiddleware'
|
import { setupMultiplex } from './lib/stream-utils'
|
||||||
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
|
|
||||||
const KeyringController = require('eth-keychain-controller')
|
const KeyringController = require('eth-keychain-controller')
|
||||||
|
import { Mutex } from 'await-semaphore'
|
||||||
|
const AccountTracker = require('./lib/account-tracker')
|
||||||
|
import createMethodMiddleware from './lib/rpc-method-middleware'
|
||||||
|
import { TRANSACTION_STATUSES } from '../../shared/constants/transaction'
|
||||||
const NetworkController = require('./controllers/network')
|
const NetworkController = require('./controllers/network')
|
||||||
const PreferencesController = require('./controllers/preferences')
|
const PreferencesController = require('./controllers/preferences')
|
||||||
const CurrencyController = require('./controllers/currency')
|
const CurrencyController = require('./controllers/currency')
|
||||||
const NoticeController = require('./notice-controller')
|
const NoticeController = require('./notice-controller')
|
||||||
const ShapeShiftController = require('./controllers/shapeshift')
|
const ShapeShiftController = require('./controllers/shapeshift')
|
||||||
const AddressBookController = require('./controllers/address-book')
|
const AddressBookController = require('./controllers/address-book')
|
||||||
const InfuraController = require('./controllers/infura')
|
|
||||||
const CachedBalancesController = require('./controllers/cached-balances')
|
const CachedBalancesController = require('./controllers/cached-balances')
|
||||||
const RecentBlocksController = require('./controllers/recent-blocks')
|
const RecentBlocksController = require('./controllers/recent-blocks')
|
||||||
import MessageManager from './lib/message-manager'
|
import MessageManager from './lib/message-manager'
|
||||||
|
@ -43,11 +37,10 @@ const BalancesController = require('./controllers/computed-balances')
|
||||||
const TokenRatesController = require('./controllers/token-rates')
|
const TokenRatesController = require('./controllers/token-rates')
|
||||||
const DetectTokensController = require('./controllers/detect-tokens')
|
const DetectTokensController = require('./controllers/detect-tokens')
|
||||||
import { PermissionsController } from './controllers/permissions'
|
import { PermissionsController } from './controllers/permissions'
|
||||||
|
import { NOTIFICATION_NAMES } from './controllers/permissions/enums'
|
||||||
import getRestrictedMethods from './controllers/permissions/restrictedMethods'
|
import getRestrictedMethods from './controllers/permissions/restrictedMethods'
|
||||||
const nodeify = require('./lib/nodeify')
|
const nodeify = require('./lib/nodeify')
|
||||||
const accountImporter = require('./account-import-strategies')
|
const accountImporter = require('./account-import-strategies')
|
||||||
import { Mutex } from 'await-semaphore'
|
|
||||||
import selectChainId from './lib/select-chain-id'
|
|
||||||
const version = require('../manifest.json').version
|
const version = require('../manifest.json').version
|
||||||
import ethUtil, { BN } from 'ethereumjs-util'
|
import ethUtil, { BN } from 'ethereumjs-util'
|
||||||
const GWEI_BN = new BN('1000000000')
|
const GWEI_BN = new BN('1000000000')
|
||||||
|
@ -86,6 +79,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
|
|
||||||
this.sendUpdate = debounce(this.privateSendUpdate.bind(this), 200)
|
this.sendUpdate = debounce(this.privateSendUpdate.bind(this), 200)
|
||||||
this.opts = opts
|
this.opts = opts
|
||||||
|
this.extension = opts.extension
|
||||||
|
this.platform = opts.platform
|
||||||
const initState = opts.initState || {}
|
const initState = opts.initState || {}
|
||||||
this.recordFirstTimeInfo(initState)
|
this.recordFirstTimeInfo(initState)
|
||||||
|
|
||||||
|
@ -93,8 +88,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
// the only thing that uses controller connections are open metamask UI instances
|
// the only thing that uses controller connections are open metamask UI instances
|
||||||
this.activeControllerConnections = 0
|
this.activeControllerConnections = 0
|
||||||
|
|
||||||
// platform-specific api
|
|
||||||
this.platform = opts.platform
|
|
||||||
|
|
||||||
// observable state store
|
// observable state store
|
||||||
this.store = new ComposableObservableStore(initState)
|
this.store = new ComposableObservableStore(initState)
|
||||||
|
@ -109,6 +102,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
// network store
|
// network store
|
||||||
this.networkController = new NetworkController(initState.NetworkController)
|
this.networkController = new NetworkController(initState.NetworkController)
|
||||||
this.networkController.setEthMainnetRPCEndpoint(opts.ethMainnetRpcEndpoint)
|
this.networkController.setEthMainnetRPCEndpoint(opts.ethMainnetRpcEndpoint)
|
||||||
|
this.networkController.setInfuraProjectId(opts.infuraProjectId)
|
||||||
|
|
||||||
// preferences controller
|
// preferences controller
|
||||||
this.preferencesController = new PreferencesController({
|
this.preferencesController = new PreferencesController({
|
||||||
|
@ -125,12 +119,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
this.currencyController.updateConversionRate()
|
this.currencyController.updateConversionRate()
|
||||||
this.currencyController.scheduleConversionInterval()
|
this.currencyController.scheduleConversionInterval()
|
||||||
|
|
||||||
// infura controller
|
|
||||||
this.infuraController = new InfuraController({
|
|
||||||
initState: initState.InfuraController,
|
|
||||||
})
|
|
||||||
this.infuraController.scheduleInfuraNetworkCheck()
|
|
||||||
|
|
||||||
this.phishingController = new PhishingController()
|
this.phishingController = new PhishingController()
|
||||||
|
|
||||||
// rpc provider
|
// rpc provider
|
||||||
|
@ -241,6 +229,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
this.txController = new TransactionController({
|
this.txController = new TransactionController({
|
||||||
initState: initState.TransactionController || initState.TransactionManager,
|
initState: initState.TransactionController || initState.TransactionManager,
|
||||||
networkStore: this.networkController.networkStore,
|
networkStore: this.networkController.networkStore,
|
||||||
|
getCurrentChainId: this.networkController.getCurrentChainId.bind(
|
||||||
|
this.networkController,
|
||||||
|
),
|
||||||
preferencesStore: this.preferencesController.store,
|
preferencesStore: this.preferencesController.store,
|
||||||
txHistoryLimit: 40,
|
txHistoryLimit: 40,
|
||||||
getNetwork: this.networkController.getNetworkState.bind(this),
|
getNetwork: this.networkController.getNetworkState.bind(this),
|
||||||
|
@ -251,8 +242,11 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
})
|
})
|
||||||
this.txController.on('newUnapprovedTx', () => opts.showUnapprovedTx())
|
this.txController.on('newUnapprovedTx', () => opts.showUnapprovedTx())
|
||||||
|
|
||||||
this.txController.on(`tx:status-update`, (txId, status) => {
|
this.txController.on(`tx:status-update`, async (txId, status) => {
|
||||||
if (status === 'confirmed' || status === 'failed') {
|
if (
|
||||||
|
status === TRANSACTION_STATUSES.CONFIRMED ||
|
||||||
|
status === TRANSACTION_STATUSES.FAILED
|
||||||
|
) {
|
||||||
const txMeta = this.txController.txStateManager.getTx(txId)
|
const txMeta = this.txController.txStateManager.getTx(txId)
|
||||||
this.platform.showTransactionNotification(txMeta)
|
this.platform.showTransactionNotification(txMeta)
|
||||||
}
|
}
|
||||||
|
@ -292,9 +286,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
})
|
})
|
||||||
|
|
||||||
// ensure isClientOpenAndUnlocked is updated when memState updates
|
// ensure isClientOpenAndUnlocked is updated when memState updates
|
||||||
this.on('update', (memState) => {
|
this.on('update', (memState) => this._onStateUpdate(memState))
|
||||||
this.isClientOpenAndUnlocked = memState.isUnlocked && this._isClientOpen
|
|
||||||
})
|
|
||||||
|
|
||||||
this.store.updateStructure({
|
this.store.updateStructure({
|
||||||
TransactionController: this.txController.store,
|
TransactionController: this.txController.store,
|
||||||
|
@ -305,7 +297,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
NoticeController: this.noticeController.store,
|
NoticeController: this.noticeController.store,
|
||||||
ShapeShiftController: this.shapeshiftController.store,
|
ShapeShiftController: this.shapeshiftController.store,
|
||||||
NetworkController: this.networkController.store,
|
NetworkController: this.networkController.store,
|
||||||
InfuraController: this.infuraController.store,
|
|
||||||
CachedBalancesController: this.cachedBalancesController.store,
|
CachedBalancesController: this.cachedBalancesController.store,
|
||||||
PermissionsController: this.permissionsController.permissions,
|
PermissionsController: this.permissionsController.permissions,
|
||||||
PermissionsMetadata: this.permissionsController.store,
|
PermissionsMetadata: this.permissionsController.store,
|
||||||
|
@ -330,11 +321,13 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
CurrencyController: this.currencyController.store,
|
CurrencyController: this.currencyController.store,
|
||||||
NoticeController: this.noticeController.memStore,
|
NoticeController: this.noticeController.memStore,
|
||||||
ShapeshiftController: this.shapeshiftController.store,
|
ShapeshiftController: this.shapeshiftController.store,
|
||||||
InfuraController: this.infuraController.store,
|
|
||||||
PermissionsController: this.permissionsController.permissions,
|
PermissionsController: this.permissionsController.permissions,
|
||||||
PermissionsMetadata: this.permissionsController.store,
|
PermissionsMetadata: this.permissionsController.store,
|
||||||
})
|
})
|
||||||
this.memStore.subscribe(this.sendUpdate.bind(this))
|
this.memStore.subscribe(this.sendUpdate.bind(this))
|
||||||
|
|
||||||
|
// TODO:LegacyProvider: Delete
|
||||||
|
this.publicConfigStore = this.createPublicConfigStore()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -367,7 +360,11 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
processDecryptMessage: this.newRequestDecryptMessage.bind(this),
|
processDecryptMessage: this.newRequestDecryptMessage.bind(this),
|
||||||
processEncryptionPublicKey: this.newRequestEncryptionPublicKey.bind(this),
|
processEncryptionPublicKey: this.newRequestEncryptionPublicKey.bind(this),
|
||||||
getPendingNonce: this.getPendingNonce.bind(this),
|
getPendingNonce: this.getPendingNonce.bind(this),
|
||||||
getPendingTransactionByHash: (hash) => this.txController.getFilteredTxList({ hash, status: 'submitted' })[0],
|
getPendingTransactionByHash: (hash) =>
|
||||||
|
this.txController.getFilteredTxList({
|
||||||
|
hash,
|
||||||
|
status: TRANSACTION_STATUSES.SUBMITTED,
|
||||||
|
})[0],
|
||||||
}
|
}
|
||||||
const providerProxy = this.networkController.initializeProvider(providerOpts)
|
const providerProxy = this.networkController.initializeProvider(providerOpts)
|
||||||
return providerProxy
|
return providerProxy
|
||||||
|
@ -380,30 +377,64 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
createPublicConfigStore () {
|
createPublicConfigStore () {
|
||||||
// subset of state for metamask inpage provider
|
// subset of state for metamask inpage provider
|
||||||
const publicConfigStore = new ObservableStore()
|
const publicConfigStore = new ObservableStore()
|
||||||
|
const { networkController } = this
|
||||||
|
|
||||||
// setup memStore subscription hooks
|
// setup memStore subscription hooks
|
||||||
this.on('update', updatePublicConfigStore)
|
this.on('update', updatePublicConfigStore)
|
||||||
updatePublicConfigStore(this.getState())
|
updatePublicConfigStore(this.getState())
|
||||||
|
|
||||||
publicConfigStore.destroy = () => {
|
|
||||||
this.removeEventListener && this.removeEventListener('update', updatePublicConfigStore)
|
|
||||||
}
|
|
||||||
|
|
||||||
function updatePublicConfigStore (memState) {
|
function updatePublicConfigStore (memState) {
|
||||||
publicConfigStore.putState(selectPublicState(memState))
|
const chainId = networkController.getCurrentChainId()
|
||||||
|
if (memState.network !== 'loading') {
|
||||||
|
publicConfigStore.putState(selectPublicState(chainId, memState))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectPublicState ({ isUnlocked, network, provider, selectedAddress }) {
|
function selectPublicState (chainId, { isUnlocked, network, selectedAddress }) {
|
||||||
return {
|
return {
|
||||||
isUnlocked,
|
isUnlocked,
|
||||||
|
chainId,
|
||||||
selectedAddress: isUnlocked ? selectedAddress : undefined,
|
selectedAddress: isUnlocked ? selectedAddress : undefined,
|
||||||
networkVersion: network,
|
networkVersion: network,
|
||||||
chainId: selectChainId({ network, provider }),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return publicConfigStore
|
return publicConfigStore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets relevant state for the provider of an external origin.
|
||||||
|
*
|
||||||
|
* @param {string} origin - The origin to get the provider state for.
|
||||||
|
* @returns {Promise<{
|
||||||
|
* isUnlocked: boolean,
|
||||||
|
* networkVersion: string,
|
||||||
|
* chainId: string,
|
||||||
|
* accounts: string[],
|
||||||
|
* }>} An object with relevant state properties.
|
||||||
|
*/
|
||||||
|
async getProviderState (origin) {
|
||||||
|
return {
|
||||||
|
isUnlocked: this.isUnlocked(),
|
||||||
|
...this.getProviderNetworkState(),
|
||||||
|
accounts: await this.permissionsController.getAccounts(origin),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets network state relevant for external providers.
|
||||||
|
*
|
||||||
|
* @param {Object} [memState] - The MetaMask memState. If not provided,
|
||||||
|
* this function will retrieve the most recent state.
|
||||||
|
* @returns {Object} An object with relevant network state properties.
|
||||||
|
*/
|
||||||
|
getProviderNetworkState (memState) {
|
||||||
|
const { network } = memState || this.getState()
|
||||||
|
return {
|
||||||
|
chainId: this.networkController.getCurrentChainId(),
|
||||||
|
networkVersion: network,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
// EXPOSED TO THE UI SUBSYSTEM
|
// EXPOSED TO THE UI SUBSYSTEM
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
@ -1536,8 +1567,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
*/
|
*/
|
||||||
setupUntrustedCommunication (connectionStream, sender) {
|
setupUntrustedCommunication (connectionStream, sender) {
|
||||||
const { usePhishDetect } = this.preferencesController.store.getState()
|
const { usePhishDetect } = this.preferencesController.store.getState()
|
||||||
const hostname = (new URL(sender.url)).hostname
|
const { hostname } = new URL(sender.url)
|
||||||
// Check if new connection is blacklisted if phishing detection is on
|
// Check if new connection is blocked if phishing detection is on
|
||||||
if (usePhishDetect && this.phishingController.test(hostname)) {
|
if (usePhishDetect && this.phishingController.test(hostname)) {
|
||||||
log.debug('Nifty Wallet - sending phishing warning for', hostname)
|
log.debug('Nifty Wallet - sending phishing warning for', hostname)
|
||||||
this.sendPhishingWarning(connectionStream, hostname)
|
this.sendPhishingWarning(connectionStream, hostname)
|
||||||
|
@ -1548,7 +1579,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
const mux = setupMultiplex(connectionStream)
|
const mux = setupMultiplex(connectionStream)
|
||||||
|
|
||||||
// messages between inpage and background
|
// messages between inpage and background
|
||||||
this.setupProviderConnection(mux.createStream('provider'), sender)
|
this.setupProviderConnection(mux.createStream('metamask-provider'), sender)
|
||||||
this.setupPublicConfig(mux.createStream('publicConfig'))
|
this.setupPublicConfig(mux.createStream('publicConfig'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1625,11 +1656,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
* @param {boolean} isInternal - True if this is a connection with an internal process
|
* @param {boolean} isInternal - True if this is a connection with an internal process
|
||||||
*/
|
*/
|
||||||
setupProviderConnection (outStream, sender, isInternal) {
|
setupProviderConnection (outStream, sender, isInternal) {
|
||||||
const origin = isInternal
|
const origin = isInternal ? 'metamask' : new URL(sender.url).origin
|
||||||
? 'metamask'
|
|
||||||
: (new URL(sender.url)).hostname
|
|
||||||
let extensionId
|
let extensionId
|
||||||
if (sender.id !== extension.runtime.id) {
|
if (sender.id !== this.extension.runtime.id) {
|
||||||
extensionId = sender.id
|
extensionId = sender.id
|
||||||
}
|
}
|
||||||
let tabId
|
let tabId
|
||||||
|
@ -1637,18 +1666,20 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
tabId = sender.tab.id
|
tabId = sender.tab.id
|
||||||
}
|
}
|
||||||
|
|
||||||
const engine = this.setupProviderEngine({ origin, location: sender.url, extensionId, tabId })
|
const engine = this.setupProviderEngine({
|
||||||
|
origin,
|
||||||
|
location: sender.url,
|
||||||
|
extensionId,
|
||||||
|
tabId,
|
||||||
|
isInternal,
|
||||||
|
})
|
||||||
|
|
||||||
// setup connection
|
// setup connection
|
||||||
const providerStream = createEngineStream({ engine })
|
const providerStream = createEngineStream({ engine })
|
||||||
|
|
||||||
const connectionId = this.addConnection(origin, { engine })
|
const connectionId = this.addConnection(origin, { engine })
|
||||||
|
|
||||||
pump(
|
pump(outStream, providerStream, outStream, (err) => {
|
||||||
outStream,
|
|
||||||
providerStream,
|
|
||||||
outStream,
|
|
||||||
(err) => {
|
|
||||||
// handle any middleware cleanup
|
// handle any middleware cleanup
|
||||||
engine._middleware.forEach((mid) => {
|
engine._middleware.forEach((mid) => {
|
||||||
if (mid.destroy && typeof mid.destroy === 'function') {
|
if (mid.destroy && typeof mid.destroy === 'function') {
|
||||||
|
@ -1659,8 +1690,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
if (err) {
|
if (err) {
|
||||||
log.error(err)
|
log.error(err)
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1673,7 +1703,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
**/
|
**/
|
||||||
setupProviderEngine ({ origin, location, extensionId, tabId }) {
|
setupProviderEngine ({ origin, location, extensionId, tabId }) {
|
||||||
// setup json rpc engine stack
|
// setup json rpc engine stack
|
||||||
const engine = new RpcEngine()
|
const engine = new JsonRpcEngine()
|
||||||
const provider = this.provider
|
const provider = this.provider
|
||||||
const blockTracker = this.blockTracker
|
const blockTracker = this.blockTracker
|
||||||
|
|
||||||
|
@ -1692,6 +1722,15 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
}
|
}
|
||||||
// logging
|
// logging
|
||||||
engine.push(createLoggerMiddleware({ origin }))
|
engine.push(createLoggerMiddleware({ origin }))
|
||||||
|
engine.push(
|
||||||
|
createMethodMiddleware({
|
||||||
|
origin,
|
||||||
|
getProviderState: this.getProviderState.bind(this),
|
||||||
|
handleWatchAssetRequest: this.preferencesController.requestWatchAsset.bind(
|
||||||
|
this.preferencesController,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
)
|
||||||
// filter and subscription polyfills
|
// filter and subscription polyfills
|
||||||
engine.push(filterMiddleware)
|
engine.push(filterMiddleware)
|
||||||
engine.push(subscriptionManager.middleware)
|
engine.push(subscriptionManager.middleware)
|
||||||
|
@ -1705,6 +1744,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* TODO:LegacyProvider: Delete
|
||||||
* A method for providing our public config info over a stream.
|
* A method for providing our public config info over a stream.
|
||||||
* This includes info we like to be synchronous if possible, like
|
* This includes info we like to be synchronous if possible, like
|
||||||
* the current selected account, and network ID.
|
* the current selected account, and network ID.
|
||||||
|
@ -1715,20 +1755,14 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
* @param {*} outStream - The stream to provide public config over.
|
* @param {*} outStream - The stream to provide public config over.
|
||||||
*/
|
*/
|
||||||
setupPublicConfig (outStream) {
|
setupPublicConfig (outStream) {
|
||||||
const configStore = this.createPublicConfigStore()
|
const configStream = storeAsStream(this.publicConfigStore)
|
||||||
const configStream = asStream(configStore)
|
|
||||||
|
|
||||||
pump(
|
pump(configStream, outStream, (err) => {
|
||||||
configStream,
|
|
||||||
outStream,
|
|
||||||
(err) => {
|
|
||||||
configStore.destroy()
|
|
||||||
configStream.destroy()
|
configStream.destroy()
|
||||||
if (err) {
|
if (err) {
|
||||||
log.error(err)
|
log.error(err)
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1739,10 +1773,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
* @param {string} origin - The connection's origin string.
|
* @param {string} origin - The connection's origin string.
|
||||||
* @param {Object} options - Data associated with the connection
|
* @param {Object} options - Data associated with the connection
|
||||||
* @param {Object} options.engine - The connection's JSON Rpc Engine
|
* @param {Object} options.engine - The connection's JSON Rpc Engine
|
||||||
* @returns {string} - The connection's id (so that it can be deleted later)
|
* @returns {string} The connection's id (so that it can be deleted later)
|
||||||
*/
|
*/
|
||||||
addConnection (origin, { engine }) {
|
addConnection (origin, { engine }) {
|
||||||
|
|
||||||
if (origin === 'metamask') {
|
if (origin === 'metamask') {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -1767,7 +1800,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
* @param {string} id - The connection's id, as returned from addConnection.
|
* @param {string} id - The connection's id, as returned from addConnection.
|
||||||
*/
|
*/
|
||||||
removeConnection (origin, id) {
|
removeConnection (origin, id) {
|
||||||
|
|
||||||
const connections = this.connections[origin]
|
const connections = this.connections[origin]
|
||||||
if (!connections) {
|
if (!connections) {
|
||||||
return
|
return
|
||||||
|
@ -1775,7 +1807,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
|
|
||||||
delete connections[id]
|
delete connections[id]
|
||||||
|
|
||||||
if (Object.keys(connections.length === 0)) {
|
if (Object.keys(connections).length === 0) {
|
||||||
delete this.connections[origin]
|
delete this.connections[origin]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1783,49 +1815,59 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
/**
|
/**
|
||||||
* Causes the RPC engines associated with the connections to the given origin
|
* Causes the RPC engines associated with the connections to the given origin
|
||||||
* to emit a notification event with the given payload.
|
* to emit a notification event with the given payload.
|
||||||
* Does nothing if the extension is locked or the origin is unknown.
|
*
|
||||||
|
* The caller is responsible for ensuring that only permitted notifications
|
||||||
|
* are sent.
|
||||||
|
*
|
||||||
|
* Ignores unknown origins.
|
||||||
*
|
*
|
||||||
* @param {string} origin - The connection's origin string.
|
* @param {string} origin - The connection's origin string.
|
||||||
* @param {any} payload - The event payload.
|
* @param {any} payload - The event payload.
|
||||||
*/
|
*/
|
||||||
notifyConnections (origin, payload) {
|
notifyConnections (origin, payload) {
|
||||||
|
|
||||||
const { isUnlocked } = this.getState()
|
|
||||||
const connections = this.connections[origin]
|
const connections = this.connections[origin]
|
||||||
if (!isUnlocked || !connections) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (connections) {
|
||||||
Object.values(connections).forEach((conn) => {
|
Object.values(connections).forEach((conn) => {
|
||||||
conn.engine && conn.engine.emit('notification', payload)
|
if (conn.engine) {
|
||||||
|
conn.engine.emit('notification', payload)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Causes the RPC engines associated with all connections to emit a
|
* Causes the RPC engines associated with all connections to emit a
|
||||||
* notification event with the given payload.
|
* notification event with the given payload.
|
||||||
* Does nothing if the extension is locked.
|
|
||||||
*
|
*
|
||||||
* @param {any} payload - The event payload.
|
* If the "payload" parameter is a function, the payload for each connection
|
||||||
|
* will be the return value of that function called with the connection's
|
||||||
|
* origin.
|
||||||
|
*
|
||||||
|
* The caller is responsible for ensuring that only permitted notifications
|
||||||
|
* are sent.
|
||||||
|
*
|
||||||
|
* @param {any} payload - The event payload, or payload getter function.
|
||||||
*/
|
*/
|
||||||
notifyAllConnections (payload) {
|
notifyAllConnections (payload) {
|
||||||
|
const getPayload =
|
||||||
const { isUnlocked } = this.getState()
|
typeof payload === 'function'
|
||||||
if (!isUnlocked) {
|
? (origin) => payload(origin)
|
||||||
return
|
: () => payload
|
||||||
}
|
|
||||||
|
|
||||||
Object.values(this.connections).forEach((origin) => {
|
Object.values(this.connections).forEach((origin) => {
|
||||||
Object.values(origin).forEach((conn) => {
|
Object.values(origin).forEach((conn) => {
|
||||||
conn.engine && conn.engine.emit('notification', payload)
|
if (conn.engine) {
|
||||||
|
conn.engine.emit('notification', getPayload(origin))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a KeyringController update
|
* Handle a KeyringController update
|
||||||
* @param {object} state the KC state
|
* @param {Object} state - the KC state
|
||||||
* @return {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
async _onKeyringControllerUpdate (state) {
|
async _onKeyringControllerUpdate (state) {
|
||||||
|
@ -1850,6 +1892,53 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle global unlock, triggered by KeyringController unlock.
|
||||||
|
* Notifies all connections that the extension is unlocked.
|
||||||
|
*/
|
||||||
|
_onUnlock () {
|
||||||
|
this.notifyAllConnections((origin) => {
|
||||||
|
return {
|
||||||
|
method: NOTIFICATION_NAMES.unlockStateChanged,
|
||||||
|
params: {
|
||||||
|
isUnlocked: true,
|
||||||
|
accounts: this.permissionsController.getAccounts(origin),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
this.emit('unlock')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle global lock, triggered by KeyringController lock.
|
||||||
|
* Notifies all connections that the extension is locked.
|
||||||
|
*/
|
||||||
|
_onLock () {
|
||||||
|
this.notifyAllConnections({
|
||||||
|
method: NOTIFICATION_NAMES.unlockStateChanged,
|
||||||
|
params: {
|
||||||
|
isUnlocked: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
this.emit('lock')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle memory state updates.
|
||||||
|
* - Ensure isClientOpenAndUnlocked is updated
|
||||||
|
* - Notifies all connections with the new provider network state
|
||||||
|
* - The external providers handle diffing the state
|
||||||
|
*/
|
||||||
|
_onStateUpdate (newState) {
|
||||||
|
this.isClientOpenAndUnlocked = newState.isUnlocked && this._isClientOpen
|
||||||
|
this.notifyAllConnections({
|
||||||
|
method: NOTIFICATION_NAMES.chainChanged,
|
||||||
|
params: this.getProviderNetworkState(newState),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// misc
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A method for emitting the full MetaMask state to all registered listeners.
|
* A method for emitting the full MetaMask state to all registered listeners.
|
||||||
* @private
|
* @private
|
||||||
|
|
|
@ -7,7 +7,7 @@ This migration updates "transaction state history" to diffs style
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const clone = require('clone')
|
const clone = require('clone')
|
||||||
import txStateHistoryHelper from '../controllers/transactions/lib/tx-state-history-helper'
|
import { migrateFromSnapshotsToDiffs, snapshotFromTxMeta } from '../controllers/transactions/lib/tx-state-history-helpers'
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -35,13 +35,13 @@ function transformState (state) {
|
||||||
newState.TransactionController.transactions = transactions.map((txMeta) => {
|
newState.TransactionController.transactions = transactions.map((txMeta) => {
|
||||||
// no history: initialize
|
// no history: initialize
|
||||||
if (!txMeta.history || txMeta.history.length === 0) {
|
if (!txMeta.history || txMeta.history.length === 0) {
|
||||||
const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta)
|
const snapshot = snapshotFromTxMeta(txMeta)
|
||||||
txMeta.history = [snapshot]
|
txMeta.history = [snapshot]
|
||||||
return txMeta
|
return txMeta
|
||||||
}
|
}
|
||||||
// has history: migrate
|
// has history: migrate
|
||||||
const newHistory = (
|
const newHistory = (
|
||||||
txStateHistoryHelper.migrateFromSnapshotsToDiffs(txMeta.history)
|
migrateFromSnapshotsToDiffs(txMeta.history)
|
||||||
// remove empty diffs
|
// remove empty diffs
|
||||||
.filter((entry) => {
|
.filter((entry) => {
|
||||||
return !Array.isArray(entry) || entry.length > 0
|
return !Array.isArray(entry) || entry.length > 0
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const EventEmitter = require('events').EventEmitter
|
const EventEmitter = require('events').EventEmitter
|
||||||
const semver = require('semver')
|
const semver = require('semver')
|
||||||
const extend = require('xtend')
|
const extend = require('xtend')
|
||||||
const ObservableStore = require('obs-store')
|
import { ObservableStore } from '@metamask/obs-store'
|
||||||
const hardCodedNotices = require('../../notices/notices.js')
|
const hardCodedNotices = require('../../notices/notices.js')
|
||||||
const uniqBy = require('lodash.uniqby')
|
const uniqBy = require('lodash.uniqby')
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,11 @@ window.onload = function () {
|
||||||
const querystring = require('querystring')
|
const querystring = require('querystring')
|
||||||
const dnode = require('dnode')
|
const dnode = require('dnode')
|
||||||
const { EventEmitter } = require('events')
|
const { EventEmitter } = require('events')
|
||||||
const PortStream = require('extension-port-stream')
|
import PortStream from 'extension-port-stream'
|
||||||
const extension = require('extensionizer')
|
const extension = require('extensionizer')
|
||||||
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
|
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
|
||||||
const { getEnvironmentType } = require('./lib/util')
|
const { getEnvironmentType } = require('./lib/util')
|
||||||
const ExtensionPlatform = require('./platforms/extension')
|
import ExtensionPlatform from './platforms/extension'
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', start)
|
document.addEventListener('DOMContentLoaded', start)
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import extension from 'extensionizer'
|
import extension from 'extensionizer'
|
||||||
const explorerLinks = require('eth-net-props').explorerLinks
|
const explorerLinks = require('eth-net-props').explorerLinks
|
||||||
const { capitalizeFirstLetter, getEnvironmentType, checkForError } = require('../lib/util')
|
import { getEnvironmentType, checkForError } from '../lib/util'
|
||||||
const { ENVIRONMENT_TYPE_BACKGROUND } = require('../lib/enums')
|
import { ENVIRONMENT_TYPE_BACKGROUND } from '../lib/enums'
|
||||||
|
import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction'
|
||||||
class ExtensionPlatform {
|
|
||||||
|
|
||||||
|
export default class ExtensionPlatform {
|
||||||
//
|
//
|
||||||
// Public
|
// Public
|
||||||
//
|
//
|
||||||
|
@ -36,9 +36,9 @@ class ExtensionPlatform {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
closeWindow (windowId) {
|
focusWindow (windowId) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
extension.windows.remove(windowId, () => {
|
extension.windows.update(windowId, { focused: true }, () => {
|
||||||
const error = checkForError()
|
const error = checkForError()
|
||||||
if (error) {
|
if (error) {
|
||||||
return reject(error)
|
return reject(error)
|
||||||
|
@ -48,9 +48,9 @@ class ExtensionPlatform {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
focusWindow (windowId) {
|
updateWindowPosition (windowId, left, top) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
extension.windows.update(windowId, { focused: true }, () => {
|
extension.windows.update(windowId, { left, top }, () => {
|
||||||
const error = checkForError()
|
const error = checkForError()
|
||||||
if (error) {
|
if (error) {
|
||||||
return reject(error)
|
return reject(error)
|
||||||
|
@ -105,18 +105,22 @@ class ExtensionPlatform {
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
cb(e)
|
cb(e)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showTransactionNotification (txMeta) {
|
showTransactionNotification (txMeta) {
|
||||||
const { status, txReceipt: { status: receiptStatus } = {} } = txMeta
|
const { status, txReceipt: { status: receiptStatus } = {} } = txMeta
|
||||||
|
|
||||||
if (status === 'confirmed') {
|
if (status === TRANSACTION_STATUSES.CONFIRMED) {
|
||||||
// There was an on-chain failure
|
// There was an on-chain failure
|
||||||
receiptStatus === '0x0'
|
receiptStatus === '0x0'
|
||||||
? this._showFailedTransaction(txMeta, 'Transaction encountered an error.')
|
? this._showFailedTransaction(
|
||||||
|
txMeta,
|
||||||
|
'Transaction encountered an error.',
|
||||||
|
)
|
||||||
: this._showConfirmedTransaction(txMeta)
|
: this._showConfirmedTransaction(txMeta)
|
||||||
} else if (status === 'failed') {
|
} else if (status === TRANSACTION_STATUSES.FAILED) {
|
||||||
this._showFailedTransaction(txMeta)
|
this._showFailedTransaction(txMeta)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -185,7 +189,6 @@ class ExtensionPlatform {
|
||||||
}
|
}
|
||||||
|
|
||||||
_showConfirmedTransaction (txMeta) {
|
_showConfirmedTransaction (txMeta) {
|
||||||
|
|
||||||
this._subscribeToNotificationClicked()
|
this._subscribeToNotificationClicked()
|
||||||
|
|
||||||
const { url, explorerName } = this._getExplorer(txMeta.hash, parseInt(txMeta.metamaskNetworkId))
|
const { url, explorerName } = this._getExplorer(txMeta.hash, parseInt(txMeta.metamaskNetworkId))
|
||||||
|
@ -197,30 +200,28 @@ class ExtensionPlatform {
|
||||||
}
|
}
|
||||||
|
|
||||||
_showFailedTransaction (txMeta, errorMessage) {
|
_showFailedTransaction (txMeta, errorMessage) {
|
||||||
|
|
||||||
const nonce = parseInt(txMeta.txParams.nonce, 16)
|
const nonce = parseInt(txMeta.txParams.nonce, 16)
|
||||||
const title = 'Failed transaction'
|
const title = 'Failed transaction'
|
||||||
const message = `Transaction ${nonce} failed! ${errorMessage || capitalizeFirstLetter(txMeta.err.message)}`
|
const message = `Transaction ${nonce} failed! ${
|
||||||
|
errorMessage || txMeta.err.message
|
||||||
|
}`
|
||||||
this._showNotification(title, message)
|
this._showNotification(title, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
_showNotification (title, message, url) {
|
_showNotification (title, message, url) {
|
||||||
extension.notifications.create(
|
extension.notifications.create(url, {
|
||||||
url,
|
type: 'basic',
|
||||||
{
|
title,
|
||||||
'type': 'basic',
|
iconUrl: extension.extension.getURL('../../images/icon-64.png'),
|
||||||
'title': title,
|
message,
|
||||||
'iconUrl': extension.extension.getURL('../../images/icon-64.png'),
|
|
||||||
'message': message,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
_subscribeToNotificationClicked () {
|
_subscribeToNotificationClicked () {
|
||||||
if (!extension.notifications.onClicked.hasListener(this._viewOnExplorer)) {
|
if (!extension.notifications.onClicked.hasListener(this._viewOnExplorer)) {
|
||||||
extension.notifications.onClicked.removeListener(this._viewOnExplorer)
|
|
||||||
}
|
|
||||||
extension.notifications.onClicked.addListener(this._viewOnExplorer)
|
extension.notifications.onClicked.addListener(this._viewOnExplorer)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_viewOnExplorer (url) {
|
_viewOnExplorer (url) {
|
||||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||||
|
@ -237,5 +238,3 @@ class ExtensionPlatform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ExtensionPlatform
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
const injectCss = require('inject-css')
|
const injectCss = require('inject-css')
|
||||||
const OldMetaMaskUiCss = require('../../old-ui/css')
|
const OldMetaMaskUiCss = require('../../old-ui/css')
|
||||||
const startPopup = require('./popup-core')
|
const startPopup = require('./popup-core')
|
||||||
const PortStream = require('extension-port-stream')
|
import PortStream from 'extension-port-stream'
|
||||||
const { getEnvironmentType } = require('./lib/util')
|
const { getEnvironmentType } = require('./lib/util')
|
||||||
import extension from 'extensionizer'
|
import extension from 'extensionizer'
|
||||||
const ExtensionPlatform = require('./platforms/extension')
|
import ExtensionPlatform from './platforms/extension'
|
||||||
const setupRaven = require('./lib/setupRaven')
|
const setupRaven = require('./lib/setupRaven')
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ async function start () {
|
||||||
if (err) return displayCriticalError(err)
|
if (err) return displayCriticalError(err)
|
||||||
|
|
||||||
// Code commented out until we begin auto adding users to NewUI
|
// Code commented out until we begin auto adding users to NewUI
|
||||||
// const { isMascara, identities = {}, featureFlags = {} } = store.getState().metamask
|
// const { identities = {}, featureFlags = {} } = store.getState().metamask
|
||||||
// const firstTime = Object.keys(identities).length === 0
|
// const firstTime = Object.keys(identities).length === 0
|
||||||
|
|
||||||
// Code commented out until we begin auto adding users to NewUI
|
// Code commented out until we begin auto adding users to NewUI
|
||||||
|
|
|
@ -24,7 +24,6 @@ async function start () {
|
||||||
const SHORT_SHA1 = CIRCLE_SHA1.slice(0, 7)
|
const SHORT_SHA1 = CIRCLE_SHA1.slice(0, 7)
|
||||||
const BUILD_LINK_BASE = `https://${CIRCLE_BUILD_NUM}-42009758-gh.circle-artifacts.com/0`
|
const BUILD_LINK_BASE = `https://${CIRCLE_BUILD_NUM}-42009758-gh.circle-artifacts.com/0`
|
||||||
|
|
||||||
const MASCARA = `${BUILD_LINK_BASE}/builds/mascara/home.html`
|
|
||||||
const CHROME = `${BUILD_LINK_BASE}/builds/niftywallet-chrome-${VERSION}.zip`
|
const CHROME = `${BUILD_LINK_BASE}/builds/niftywallet-chrome-${VERSION}.zip`
|
||||||
const FIREFOX = `${BUILD_LINK_BASE}/builds/niftywallet-firefox-${VERSION}.zip`
|
const FIREFOX = `${BUILD_LINK_BASE}/builds/niftywallet-firefox-${VERSION}.zip`
|
||||||
const EDGE = `${BUILD_LINK_BASE}/builds/niftywallet-edge-${VERSION}.zip`
|
const EDGE = `${BUILD_LINK_BASE}/builds/niftywallet-edge-${VERSION}.zip`
|
||||||
|
@ -35,7 +34,6 @@ async function start () {
|
||||||
<details>
|
<details>
|
||||||
<summary>
|
<summary>
|
||||||
Builds ready [${SHORT_SHA1}]:
|
Builds ready [${SHORT_SHA1}]:
|
||||||
<a href="${MASCARA}">mascara</a>,
|
|
||||||
<a href="${CHROME}">chrome</a>,
|
<a href="${CHROME}">chrome</a>,
|
||||||
<a href="${FIREFOX}">firefox</a>,
|
<a href="${FIREFOX}">firefox</a>,
|
||||||
<a href="${EDGE}">edge</a>,
|
<a href="${EDGE}">edge</a>,
|
||||||
|
|
|
@ -22,7 +22,7 @@ const backGroundConnectionModifiers = require('./backGroundConnectionModifiers')
|
||||||
const Selector = require('./selector')
|
const Selector = require('./selector')
|
||||||
const MetamaskController = require('../app/scripts/metamask-controller')
|
const MetamaskController = require('../app/scripts/metamask-controller')
|
||||||
const firstTimeState = require('../app/scripts/first-time-state')
|
const firstTimeState = require('../app/scripts/first-time-state')
|
||||||
const ExtensionPlatform = require('../app/scripts/platforms/extension')
|
import ExtensionPlatform from '../app/scripts/platforms/extension'
|
||||||
const noop = function () {}
|
const noop = function () {}
|
||||||
|
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
"metamask": {
|
"metamask": {
|
||||||
"isInitialized": true,
|
"isInitialized": true,
|
||||||
"isUnlocked": true,
|
"isUnlocked": true,
|
||||||
"isMascara": false,
|
|
||||||
"rpcTarget": "https://rawtestrpc.metamask.io/",
|
"rpcTarget": "https://rawtestrpc.metamask.io/",
|
||||||
"identities": {
|
"identities": {
|
||||||
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
|
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825": {
|
||||||
|
|
57
gulpfile.js
57
gulpfile.js
|
@ -31,6 +31,7 @@ function gulpParallel (...args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const conf = require('rc')('niftywallet', {
|
const conf = require('rc')('niftywallet', {
|
||||||
|
INFURA_PROJECT_ID: process.env.INFURA_PROJECT_ID,
|
||||||
ETH_MAINNET_RPC_ENDPOINT: process.env.ETH_MAINNET_RPC_ENDPOINT,
|
ETH_MAINNET_RPC_ENDPOINT: process.env.ETH_MAINNET_RPC_ENDPOINT,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -41,8 +42,6 @@ const browserPlatforms = [
|
||||||
'opera',
|
'opera',
|
||||||
]
|
]
|
||||||
const commonPlatforms = [
|
const commonPlatforms = [
|
||||||
// browser webapp
|
|
||||||
'mascara',
|
|
||||||
// browser extensions
|
// browser extensions
|
||||||
...browserPlatforms,
|
...browserPlatforms,
|
||||||
]
|
]
|
||||||
|
@ -69,7 +68,7 @@ createCopyTasks('images', {
|
||||||
destinations: commonPlatforms.map(platform => `./dist/${platform}/images`),
|
destinations: commonPlatforms.map(platform => `./dist/${platform}/images`),
|
||||||
})
|
})
|
||||||
createCopyTasks('contractImages', {
|
createCopyTasks('contractImages', {
|
||||||
source: './node_modules/eth-contract-metadata/images/',
|
source: './node_modules/@metamask/contract-metadata/images/',
|
||||||
destinations: commonPlatforms.map(platform => `./dist/${platform}/images/contract`),
|
destinations: commonPlatforms.map(platform => `./dist/${platform}/images/contract`),
|
||||||
})
|
})
|
||||||
createCopyTasks('contractImagesPOA', {
|
createCopyTasks('contractImagesPOA', {
|
||||||
|
@ -112,14 +111,6 @@ createCopyTasks('manifest', {
|
||||||
destinations: browserPlatforms.map(platform => `./dist/${platform}`),
|
destinations: browserPlatforms.map(platform => `./dist/${platform}`),
|
||||||
})
|
})
|
||||||
|
|
||||||
// copy mascara
|
|
||||||
|
|
||||||
createCopyTasks('html:mascara', {
|
|
||||||
source: './mascara/',
|
|
||||||
pattern: 'proxy/index.html',
|
|
||||||
destinations: [`./dist/mascara/`],
|
|
||||||
})
|
|
||||||
|
|
||||||
function createCopyTasks (label, opts) {
|
function createCopyTasks (label, opts) {
|
||||||
if (!opts.devOnly) {
|
if (!opts.devOnly) {
|
||||||
const copyTaskName = `copy:${label}`
|
const copyTaskName = `copy:${label}`
|
||||||
|
@ -254,8 +245,6 @@ const buildJsFiles = [
|
||||||
// bundle tasks
|
// bundle tasks
|
||||||
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'dev:extension:js', devMode: true })
|
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'dev:extension:js', devMode: true })
|
||||||
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'build:extension:js' })
|
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'build:extension:js' })
|
||||||
createTasksForBuildJsMascara({ taskPrefix: 'build:mascara:js' })
|
|
||||||
createTasksForBuildJsMascara({ taskPrefix: 'dev:mascara:js', devMode: true })
|
|
||||||
|
|
||||||
function createTasksForBuildJsExtension ({ buildJsFiles, taskPrefix, devMode, bundleTaskOpts = {} }) {
|
function createTasksForBuildJsExtension ({ buildJsFiles, taskPrefix, devMode, bundleTaskOpts = {} }) {
|
||||||
// inpage must be built before all other scripts:
|
// inpage must be built before all other scripts:
|
||||||
|
@ -275,22 +264,6 @@ function createTasksForBuildJsExtension ({ buildJsFiles, taskPrefix, devMode, bu
|
||||||
createTasksForBuildJs({ rootDir, taskPrefix, bundleTaskOpts, destinations, buildPhase1, buildPhase2 })
|
createTasksForBuildJs({ rootDir, taskPrefix, bundleTaskOpts, destinations, buildPhase1, buildPhase2 })
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTasksForBuildJsMascara ({ taskPrefix, devMode, bundleTaskOpts = {} }) {
|
|
||||||
// inpage must be built before all other scripts:
|
|
||||||
const rootDir = './mascara/src/'
|
|
||||||
const buildPhase1 = ['ui', 'proxy', 'background', 'metamascara']
|
|
||||||
const destinations = ['./dist/mascara']
|
|
||||||
bundleTaskOpts = Object.assign({
|
|
||||||
buildSourceMaps: true,
|
|
||||||
sourceMapDir: './',
|
|
||||||
minifyBuild: false,
|
|
||||||
buildWithFullPaths: devMode,
|
|
||||||
watch: devMode,
|
|
||||||
devMode,
|
|
||||||
}, bundleTaskOpts)
|
|
||||||
createTasksForBuildJs({ rootDir, taskPrefix, bundleTaskOpts, destinations, buildPhase1 })
|
|
||||||
}
|
|
||||||
|
|
||||||
function createTasksForBuildJs ({ rootDir, taskPrefix, bundleTaskOpts, destinations, buildPhase1 = [], buildPhase2 = [] }) {
|
function createTasksForBuildJs ({ rootDir, taskPrefix, bundleTaskOpts, destinations, buildPhase1 = [], buildPhase2 = [] }) {
|
||||||
// bundle task for each file
|
// bundle task for each file
|
||||||
const jsFiles = [].concat(buildPhase1, buildPhase2)
|
const jsFiles = [].concat(buildPhase1, buildPhase2)
|
||||||
|
@ -338,7 +311,6 @@ gulp.task('dev',
|
||||||
'clean',
|
'clean',
|
||||||
gulp.parallel(
|
gulp.parallel(
|
||||||
'dev:extension:js',
|
'dev:extension:js',
|
||||||
'dev:mascara:js',
|
|
||||||
'dev:copy',
|
'dev:copy',
|
||||||
'dev:reload',
|
'dev:reload',
|
||||||
),
|
),
|
||||||
|
@ -356,23 +328,11 @@ gulp.task('dev:extension',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
gulp.task('dev:mascara',
|
|
||||||
gulp.series(
|
|
||||||
'clean',
|
|
||||||
gulp.parallel(
|
|
||||||
'dev:mascara:js',
|
|
||||||
'dev:copy',
|
|
||||||
'dev:reload',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
gulp.task('build',
|
gulp.task('build',
|
||||||
gulp.series(
|
gulp.series(
|
||||||
'clean',
|
'clean',
|
||||||
gulpParallel(
|
gulpParallel(
|
||||||
'build:extension:js',
|
'build:extension:js',
|
||||||
'build:mascara:js',
|
|
||||||
'copy',
|
'copy',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -388,16 +348,6 @@ gulp.task('build:extension',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
gulp.task('build:mascara',
|
|
||||||
gulp.series(
|
|
||||||
'clean',
|
|
||||||
gulp.parallel(
|
|
||||||
'build:mascara:js',
|
|
||||||
'copy',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
gulp.task('dist',
|
gulp.task('dist',
|
||||||
gulp.series(
|
gulp.series(
|
||||||
'build',
|
'build',
|
||||||
|
@ -430,6 +380,7 @@ function generateBundler (opts, performBundle) {
|
||||||
METAMASK_DEBUG: opts.devMode,
|
METAMASK_DEBUG: opts.devMode,
|
||||||
NODE_ENV: opts.devMode ? 'development' : 'production',
|
NODE_ENV: opts.devMode ? 'development' : 'production',
|
||||||
ETH_MAINNET_RPC_ENDPOINT: conf.ETH_MAINNET_RPC_ENDPOINT,
|
ETH_MAINNET_RPC_ENDPOINT: conf.ETH_MAINNET_RPC_ENDPOINT,
|
||||||
|
INFURA_PROJECT_ID: conf.INFURA_PROJECT_ID,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
if (opts.watch) {
|
if (opts.watch) {
|
||||||
|
@ -510,7 +461,7 @@ function bundleTask (opts) {
|
||||||
buildStream = buildStream
|
buildStream = buildStream
|
||||||
.pipe(uglify({
|
.pipe(uglify({
|
||||||
mangle: {
|
mangle: {
|
||||||
reserved: [ 'MetamaskInpageProvider' ],
|
reserved: [ 'MetaMaskInpageProvider' ],
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
start the dual servers (dapp + mascara)
|
|
||||||
```
|
|
||||||
npm run mascara
|
|
||||||
```
|
|
||||||
|
|
||||||
### First time use:
|
|
||||||
|
|
||||||
- navigate to: http://localhost:9001
|
|
||||||
- Create an Account
|
|
||||||
- go back to http://localhost:9002
|
|
||||||
- open devTools
|
|
||||||
- click Sync Tx
|
|
||||||
|
|
||||||
### Tests:
|
|
||||||
|
|
||||||
```
|
|
||||||
npm run testMascara
|
|
||||||
```
|
|
||||||
|
|
||||||
Test will run in browser, you will have to have these browsers installed:
|
|
||||||
|
|
||||||
- Chrome
|
|
||||||
- Firefox
|
|
||||||
- Opera
|
|
||||||
|
|
||||||
|
|
||||||
### Deploy:
|
|
||||||
|
|
||||||
Will build and deploy mascara via docker
|
|
||||||
|
|
||||||
```
|
|
||||||
docker-compose build && docker-compose stop && docker-compose up -d && docker-compose logs --tail 200 -f
|
|
||||||
```
|
|
|
@ -1,38 +0,0 @@
|
||||||
const EthQuery = require('ethjs-query')
|
|
||||||
|
|
||||||
window.addEventListener('load', loadProvider)
|
|
||||||
window.addEventListener('message', console.warn)
|
|
||||||
|
|
||||||
async function loadProvider () {
|
|
||||||
const ethereumProvider = window.metamask.createDefaultProvider({ host: 'http://localhost:9001' })
|
|
||||||
const ethQuery = new EthQuery(ethereumProvider)
|
|
||||||
const accounts = await ethQuery.accounts()
|
|
||||||
window.METAMASK_ACCOUNT = accounts[0] || 'locked'
|
|
||||||
logToDom(accounts.length ? accounts[0] : 'LOCKED or undefined', 'account')
|
|
||||||
setupButtons(ethQuery)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function logToDom (message, context) {
|
|
||||||
document.getElementById(context).innerText = message
|
|
||||||
console.log(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupButtons (ethQuery) {
|
|
||||||
const accountButton = document.getElementById('action-button-1')
|
|
||||||
accountButton.addEventListener('click', async () => {
|
|
||||||
const accounts = await ethQuery.accounts()
|
|
||||||
window.METAMASK_ACCOUNT = accounts[0] || 'locked'
|
|
||||||
logToDom(accounts.length ? accounts[0] : 'LOCKED or undefined', 'account')
|
|
||||||
})
|
|
||||||
const txButton = document.getElementById('action-button-2')
|
|
||||||
txButton.addEventListener('click', async () => {
|
|
||||||
if (!window.METAMASK_ACCOUNT || window.METAMASK_ACCOUNT === 'locked') return
|
|
||||||
const txHash = await ethQuery.sendTransaction({
|
|
||||||
from: window.METAMASK_ACCOUNT,
|
|
||||||
to: window.METAMASK_ACCOUNT,
|
|
||||||
data: '',
|
|
||||||
})
|
|
||||||
logToDom(txHash, 'cb-value')
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<script src="http://localhost:9001/metamascara.js"></script>
|
|
||||||
<title>Nifty Wallet ZeroClient Example</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<button id="action-button-1">GET ACCOUNT</button>
|
|
||||||
<div id="account"></div>
|
|
||||||
<button id="action-button-2">SEND TRANSACTION</button>
|
|
||||||
<div id="cb-value" ></div>
|
|
||||||
<script src="./app.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,31 +0,0 @@
|
||||||
const express = require('express')
|
|
||||||
const path = require('path')
|
|
||||||
const createMetamascaraServer = require('../server/')
|
|
||||||
const createBundle = require('../server/util').createBundle
|
|
||||||
const serveBundle = require('../server/util').serveBundle
|
|
||||||
//
|
|
||||||
// Iframe Server
|
|
||||||
//
|
|
||||||
|
|
||||||
const mascaraServer = createMetamascaraServer()
|
|
||||||
|
|
||||||
// start the server
|
|
||||||
const mascaraPort = 9001
|
|
||||||
mascaraServer.listen(mascaraPort)
|
|
||||||
console.log(`Mascara service listening on port ${mascaraPort}`)
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// Dapp Server
|
|
||||||
//
|
|
||||||
|
|
||||||
const dappServer = express()
|
|
||||||
|
|
||||||
// serve dapp bundle
|
|
||||||
serveBundle(dappServer, '/app.js', createBundle(require.resolve('./app.js')))
|
|
||||||
dappServer.use(express.static(path.join(__dirname, '/app/')))
|
|
||||||
|
|
||||||
// start the server
|
|
||||||
const dappPort = '9002'
|
|
||||||
dappServer.listen(dappPort)
|
|
||||||
console.log(`Dapp listening on port ${dappPort}`)
|
|
|
@ -1,20 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
|
|
||||||
<title>Nifty Wallet ZeroClient Iframe</title>
|
|
||||||
<meta name="description" content="MetaMask ZeroClient">
|
|
||||||
<meta name="author" content="MetaMask">
|
|
||||||
|
|
||||||
<!--[if lt IE 9]>
|
|
||||||
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
||||||
<![endif]-->
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
Hello! I am the Nifty Wallet iframe.
|
|
||||||
<script src="./proxy.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,21 +0,0 @@
|
||||||
const path = require('path')
|
|
||||||
const express = require('express')
|
|
||||||
const compression = require('compression')
|
|
||||||
|
|
||||||
module.exports = createMetamascaraServer
|
|
||||||
|
|
||||||
|
|
||||||
function createMetamascaraServer () {
|
|
||||||
|
|
||||||
// setup server
|
|
||||||
const server = express()
|
|
||||||
server.use(compression())
|
|
||||||
|
|
||||||
// serve assets
|
|
||||||
server.use(express.static(path.join(__dirname, '/../ui/'), { setHeaders: (res) => res.set('X-Frame-Options', 'DENY') }))
|
|
||||||
server.use(express.static(path.join(__dirname, '/../../dist/mascara')))
|
|
||||||
server.use(express.static(path.join(__dirname, '/../proxy')))
|
|
||||||
|
|
||||||
return server
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
const browserify = require('browserify')
|
|
||||||
const watchify = require('watchify')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
serveBundle,
|
|
||||||
createBundle,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function serveBundle (server, path, bundle) {
|
|
||||||
server.get(path, function (req, res) {
|
|
||||||
res.setHeader('Content-Type', 'application/javascript; charset=UTF-8')
|
|
||||||
res.send(bundle.latest)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function createBundle (entryPoint) {
|
|
||||||
|
|
||||||
var bundleContainer = {}
|
|
||||||
|
|
||||||
var bundler = browserify({
|
|
||||||
entries: [entryPoint],
|
|
||||||
cache: {},
|
|
||||||
packageCache: {},
|
|
||||||
plugin: [watchify],
|
|
||||||
})
|
|
||||||
.transform('babelify')
|
|
||||||
.transform('uglifyify', { global: true })
|
|
||||||
|
|
||||||
bundler.on('update', bundle)
|
|
||||||
bundle()
|
|
||||||
|
|
||||||
return bundleContainer
|
|
||||||
|
|
||||||
function bundle () {
|
|
||||||
bundler.bundle(function (err, result) {
|
|
||||||
if (err) {
|
|
||||||
console.log(`Bundle failed! (${entryPoint})`)
|
|
||||||
console.error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
console.log(`Bundle updated! (${entryPoint})`)
|
|
||||||
bundleContainer.latest = result.toString()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,198 +0,0 @@
|
||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import classnames from 'classnames'
|
|
||||||
import {connect} from 'react-redux'
|
|
||||||
import {qrcode} from 'qrcode-npm'
|
|
||||||
import copyToClipboard from 'copy-to-clipboard'
|
|
||||||
import ShapeShiftForm from '../shapeshift-form'
|
|
||||||
import {buyEth, showAccountDetail} from '../../../../ui/app/actions'
|
|
||||||
|
|
||||||
const OPTION_VALUES = {
|
|
||||||
COINBASE: 'coinbase',
|
|
||||||
SHAPESHIFT: 'shapeshift',
|
|
||||||
QR_CODE: 'qr_code',
|
|
||||||
}
|
|
||||||
|
|
||||||
const OPTIONS = [
|
|
||||||
{
|
|
||||||
name: 'Direct Deposit',
|
|
||||||
value: OPTION_VALUES.QR_CODE,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Buy with Dollars',
|
|
||||||
value: OPTION_VALUES.COINBASE,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Buy with Cryptos',
|
|
||||||
value: OPTION_VALUES.SHAPESHIFT,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
class BuyEtherWidget extends Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
address: PropTypes.string,
|
|
||||||
skipText: PropTypes.string,
|
|
||||||
className: PropTypes.string,
|
|
||||||
onSkip: PropTypes.func,
|
|
||||||
goToCoinbase: PropTypes.func,
|
|
||||||
showAccountDetail: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
selectedOption: OPTION_VALUES.QR_CODE,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
copyToClipboard = () => {
|
|
||||||
const { address } = this.props
|
|
||||||
|
|
||||||
this.setState({ justCopied: true }, () => copyToClipboard(address))
|
|
||||||
|
|
||||||
setTimeout(() => this.setState({ justCopied: false }), 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderSkip () {
|
|
||||||
const {showAccountDetail, address, skipText, onSkip} = this.props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="buy-ether__do-it-later"
|
|
||||||
onClick={() => {
|
|
||||||
if (onSkip) return onSkip()
|
|
||||||
showAccountDetail(address)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{skipText || 'Do it later'}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCoinbaseLogo () {
|
|
||||||
return (
|
|
||||||
<svg width="140px" height="49px" viewBox="0 0 579 126" version="1.1">
|
|
||||||
<g id="Page-1" stroke="none" strokeWidth={1} fill="none" fillRule="evenodd">
|
|
||||||
<g id="Imported-Layers" fill="#0081C9">
|
|
||||||
<path d="M37.752,125.873 C18.824,125.873 0.369,112.307 0.369,81.549 C0.369,50.79 18.824,37.382 37.752,37.382 C47.059,37.382 54.315,39.749 59.52,43.219 L53.841,55.68 C50.371,53.156 45.166,51.579 39.961,51.579 C28.604,51.579 18.193,60.57 18.193,81.391 C18.193,102.212 28.919,111.361 39.961,111.361 C45.166,111.361 50.371,109.783 53.841,107.26 L59.52,120.036 C54.157,123.664 47.059,125.873 37.752,125.873" id="Fill-1" />
|
|
||||||
<path d="M102.898,125.873 C78.765,125.873 65.515,106.786 65.515,81.549 C65.515,56.311 78.765,37.382 102.898,37.382 C127.032,37.382 140.282,56.311 140.282,81.549 C140.282,106.786 127.032,125.873 102.898,125.873 L102.898,125.873 Z M102.898,51.105 C89.491,51.105 82.866,63.093 82.866,81.391 C82.866,99.688 89.491,111.834 102.898,111.834 C116.306,111.834 122.931,99.688 122.931,81.391 C122.931,63.093 116.306,51.105 102.898,51.105 L102.898,51.105 Z" id="Fill-2" />
|
|
||||||
<path d="M163.468,23.659 C157.79,23.659 153.215,19.243 153.215,13.88 C153.215,8.517 157.79,4.1 163.468,4.1 C169.146,4.1 173.721,8.517 173.721,13.88 C173.721,19.243 169.146,23.659 163.468,23.659 L163.468,23.659 Z M154.793,39.118 L172.144,39.118 L172.144,124.138 L154.793,124.138 L154.793,39.118 Z" id="Fill-3" />
|
|
||||||
<path d="M240.443,124.137 L240.443,67.352 C240.443,57.415 234.449,51.263 222.619,51.263 C216.31,51.263 210.473,52.367 207.003,53.787 L207.003,124.137 L189.81,124.137 L189.81,43.376 C198.328,39.906 209.212,37.382 222.461,37.382 C246.28,37.382 257.794,47.793 257.794,65.775 L257.794,124.137 L240.443,124.137" id="Fill-4" />
|
|
||||||
<path d="M303.536,125.873 C292.494,125.873 281.611,123.191 274.986,119.879 L274.986,0.314 L292.179,0.314 L292.179,41.326 C296.28,39.433 302.905,37.856 308.741,37.856 C330.667,37.856 345.494,53.629 345.494,79.656 C345.494,111.676 328.931,125.873 303.536,125.873 L303.536,125.873 Z M305.744,51.263 C301.012,51.263 295.491,52.367 292.179,54.103 L292.179,109.941 C294.703,111.045 299.593,112.149 304.482,112.149 C318.205,112.149 328.301,102.685 328.301,80.918 C328.301,62.305 319.467,51.263 305.744,51.263 L305.744,51.263 Z" id="Fill-5" />
|
|
||||||
<path d="M392.341,125.873 C367.892,125.873 355.589,115.935 355.589,99.215 C355.589,75.555 380.826,71.296 406.537,69.876 L406.537,64.513 C406.537,53.787 399.439,50.001 388.555,50.001 C380.511,50.001 370.731,52.525 365.053,55.207 L360.636,43.376 C367.419,40.379 378.933,37.382 390.29,37.382 C410.638,37.382 422.942,45.269 422.942,66.248 L422.942,119.879 C416.79,123.191 404.329,125.873 392.341,125.873 L392.341,125.873 Z M406.537,81.391 C389.186,82.337 371.835,83.757 371.835,98.9 C371.835,107.89 378.776,113.411 391.868,113.411 C397.389,113.411 403.856,112.465 406.537,111.203 L406.537,81.391 L406.537,81.391 Z" id="Fill-6" />
|
|
||||||
<path d="M461.743,125.873 C451.806,125.873 441.395,123.191 435.244,119.879 L441.08,106.629 C445.496,109.31 454.803,112.149 461.27,112.149 C470.576,112.149 476.728,107.575 476.728,100.477 C476.728,92.748 470.261,89.751 461.586,86.596 C450.228,82.337 437.452,77.132 437.452,61.201 C437.452,47.162 448.336,37.382 467.264,37.382 C477.517,37.382 486.035,39.906 492.029,43.376 L486.665,55.364 C482.88,52.998 475.309,50.317 469.157,50.317 C460.166,50.317 455.118,55.049 455.118,61.201 C455.118,68.93 461.428,71.611 469.788,74.766 C481.618,79.183 494.71,84.072 494.71,100.635 C494.71,115.935 483.038,125.873 461.743,125.873" id="Fill-7" />
|
|
||||||
<path d="M578.625,81.233 L522.155,89.12 C523.89,104.42 533.828,112.149 548.182,112.149 C556.699,112.149 565.848,110.099 571.684,106.944 L576.732,119.879 C570.107,123.349 558.75,125.873 547.078,125.873 C520.262,125.873 505.277,108.679 505.277,81.549 C505.277,55.522 519.789,37.382 543.607,37.382 C565.69,37.382 578.782,51.894 578.782,74.766 C578.782,76.816 578.782,79.025 578.625,81.233 L578.625,81.233 Z M543.292,50.001 C530.042,50.001 521.367,60.097 521.051,77.763 L562.22,72.084 C562.062,57.257 554.649,50.001 543.292,50.001 L543.292,50.001 Z" id="Fill-8" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCoinbaseForm () {
|
|
||||||
const {goToCoinbase, address} = this.props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="buy-ether__action-content-wrapper">
|
|
||||||
<div>{this.renderCoinbaseLogo()}</div>
|
|
||||||
<div className="buy-ether__body-text">Coinbase is the world’s most popular way to buy and sell bitcoin, ethereum, and litecoin.</div>
|
|
||||||
<a className="first-time-flow__link buy-ether__faq-link">What is Ethereum?</a>
|
|
||||||
<div className="buy-ether__buttons">
|
|
||||||
<button
|
|
||||||
className="first-time-flow__button"
|
|
||||||
onClick={() => goToCoinbase(address)}
|
|
||||||
>
|
|
||||||
Buy
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderContent () {
|
|
||||||
const { address } = this.props
|
|
||||||
const { justCopied } = this.state
|
|
||||||
const qrImage = qrcode(4, 'M')
|
|
||||||
qrImage.addData(address)
|
|
||||||
qrImage.make()
|
|
||||||
|
|
||||||
switch (this.state.selectedOption) {
|
|
||||||
case OPTION_VALUES.COINBASE:
|
|
||||||
return this.renderCoinbaseForm()
|
|
||||||
case OPTION_VALUES.SHAPESHIFT:
|
|
||||||
return (
|
|
||||||
<div className="buy-ether__action-content-wrapper">
|
|
||||||
<div className="shapeshift-logo" />
|
|
||||||
<div className="buy-ether__body-text">
|
|
||||||
Trade any leading blockchain asset for any other. Protection by Design. No Account Needed.
|
|
||||||
</div>
|
|
||||||
<ShapeShiftForm btnClass="first-time-flow__button" />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
case OPTION_VALUES.QR_CODE:
|
|
||||||
return (
|
|
||||||
<div className="buy-ether__action-content-wrapper">
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: qrImage.createTableTag(4) }} />
|
|
||||||
<div className="buy-ether__body-text">Deposit Ether directly into your account.</div>
|
|
||||||
<div className="buy-ether__small-body-text">(This is the account address that Nifty Wallet created for you to recieve funds.)</div>
|
|
||||||
<div className="buy-ether__buttons">
|
|
||||||
<button
|
|
||||||
className="first-time-flow__button"
|
|
||||||
onClick={this.copyToClipboard}
|
|
||||||
disabled={justCopied}
|
|
||||||
>
|
|
||||||
{ justCopied ? 'Copied' : 'Copy' }
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
default:
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { className = '' } = this.props
|
|
||||||
const { selectedOption } = this.state
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`${className} buy-ether__content-wrapper`}>
|
|
||||||
<div className="buy-ether__content-headline-wrapper">
|
|
||||||
<div className="buy-ether__content-headline">Deposit Options</div>
|
|
||||||
{this.renderSkip()}
|
|
||||||
</div>
|
|
||||||
<div className="buy-ether__content">
|
|
||||||
<div className="buy-ether__side-panel">
|
|
||||||
{OPTIONS.map(({ name, value }) => (
|
|
||||||
<div
|
|
||||||
key={value}
|
|
||||||
className={classnames('buy-ether__side-panel-item', {
|
|
||||||
'buy-ether__side-panel-item--selected': value === selectedOption,
|
|
||||||
})}
|
|
||||||
onClick={() => this.setState({ selectedOption: value })}
|
|
||||||
>
|
|
||||||
<div className="buy-ether__side-panel-item-name">{name}</div>
|
|
||||||
{value === selectedOption && (
|
|
||||||
<svg viewBox="0 0 574 1024" id="si-ant-right" width="15px" height="15px">
|
|
||||||
<path d="M10 9Q0 19 0 32t10 23l482 457L10 969Q0 979 0 992t10 23q10 9 24 9t24-9l506-480q10-10 10-23t-10-23L58 9Q48 0 34 0T10 9z" />
|
|
||||||
</svg>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="buy-ether__action-content">
|
|
||||||
{this.renderContent()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
({ metamask: { selectedAddress } }) => ({
|
|
||||||
address: selectedAddress,
|
|
||||||
}),
|
|
||||||
dispatch => ({
|
|
||||||
goToCoinbase: address => dispatch(buyEth({ network: '1', address, amount: 0 })),
|
|
||||||
showAccountDetail: address => dispatch(showAccountDetail(address)),
|
|
||||||
}),
|
|
||||||
)(BuyEtherWidget)
|
|
|
@ -1,26 +0,0 @@
|
||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
|
|
||||||
export default class Breadcrumbs extends Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
total: PropTypes.number,
|
|
||||||
currentIndex: PropTypes.number,
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const {total, currentIndex} = this.props
|
|
||||||
return (
|
|
||||||
<div className="breadcrumbs">
|
|
||||||
{Array(total).fill().map((_, i) => (
|
|
||||||
<div
|
|
||||||
key={i}
|
|
||||||
className="breadcrumb"
|
|
||||||
style={{backgroundColor: i === currentIndex ? '#D8D8D8' : '#FFFFFF'}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,200 +0,0 @@
|
||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import classnames from 'classnames'
|
|
||||||
import {connect} from 'react-redux'
|
|
||||||
import {qrcode} from 'qrcode-npm'
|
|
||||||
import copyToClipboard from 'copy-to-clipboard'
|
|
||||||
import ShapeShiftForm from '../shapeshift-form'
|
|
||||||
import Identicon from '../../../../ui/app/components/identicon'
|
|
||||||
import {buyEth, showAccountDetail} from '../../../../ui/app/actions'
|
|
||||||
|
|
||||||
class BuyEtherScreen extends Component {
|
|
||||||
static OPTION_VALUES = {
|
|
||||||
COINBASE: 'coinbase',
|
|
||||||
SHAPESHIFT: 'shapeshift',
|
|
||||||
QR_CODE: 'qr_code',
|
|
||||||
};
|
|
||||||
|
|
||||||
static OPTIONS = [
|
|
||||||
{
|
|
||||||
name: 'Direct Deposit',
|
|
||||||
value: BuyEtherScreen.OPTION_VALUES.QR_CODE,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Buy with Dollars',
|
|
||||||
value: BuyEtherScreen.OPTION_VALUES.COINBASE,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Buy with Cryptos',
|
|
||||||
value: BuyEtherScreen.OPTION_VALUES.SHAPESHIFT,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
address: PropTypes.string,
|
|
||||||
goToCoinbase: PropTypes.func.isRequired,
|
|
||||||
showAccountDetail: PropTypes.func.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
state = {
|
|
||||||
selectedOption: BuyEtherScreen.OPTION_VALUES.QR_CODE,
|
|
||||||
justCopied: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
copyToClipboard = () => {
|
|
||||||
const { address } = this.props
|
|
||||||
|
|
||||||
this.setState({ justCopied: true }, () => copyToClipboard(address))
|
|
||||||
|
|
||||||
setTimeout(() => this.setState({ justCopied: false }), 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderSkip () {
|
|
||||||
const {showAccountDetail, address} = this.props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="buy-ether__do-it-later"
|
|
||||||
onClick={() => showAccountDetail(address)}
|
|
||||||
>
|
|
||||||
Do it later
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCoinbaseLogo () {
|
|
||||||
return (
|
|
||||||
<svg width="140px" height="49px" viewBox="0 0 579 126" version="1.1">
|
|
||||||
<g id="Page-1" stroke="none" strokeWidth={1} fill="none" fillRule="evenodd">
|
|
||||||
<g id="Imported-Layers" fill="#0081C9">
|
|
||||||
<path d="M37.752,125.873 C18.824,125.873 0.369,112.307 0.369,81.549 C0.369,50.79 18.824,37.382 37.752,37.382 C47.059,37.382 54.315,39.749 59.52,43.219 L53.841,55.68 C50.371,53.156 45.166,51.579 39.961,51.579 C28.604,51.579 18.193,60.57 18.193,81.391 C18.193,102.212 28.919,111.361 39.961,111.361 C45.166,111.361 50.371,109.783 53.841,107.26 L59.52,120.036 C54.157,123.664 47.059,125.873 37.752,125.873" id="Fill-1" />
|
|
||||||
<path d="M102.898,125.873 C78.765,125.873 65.515,106.786 65.515,81.549 C65.515,56.311 78.765,37.382 102.898,37.382 C127.032,37.382 140.282,56.311 140.282,81.549 C140.282,106.786 127.032,125.873 102.898,125.873 L102.898,125.873 Z M102.898,51.105 C89.491,51.105 82.866,63.093 82.866,81.391 C82.866,99.688 89.491,111.834 102.898,111.834 C116.306,111.834 122.931,99.688 122.931,81.391 C122.931,63.093 116.306,51.105 102.898,51.105 L102.898,51.105 Z" id="Fill-2" />
|
|
||||||
<path d="M163.468,23.659 C157.79,23.659 153.215,19.243 153.215,13.88 C153.215,8.517 157.79,4.1 163.468,4.1 C169.146,4.1 173.721,8.517 173.721,13.88 C173.721,19.243 169.146,23.659 163.468,23.659 L163.468,23.659 Z M154.793,39.118 L172.144,39.118 L172.144,124.138 L154.793,124.138 L154.793,39.118 Z" id="Fill-3" />
|
|
||||||
<path d="M240.443,124.137 L240.443,67.352 C240.443,57.415 234.449,51.263 222.619,51.263 C216.31,51.263 210.473,52.367 207.003,53.787 L207.003,124.137 L189.81,124.137 L189.81,43.376 C198.328,39.906 209.212,37.382 222.461,37.382 C246.28,37.382 257.794,47.793 257.794,65.775 L257.794,124.137 L240.443,124.137" id="Fill-4" />
|
|
||||||
<path d="M303.536,125.873 C292.494,125.873 281.611,123.191 274.986,119.879 L274.986,0.314 L292.179,0.314 L292.179,41.326 C296.28,39.433 302.905,37.856 308.741,37.856 C330.667,37.856 345.494,53.629 345.494,79.656 C345.494,111.676 328.931,125.873 303.536,125.873 L303.536,125.873 Z M305.744,51.263 C301.012,51.263 295.491,52.367 292.179,54.103 L292.179,109.941 C294.703,111.045 299.593,112.149 304.482,112.149 C318.205,112.149 328.301,102.685 328.301,80.918 C328.301,62.305 319.467,51.263 305.744,51.263 L305.744,51.263 Z" id="Fill-5" />
|
|
||||||
<path d="M392.341,125.873 C367.892,125.873 355.589,115.935 355.589,99.215 C355.589,75.555 380.826,71.296 406.537,69.876 L406.537,64.513 C406.537,53.787 399.439,50.001 388.555,50.001 C380.511,50.001 370.731,52.525 365.053,55.207 L360.636,43.376 C367.419,40.379 378.933,37.382 390.29,37.382 C410.638,37.382 422.942,45.269 422.942,66.248 L422.942,119.879 C416.79,123.191 404.329,125.873 392.341,125.873 L392.341,125.873 Z M406.537,81.391 C389.186,82.337 371.835,83.757 371.835,98.9 C371.835,107.89 378.776,113.411 391.868,113.411 C397.389,113.411 403.856,112.465 406.537,111.203 L406.537,81.391 L406.537,81.391 Z" id="Fill-6" />
|
|
||||||
<path d="M461.743,125.873 C451.806,125.873 441.395,123.191 435.244,119.879 L441.08,106.629 C445.496,109.31 454.803,112.149 461.27,112.149 C470.576,112.149 476.728,107.575 476.728,100.477 C476.728,92.748 470.261,89.751 461.586,86.596 C450.228,82.337 437.452,77.132 437.452,61.201 C437.452,47.162 448.336,37.382 467.264,37.382 C477.517,37.382 486.035,39.906 492.029,43.376 L486.665,55.364 C482.88,52.998 475.309,50.317 469.157,50.317 C460.166,50.317 455.118,55.049 455.118,61.201 C455.118,68.93 461.428,71.611 469.788,74.766 C481.618,79.183 494.71,84.072 494.71,100.635 C494.71,115.935 483.038,125.873 461.743,125.873" id="Fill-7" />
|
|
||||||
<path d="M578.625,81.233 L522.155,89.12 C523.89,104.42 533.828,112.149 548.182,112.149 C556.699,112.149 565.848,110.099 571.684,106.944 L576.732,119.879 C570.107,123.349 558.75,125.873 547.078,125.873 C520.262,125.873 505.277,108.679 505.277,81.549 C505.277,55.522 519.789,37.382 543.607,37.382 C565.69,37.382 578.782,51.894 578.782,74.766 C578.782,76.816 578.782,79.025 578.625,81.233 L578.625,81.233 Z M543.292,50.001 C530.042,50.001 521.367,60.097 521.051,77.763 L562.22,72.084 C562.062,57.257 554.649,50.001 543.292,50.001 L543.292,50.001 Z" id="Fill-8" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCoinbaseForm () {
|
|
||||||
const {goToCoinbase, address} = this.props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="buy-ether__action-content-wrapper">
|
|
||||||
<div>{this.renderCoinbaseLogo()}</div>
|
|
||||||
<div className="buy-ether__body-text">Coinbase is the world’s most popular way to buy and sell bitcoin, ethereum, and litecoin.</div>
|
|
||||||
<a className="first-time-flow__link buy-ether__faq-link">What is Ethereum?</a>
|
|
||||||
<div className="buy-ether__buttons">
|
|
||||||
<button
|
|
||||||
className="first-time-flow__button"
|
|
||||||
onClick={() => goToCoinbase(address)}
|
|
||||||
>
|
|
||||||
Buy
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderContent () {
|
|
||||||
const { OPTION_VALUES } = BuyEtherScreen
|
|
||||||
const { address } = this.props
|
|
||||||
const { justCopied } = this.state
|
|
||||||
const qrImage = qrcode(4, 'M')
|
|
||||||
qrImage.addData(address)
|
|
||||||
qrImage.make()
|
|
||||||
|
|
||||||
switch (this.state.selectedOption) {
|
|
||||||
case OPTION_VALUES.COINBASE:
|
|
||||||
return this.renderCoinbaseForm()
|
|
||||||
case OPTION_VALUES.SHAPESHIFT:
|
|
||||||
return (
|
|
||||||
<div className="buy-ether__action-content-wrapper">
|
|
||||||
<div className="shapeshift-logo" />
|
|
||||||
<div className="buy-ether__body-text">
|
|
||||||
Trade any leading blockchain asset for any other. Protection by Design. No Account Needed.
|
|
||||||
</div>
|
|
||||||
<ShapeShiftForm btnClass="first-time-flow__button" />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
case OPTION_VALUES.QR_CODE:
|
|
||||||
return (
|
|
||||||
<div className="buy-ether__action-content-wrapper">
|
|
||||||
<div dangerouslySetInnerHTML={{ __html: qrImage.createTableTag(4) }} />
|
|
||||||
<div className="buy-ether__body-text">Deposit Ether directly into your account.</div>
|
|
||||||
<div className="buy-ether__small-body-text">(This is the account address that Nifty Wallet created for you to recieve funds.)</div>
|
|
||||||
<div className="buy-ether__buttons">
|
|
||||||
<button
|
|
||||||
className="first-time-flow__button"
|
|
||||||
onClick={this.copyToClipboard}
|
|
||||||
disabled={justCopied}
|
|
||||||
>
|
|
||||||
{ justCopied ? 'Copied' : 'Copy' }
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
default:
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { OPTIONS } = BuyEtherScreen
|
|
||||||
const { selectedOption } = this.state
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="buy-ether">
|
|
||||||
<Identicon address={this.props.address} diameter={70} />
|
|
||||||
<div className="buy-ether__title">Deposit Ether</div>
|
|
||||||
<div className="buy-ether__body-text">
|
|
||||||
Nifty Wallet works best if you have Ether in your account to pay for transaction gas fees and more. To get Ether, choose from one of these methods.
|
|
||||||
</div>
|
|
||||||
<div className="buy-ether__content-wrapper">
|
|
||||||
<div className="buy-ether__content-headline-wrapper">
|
|
||||||
<div className="buy-ether__content-headline">Deposit Options</div>
|
|
||||||
{this.renderSkip()}
|
|
||||||
</div>
|
|
||||||
<div className="buy-ether__content">
|
|
||||||
<div className="buy-ether__side-panel">
|
|
||||||
{OPTIONS.map(({ name, value }) => (
|
|
||||||
<div
|
|
||||||
key={value}
|
|
||||||
className={classnames('buy-ether__side-panel-item', {
|
|
||||||
'buy-ether__side-panel-item--selected': value === selectedOption,
|
|
||||||
})}
|
|
||||||
onClick={() => this.setState({ selectedOption: value })}
|
|
||||||
>
|
|
||||||
<div className="buy-ether__side-panel-item-name">{name}</div>
|
|
||||||
{value === selectedOption && (
|
|
||||||
<svg viewBox="0 0 574 1024" id="si-ant-right" width="15px" height="15px">
|
|
||||||
<path d="M10 9Q0 19 0 32t10 23l482 457L10 969Q0 979 0 992t10 23q10 9 24 9t24-9l506-480q10-10 10-23t-10-23L58 9Q48 0 34 0T10 9z" />
|
|
||||||
</svg>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="buy-ether__action-content">
|
|
||||||
{this.renderContent()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
({ metamask: { selectedAddress } }) => ({
|
|
||||||
address: selectedAddress,
|
|
||||||
}),
|
|
||||||
dispatch => ({
|
|
||||||
goToCoinbase: address => dispatch(buyEth({ network: '1', address, amount: 0 })),
|
|
||||||
showAccountDetail: address => dispatch(showAccountDetail(address)),
|
|
||||||
}),
|
|
||||||
)(BuyEtherScreen)
|
|
|
@ -1,163 +0,0 @@
|
||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { connect } from 'react-redux'
|
|
||||||
import { withRouter } from 'react-router-dom'
|
|
||||||
import classnames from 'classnames'
|
|
||||||
import shuffle from 'lodash.shuffle'
|
|
||||||
import { compose } from 'recompose'
|
|
||||||
import Identicon from '../../../../ui/app/components/identicon'
|
|
||||||
import { confirmSeedWords, showModal } from '../../../../ui/app/actions'
|
|
||||||
import Breadcrumbs from './breadcrumbs'
|
|
||||||
import LoadingScreen from './loading-screen'
|
|
||||||
import { DEFAULT_ROUTE, INITIALIZE_BACKUP_PHRASE_ROUTE } from '../../../../ui/app/routes'
|
|
||||||
|
|
||||||
class ConfirmSeedScreen extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
isLoading: PropTypes.bool,
|
|
||||||
address: PropTypes.string,
|
|
||||||
seedWords: PropTypes.string,
|
|
||||||
confirmSeedWords: PropTypes.func,
|
|
||||||
history: PropTypes.object,
|
|
||||||
openBuyEtherModal: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
seedWords: '',
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
const { seedWords } = props
|
|
||||||
this.state = {
|
|
||||||
selectedSeeds: [],
|
|
||||||
shuffledSeeds: seedWords && shuffle(seedWords.split(' ')) || [],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
UNSAFE_componentWillMount () {
|
|
||||||
const { seedWords, history } = this.props
|
|
||||||
|
|
||||||
if (!seedWords) {
|
|
||||||
history.push(DEFAULT_ROUTE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClick () {
|
|
||||||
const { confirmSeedWords, history, openBuyEtherModal } = this.props
|
|
||||||
|
|
||||||
confirmSeedWords()
|
|
||||||
.then(() => {
|
|
||||||
history.push(DEFAULT_ROUTE)
|
|
||||||
openBuyEtherModal()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { seedWords, history } = this.props
|
|
||||||
const { selectedSeeds, shuffledSeeds } = this.state
|
|
||||||
const isValid = seedWords === selectedSeeds.map(([_, seed]) => seed).join(' ')
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="first-time-flow">
|
|
||||||
{
|
|
||||||
this.props.isLoading
|
|
||||||
? <LoadingScreen loadingMessage="Creating your new account" />
|
|
||||||
: (
|
|
||||||
<div className="first-view-main-wrapper">
|
|
||||||
<div className="first-view-main">
|
|
||||||
<div className="backup-phrase">
|
|
||||||
<a
|
|
||||||
className="backup-phrase__back-button"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault()
|
|
||||||
history.push(INITIALIZE_BACKUP_PHRASE_ROUTE)
|
|
||||||
}}
|
|
||||||
href="#"
|
|
||||||
>
|
|
||||||
{`< Back`}
|
|
||||||
</a>
|
|
||||||
<Identicon address={this.props.address} diameter={70} />
|
|
||||||
<div className="backup-phrase__content-wrapper">
|
|
||||||
<div>
|
|
||||||
<div className="backup-phrase__title">
|
|
||||||
Confirm your Secret Backup Phrase
|
|
||||||
</div>
|
|
||||||
<div className="backup-phrase__body-text">
|
|
||||||
Please select each phrase in order to make sure it is correct.
|
|
||||||
</div>
|
|
||||||
<div className="backup-phrase__confirm-secret">
|
|
||||||
{selectedSeeds.map(([_, word], i) => (
|
|
||||||
<button
|
|
||||||
key={i}
|
|
||||||
className="backup-phrase__confirm-seed-option"
|
|
||||||
>
|
|
||||||
{word}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="backup-phrase__confirm-seed-options">
|
|
||||||
{shuffledSeeds.map((word, i) => {
|
|
||||||
const isSelected = selectedSeeds
|
|
||||||
.filter(([index, seed]) => seed === word && index === i)
|
|
||||||
.length
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={i}
|
|
||||||
className={classnames('backup-phrase__confirm-seed-option', {
|
|
||||||
'backup-phrase__confirm-seed-option--selected': isSelected,
|
|
||||||
'backup-phrase__confirm-seed-option--unselected': !isSelected,
|
|
||||||
})}
|
|
||||||
onClick={() => {
|
|
||||||
if (!isSelected) {
|
|
||||||
this.setState({
|
|
||||||
selectedSeeds: [...selectedSeeds, [i, word]],
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
selectedSeeds: selectedSeeds
|
|
||||||
.filter(([index, seed]) => !(seed === word && index === i)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{word}
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
className="first-time-flow__button"
|
|
||||||
onClick={() => isValid && this.handleClick()}
|
|
||||||
disabled={!isValid}
|
|
||||||
>
|
|
||||||
Confirm
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Breadcrumbs total={3} currentIndex={1} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withRouter,
|
|
||||||
connect(
|
|
||||||
({ metamask: { selectedAddress, seedWords }, appState: { isLoading } }) => ({
|
|
||||||
seedWords,
|
|
||||||
isLoading,
|
|
||||||
address: selectedAddress,
|
|
||||||
}),
|
|
||||||
dispatch => ({
|
|
||||||
confirmSeedWords: () => dispatch(confirmSeedWords()),
|
|
||||||
openBuyEtherModal: () => dispatch(showModal({ name: 'DEPOSIT_ETHER'})),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)(ConfirmSeedScreen)
|
|
|
@ -1,216 +0,0 @@
|
||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import {connect} from 'react-redux'
|
|
||||||
import { withRouter } from 'react-router-dom'
|
|
||||||
import { compose } from 'recompose'
|
|
||||||
import { createNewVaultAndKeychain } from '../../../../ui/app/actions'
|
|
||||||
import Breadcrumbs from './breadcrumbs'
|
|
||||||
import EventEmitter from 'events'
|
|
||||||
import classnames from 'classnames'
|
|
||||||
import {
|
|
||||||
INITIALIZE_UNIQUE_IMAGE_ROUTE,
|
|
||||||
INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE,
|
|
||||||
INITIALIZE_NOTICE_ROUTE,
|
|
||||||
} from '../../../../ui/app/routes'
|
|
||||||
import TextField from '../../../../ui/app/components/text-field'
|
|
||||||
|
|
||||||
class CreatePasswordScreen extends Component {
|
|
||||||
static contextTypes = {
|
|
||||||
t: PropTypes.func,
|
|
||||||
}
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
isLoading: PropTypes.bool.isRequired,
|
|
||||||
createAccount: PropTypes.func.isRequired,
|
|
||||||
history: PropTypes.object.isRequired,
|
|
||||||
isInitialized: PropTypes.bool,
|
|
||||||
isUnlocked: PropTypes.bool,
|
|
||||||
isMascara: PropTypes.bool.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
state = {
|
|
||||||
password: '',
|
|
||||||
confirmPassword: '',
|
|
||||||
passwordError: null,
|
|
||||||
confirmPasswordError: null,
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
this.animationEventEmitter = new EventEmitter()
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
UNSAFE_componentWillMount () {
|
|
||||||
const { isInitialized, history } = this.props
|
|
||||||
|
|
||||||
if (isInitialized) {
|
|
||||||
history.push(INITIALIZE_NOTICE_ROUTE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isValid () {
|
|
||||||
const { password, confirmPassword } = this.state
|
|
||||||
|
|
||||||
if (!password || !confirmPassword) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (password.length < 8) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return password === confirmPassword
|
|
||||||
}
|
|
||||||
|
|
||||||
createAccount = (event) => {
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
if (!this.isValid()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const { password } = this.state
|
|
||||||
const { createAccount, history } = this.props
|
|
||||||
|
|
||||||
this.setState({ isLoading: true })
|
|
||||||
createAccount(password)
|
|
||||||
.then(() => history.push(INITIALIZE_UNIQUE_IMAGE_ROUTE))
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePasswordChange (password) {
|
|
||||||
const { confirmPassword } = this.state
|
|
||||||
let confirmPasswordError = null
|
|
||||||
let passwordError = null
|
|
||||||
|
|
||||||
if (password && password.length < 8) {
|
|
||||||
passwordError = this.context.t('passwordNotLongEnough')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (confirmPassword && password !== confirmPassword) {
|
|
||||||
confirmPasswordError = this.context.t('passwordsDontMatch')
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ password, passwordError, confirmPasswordError })
|
|
||||||
}
|
|
||||||
|
|
||||||
handleConfirmPasswordChange (confirmPassword) {
|
|
||||||
const { password } = this.state
|
|
||||||
let confirmPasswordError = null
|
|
||||||
|
|
||||||
if (password !== confirmPassword) {
|
|
||||||
confirmPasswordError = this.context.t('passwordsDontMatch')
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ confirmPassword, confirmPasswordError })
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { history, isMascara } = this.props
|
|
||||||
const { passwordError, confirmPasswordError } = this.state
|
|
||||||
const { t } = this.context
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classnames({ 'first-view-main-wrapper': !isMascara })}>
|
|
||||||
<div className={classnames({
|
|
||||||
'first-view-main': !isMascara,
|
|
||||||
'first-view-main__mascara': isMascara,
|
|
||||||
})}>
|
|
||||||
{isMascara && <div className="mascara-info first-view-phone-invisible">
|
|
||||||
<div className="info">
|
|
||||||
Nifty Wallet is a secure identity vault for Ethereum.
|
|
||||||
</div>
|
|
||||||
<div className="info">
|
|
||||||
It allows you to hold ether & tokens, and interact with decentralized applications.
|
|
||||||
</div>
|
|
||||||
</div>}
|
|
||||||
<form className="create-password">
|
|
||||||
<div className="create-password__title">
|
|
||||||
Create Password
|
|
||||||
</div>
|
|
||||||
<TextField
|
|
||||||
id="create-password"
|
|
||||||
label={t('newPassword')}
|
|
||||||
type="password"
|
|
||||||
className="first-time-flow__input"
|
|
||||||
value={this.state.password}
|
|
||||||
onChange={event => this.handlePasswordChange(event.target.value)}
|
|
||||||
error={passwordError}
|
|
||||||
autoFocus
|
|
||||||
autoComplete="new-password"
|
|
||||||
margin="normal"
|
|
||||||
fullWidth
|
|
||||||
largeLabel
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
id="confirm-password"
|
|
||||||
label={t('confirmPassword')}
|
|
||||||
type="password"
|
|
||||||
className="first-time-flow__input"
|
|
||||||
value={this.state.confirmPassword}
|
|
||||||
onChange={event => this.handleConfirmPasswordChange(event.target.value)}
|
|
||||||
error={confirmPasswordError}
|
|
||||||
autoComplete="confirm-password"
|
|
||||||
margin="normal"
|
|
||||||
fullWidth
|
|
||||||
largeLabel
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className="first-time-flow__button"
|
|
||||||
disabled={!this.isValid()}
|
|
||||||
onClick={this.createAccount}
|
|
||||||
>
|
|
||||||
Create
|
|
||||||
</button>
|
|
||||||
<a
|
|
||||||
href=""
|
|
||||||
className="first-time-flow__link create-password__import-link"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault()
|
|
||||||
history.push(INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Import with seed phrase
|
|
||||||
</a>
|
|
||||||
{ /* }
|
|
||||||
<a
|
|
||||||
href=""
|
|
||||||
className="first-time-flow__link create-password__import-link"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault()
|
|
||||||
history.push(INITIALIZE_IMPORT_ACCOUNT_ROUTE)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Import an account
|
|
||||||
</a>
|
|
||||||
{ */ }
|
|
||||||
<Breadcrumbs total={3} currentIndex={0} />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = ({ metamask, appState }) => {
|
|
||||||
const { isInitialized, isUnlocked, isMascara, noActiveNotices } = metamask
|
|
||||||
const { isLoading } = appState
|
|
||||||
|
|
||||||
return {
|
|
||||||
isLoading,
|
|
||||||
isInitialized,
|
|
||||||
isUnlocked,
|
|
||||||
isMascara,
|
|
||||||
noActiveNotices,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withRouter,
|
|
||||||
connect(
|
|
||||||
mapStateToProps,
|
|
||||||
dispatch => ({
|
|
||||||
createAccount: password => dispatch(createNewVaultAndKeychain(password)),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)(CreatePasswordScreen)
|
|
|
@ -1,214 +0,0 @@
|
||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import {connect} from 'react-redux'
|
|
||||||
import classnames from 'classnames'
|
|
||||||
import LoadingScreen from './loading-screen'
|
|
||||||
import {importNewAccount, hideWarning} from '../../../../ui/app/actions'
|
|
||||||
|
|
||||||
class Input extends Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
label: PropTypes.string.isRequired,
|
|
||||||
placeholder: PropTypes.string.isRequired,
|
|
||||||
type: PropTypes.string.isRequired,
|
|
||||||
errorMessage: PropTypes.string.isRequired,
|
|
||||||
onChange: PropTypes.func.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const {label, type, placeholder, errorMessage, onChange} = this.props
|
|
||||||
return (
|
|
||||||
<div className="import-account__input-wrapper">
|
|
||||||
<div className="import-account__input-label">{label}</div>
|
|
||||||
<input
|
|
||||||
type={type}
|
|
||||||
placeholder={placeholder}
|
|
||||||
className={classnames('first-time-flow__input import-account__input', {
|
|
||||||
'first-time-flow__input--error': errorMessage,
|
|
||||||
})}
|
|
||||||
onChange={onChange}
|
|
||||||
/>
|
|
||||||
<div className="import-account__input-error-message">{errorMessage}</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ImportAccountScreen extends Component {
|
|
||||||
static OPTIONS = {
|
|
||||||
PRIVATE_KEY: 'private_key',
|
|
||||||
JSON_FILE: 'json_file',
|
|
||||||
};
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
warning: PropTypes.string,
|
|
||||||
back: PropTypes.func.isRequired,
|
|
||||||
next: PropTypes.func.isRequired,
|
|
||||||
importNewAccount: PropTypes.func.isRequired,
|
|
||||||
hideWarning: PropTypes.func.isRequired,
|
|
||||||
isLoading: PropTypes.bool.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
selectedOption: ImportAccountScreen.OPTIONS.PRIVATE_KEY,
|
|
||||||
privateKey: '',
|
|
||||||
jsonFile: {},
|
|
||||||
}
|
|
||||||
|
|
||||||
isValid () {
|
|
||||||
const { OPTIONS } = ImportAccountScreen
|
|
||||||
const { privateKey, jsonFile, password } = this.state
|
|
||||||
|
|
||||||
switch (this.state.selectedOption) {
|
|
||||||
case OPTIONS.JSON_FILE:
|
|
||||||
return Boolean(jsonFile && password)
|
|
||||||
case OPTIONS.PRIVATE_KEY:
|
|
||||||
default:
|
|
||||||
return Boolean(privateKey)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onClick = () => {
|
|
||||||
const { OPTIONS } = ImportAccountScreen
|
|
||||||
const { importNewAccount, next } = this.props
|
|
||||||
const { privateKey, jsonFile, password } = this.state
|
|
||||||
|
|
||||||
switch (this.state.selectedOption) {
|
|
||||||
case OPTIONS.JSON_FILE:
|
|
||||||
return importNewAccount('JSON File', [ jsonFile, password ])
|
|
||||||
// JS runtime requires caught rejections but failures are handled by Redux
|
|
||||||
.catch()
|
|
||||||
.then(next)
|
|
||||||
case OPTIONS.PRIVATE_KEY:
|
|
||||||
default:
|
|
||||||
return importNewAccount('Private Key', [ privateKey ])
|
|
||||||
// JS runtime requires caught rejections but failures are handled by Redux
|
|
||||||
.catch()
|
|
||||||
.then(next)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderPrivateKey () {
|
|
||||||
return Input({
|
|
||||||
label: 'Add Private Key String',
|
|
||||||
placeholder: 'Enter private key',
|
|
||||||
onChange: e => this.setState({ privateKey: e.target.value }),
|
|
||||||
errorMessage: this.props.warning && 'Something went wrong. Please make sure your private key is correct.',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
renderJsonFile () {
|
|
||||||
const { jsonFile: { name } } = this.state
|
|
||||||
const { warning } = this.props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="">
|
|
||||||
<div className="import-account__input-wrapper">
|
|
||||||
<div className="import-account__input-label">Upload File</div>
|
|
||||||
<div className="import-account__file-picker-wrapper">
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
id="file"
|
|
||||||
className="import-account__file-input"
|
|
||||||
onChange={e => this.setState({ jsonFile: e.target.files[0] })}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="file"
|
|
||||||
className={classnames('import-account__file-input-label', {
|
|
||||||
'import-account__file-input-label--error': warning,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
Choose File
|
|
||||||
</label>
|
|
||||||
<div className="import-account__file-name">{name}</div>
|
|
||||||
</div>
|
|
||||||
<div className="import-account__input-error-message">
|
|
||||||
{warning && 'Something went wrong. Please make sure your JSON file is properly formatted.'}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{Input({
|
|
||||||
label: 'Enter Password',
|
|
||||||
placeholder: 'Enter Password',
|
|
||||||
type: 'password',
|
|
||||||
onChange: e => this.setState({ password: e.target.value }),
|
|
||||||
errorMessage: warning && 'Please make sure your password is correct.',
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderContent () {
|
|
||||||
const { OPTIONS } = ImportAccountScreen
|
|
||||||
|
|
||||||
switch (this.state.selectedOption) {
|
|
||||||
case OPTIONS.JSON_FILE:
|
|
||||||
return this.renderJsonFile()
|
|
||||||
case OPTIONS.PRIVATE_KEY:
|
|
||||||
default:
|
|
||||||
return this.renderPrivateKey()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { OPTIONS } = ImportAccountScreen
|
|
||||||
const { selectedOption } = this.state
|
|
||||||
|
|
||||||
return this.props.isLoading
|
|
||||||
? <LoadingScreen loadingMessage="Creating your new account" />
|
|
||||||
: (
|
|
||||||
<div className="import-account">
|
|
||||||
<a
|
|
||||||
className="import-account__back-button"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault()
|
|
||||||
this.props.back()
|
|
||||||
}}
|
|
||||||
href="#"
|
|
||||||
>
|
|
||||||
{`< Back`}
|
|
||||||
</a>
|
|
||||||
<div className="import-account__title">
|
|
||||||
Import an Account
|
|
||||||
</div>
|
|
||||||
<div className="import-account__selector-label">
|
|
||||||
How would you like to import your account?
|
|
||||||
</div>
|
|
||||||
<select
|
|
||||||
className="import-account__dropdown"
|
|
||||||
value={selectedOption}
|
|
||||||
onChange={e => {
|
|
||||||
this.setState({ selectedOption: e.target.value })
|
|
||||||
this.props.hideWarning()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<option value={OPTIONS.PRIVATE_KEY}>Private Key</option>
|
|
||||||
<option value={OPTIONS.JSON_FILE}>JSON File</option>
|
|
||||||
</select>
|
|
||||||
{this.renderContent()}
|
|
||||||
<button
|
|
||||||
className="first-time-flow__button"
|
|
||||||
disabled={!this.isValid()}
|
|
||||||
onClick={this.onClick}
|
|
||||||
>
|
|
||||||
Import
|
|
||||||
</button>
|
|
||||||
<a
|
|
||||||
href="https://github.com/MetaMask/faq/blob/master/README.md#q-i-cant-use-the-import-feature-for-uploading-a-json-file-the-window-keeps-closing-when-i-try-to-select-a-file"
|
|
||||||
className="first-time-flow__link import-account__faq-link"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
File import not working?
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
({ appState: { isLoading, warning } }) => ({ isLoading, warning }),
|
|
||||||
dispatch => ({
|
|
||||||
importNewAccount: (strategy, args) => dispatch(importNewAccount(strategy, args)),
|
|
||||||
hideWarning: () => dispatch(hideWarning()),
|
|
||||||
}),
|
|
||||||
)(ImportAccountScreen)
|
|
|
@ -1,191 +0,0 @@
|
||||||
import {validateMnemonic} from 'bip39'
|
|
||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import {connect} from 'react-redux'
|
|
||||||
import {
|
|
||||||
createNewVaultAndRestore,
|
|
||||||
unMarkPasswordForgotten,
|
|
||||||
} from '../../../../ui/app/actions'
|
|
||||||
import { INITIALIZE_NOTICE_ROUTE } from '../../../../ui/app/routes'
|
|
||||||
import TextField from '../../../../ui/app/components/text-field'
|
|
||||||
|
|
||||||
class ImportSeedPhraseScreen extends Component {
|
|
||||||
static contextTypes = {
|
|
||||||
t: PropTypes.func,
|
|
||||||
}
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
warning: PropTypes.string,
|
|
||||||
createNewVaultAndRestore: PropTypes.func.isRequired,
|
|
||||||
leaveImportSeedScreenState: PropTypes.func,
|
|
||||||
history: PropTypes.object,
|
|
||||||
isLoading: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
seedPhrase: '',
|
|
||||||
password: '',
|
|
||||||
confirmPassword: '',
|
|
||||||
seedPhraseError: null,
|
|
||||||
passwordError: null,
|
|
||||||
confirmPasswordError: null,
|
|
||||||
}
|
|
||||||
|
|
||||||
parseSeedPhrase = (seedPhrase) => {
|
|
||||||
return seedPhrase
|
|
||||||
.match(/\w+/g)
|
|
||||||
.join(' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSeedPhraseChange (seedPhrase) {
|
|
||||||
let seedPhraseError = null
|
|
||||||
|
|
||||||
if (seedPhrase) {
|
|
||||||
const wordsCount = this.parseSeedPhrase(seedPhrase).split(' ').length
|
|
||||||
if (wordsCount !== 12 && wordsCount !== 24) {
|
|
||||||
seedPhraseError = this.context.t('seedPhraseReq')
|
|
||||||
} else if (!validateMnemonic(seedPhrase)) {
|
|
||||||
seedPhraseError = this.context.t('invalidSeedPhrase')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ seedPhrase, seedPhraseError })
|
|
||||||
}
|
|
||||||
|
|
||||||
handlePasswordChange (password) {
|
|
||||||
const { confirmPassword } = this.state
|
|
||||||
let confirmPasswordError = null
|
|
||||||
let passwordError = null
|
|
||||||
|
|
||||||
if (password && password.length < 8) {
|
|
||||||
passwordError = this.context.t('passwordNotLongEnough')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (confirmPassword && password !== confirmPassword) {
|
|
||||||
confirmPasswordError = this.context.t('passwordsDontMatch')
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ password, passwordError, confirmPasswordError })
|
|
||||||
}
|
|
||||||
|
|
||||||
handleConfirmPasswordChange (confirmPassword) {
|
|
||||||
const { password } = this.state
|
|
||||||
let confirmPasswordError = null
|
|
||||||
|
|
||||||
if (password !== confirmPassword) {
|
|
||||||
confirmPasswordError = this.context.t('passwordsDontMatch')
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ confirmPassword, confirmPasswordError })
|
|
||||||
}
|
|
||||||
|
|
||||||
onClick = () => {
|
|
||||||
const { password, seedPhrase } = this.state
|
|
||||||
const {
|
|
||||||
createNewVaultAndRestore,
|
|
||||||
leaveImportSeedScreenState,
|
|
||||||
history,
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
leaveImportSeedScreenState()
|
|
||||||
createNewVaultAndRestore(password, this.parseSeedPhrase(seedPhrase))
|
|
||||||
.then(() => history.push(INITIALIZE_NOTICE_ROUTE))
|
|
||||||
}
|
|
||||||
|
|
||||||
hasError () {
|
|
||||||
const { passwordError, confirmPasswordError, seedPhraseError } = this.state
|
|
||||||
return passwordError || confirmPasswordError || seedPhraseError
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const {
|
|
||||||
seedPhrase,
|
|
||||||
password,
|
|
||||||
confirmPassword,
|
|
||||||
seedPhraseError,
|
|
||||||
passwordError,
|
|
||||||
confirmPasswordError,
|
|
||||||
} = this.state
|
|
||||||
const { t } = this.context
|
|
||||||
const { isLoading } = this.props
|
|
||||||
const disabled = !seedPhrase || !password || !confirmPassword || isLoading || this.hasError()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="first-view-main-wrapper">
|
|
||||||
<div className="first-view-main">
|
|
||||||
<div className="import-account">
|
|
||||||
<a
|
|
||||||
className="import-account__back-button"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault()
|
|
||||||
this.props.history.goBack()
|
|
||||||
}}
|
|
||||||
href="#"
|
|
||||||
>
|
|
||||||
{`< Back`}
|
|
||||||
</a>
|
|
||||||
<div className="import-account__title">
|
|
||||||
Import an Account with Seed Phrase
|
|
||||||
</div>
|
|
||||||
<div className="import-account__selector-label">
|
|
||||||
Enter your secret twelve word phrase here to restore your vault.
|
|
||||||
</div>
|
|
||||||
<div className="import-account__input-wrapper">
|
|
||||||
<label className="import-account__input-label">Wallet Seed</label>
|
|
||||||
<textarea
|
|
||||||
className="import-account__secret-phrase"
|
|
||||||
onChange={e => this.handleSeedPhraseChange(e.target.value)}
|
|
||||||
value={this.state.seedPhrase}
|
|
||||||
placeholder="Separate each word with a single space"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span className="error">
|
|
||||||
{ seedPhraseError }
|
|
||||||
</span>
|
|
||||||
<TextField
|
|
||||||
id="password"
|
|
||||||
label={t('newPassword')}
|
|
||||||
type="password"
|
|
||||||
className="first-time-flow__input"
|
|
||||||
value={this.state.password}
|
|
||||||
onChange={event => this.handlePasswordChange(event.target.value)}
|
|
||||||
error={passwordError}
|
|
||||||
autoComplete="new-password"
|
|
||||||
margin="normal"
|
|
||||||
largeLabel
|
|
||||||
/>
|
|
||||||
<TextField
|
|
||||||
id="confirm-password"
|
|
||||||
label={t('confirmPassword')}
|
|
||||||
type="password"
|
|
||||||
className="first-time-flow__input"
|
|
||||||
value={this.state.confirmPassword}
|
|
||||||
onChange={event => this.handleConfirmPasswordChange(event.target.value)}
|
|
||||||
error={confirmPasswordError}
|
|
||||||
autoComplete="confirm-password"
|
|
||||||
margin="normal"
|
|
||||||
largeLabel
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className="first-time-flow__button"
|
|
||||||
onClick={() => !disabled && this.onClick()}
|
|
||||||
disabled={disabled}
|
|
||||||
>
|
|
||||||
Import
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
({ appState: { warning, isLoading } }) => ({ warning, isLoading }),
|
|
||||||
dispatch => ({
|
|
||||||
leaveImportSeedScreenState: () => {
|
|
||||||
dispatch(unMarkPasswordForgotten())
|
|
||||||
},
|
|
||||||
createNewVaultAndRestore: (pw, seed) => dispatch(createNewVaultAndRestore(pw, seed)),
|
|
||||||
}),
|
|
||||||
)(ImportSeedPhraseScreen)
|
|
File diff suppressed because one or more lines are too long
|
@ -1,99 +0,0 @@
|
||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import {connect} from 'react-redux'
|
|
||||||
import { withRouter, Switch, Route } from 'react-router-dom'
|
|
||||||
import { compose } from 'recompose'
|
|
||||||
|
|
||||||
import CreatePasswordScreen from './create-password-screen'
|
|
||||||
import UniqueImageScreen from './unique-image-screen'
|
|
||||||
import NoticeScreen from './notice-screen'
|
|
||||||
import BackupPhraseScreen from './seed-screen'
|
|
||||||
import ImportAccountScreen from './import-account-screen'
|
|
||||||
import ImportSeedPhraseScreen from './import-seed-phrase-screen'
|
|
||||||
import ConfirmSeed from './confirm-seed-screen'
|
|
||||||
import {
|
|
||||||
INITIALIZE_ROUTE,
|
|
||||||
INITIALIZE_IMPORT_ACCOUNT_ROUTE,
|
|
||||||
INITIALIZE_UNIQUE_IMAGE_ROUTE,
|
|
||||||
INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE,
|
|
||||||
INITIALIZE_NOTICE_ROUTE,
|
|
||||||
INITIALIZE_BACKUP_PHRASE_ROUTE,
|
|
||||||
INITIALIZE_CONFIRM_SEED_ROUTE,
|
|
||||||
INITIALIZE_CREATE_PASSWORD_ROUTE,
|
|
||||||
} from '../../../../ui/app/routes'
|
|
||||||
import WelcomeScreen from '../../../../ui/app/welcome-screen'
|
|
||||||
|
|
||||||
class FirstTimeFlow extends Component {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
isInitialized: PropTypes.bool,
|
|
||||||
seedWords: PropTypes.string,
|
|
||||||
address: PropTypes.string,
|
|
||||||
noActiveNotices: PropTypes.bool,
|
|
||||||
goToBuyEtherView: PropTypes.func,
|
|
||||||
isUnlocked: PropTypes.bool,
|
|
||||||
history: PropTypes.object,
|
|
||||||
welcomeScreenSeen: PropTypes.bool,
|
|
||||||
isPopup: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
isInitialized: false,
|
|
||||||
seedWords: '',
|
|
||||||
noActiveNotices: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div className="flex-column flex-grow">
|
|
||||||
<div className="first-time-flow">
|
|
||||||
<Switch>
|
|
||||||
<Route exact path={INITIALIZE_IMPORT_ACCOUNT_ROUTE} component={ImportAccountScreen} />
|
|
||||||
<Route
|
|
||||||
exact
|
|
||||||
path={INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE}
|
|
||||||
component={ImportSeedPhraseScreen}
|
|
||||||
/>
|
|
||||||
<Route exact path={INITIALIZE_UNIQUE_IMAGE_ROUTE} component={UniqueImageScreen} />
|
|
||||||
<Route exact path={INITIALIZE_NOTICE_ROUTE} component={NoticeScreen} />
|
|
||||||
<Route exact path={INITIALIZE_BACKUP_PHRASE_ROUTE} component={BackupPhraseScreen} />
|
|
||||||
<Route exact path={INITIALIZE_CONFIRM_SEED_ROUTE} component={ConfirmSeed} />
|
|
||||||
<Route exact path={INITIALIZE_CREATE_PASSWORD_ROUTE} component={CreatePasswordScreen} />
|
|
||||||
<Route exact path={INITIALIZE_ROUTE} component={WelcomeScreen} />
|
|
||||||
</Switch>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = ({ metamask }) => {
|
|
||||||
const {
|
|
||||||
isInitialized,
|
|
||||||
seedWords,
|
|
||||||
noActiveNotices,
|
|
||||||
selectedAddress,
|
|
||||||
forgottenPassword,
|
|
||||||
isMascara,
|
|
||||||
isUnlocked,
|
|
||||||
welcomeScreenSeen,
|
|
||||||
isPopup,
|
|
||||||
} = metamask
|
|
||||||
|
|
||||||
return {
|
|
||||||
isMascara,
|
|
||||||
isInitialized,
|
|
||||||
seedWords,
|
|
||||||
noActiveNotices,
|
|
||||||
address: selectedAddress,
|
|
||||||
forgottenPassword,
|
|
||||||
isUnlocked,
|
|
||||||
welcomeScreenSeen,
|
|
||||||
isPopup,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withRouter,
|
|
||||||
connect(mapStateToProps),
|
|
||||||
)(FirstTimeFlow)
|
|
|
@ -1,17 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import Spinner from './spinner'
|
|
||||||
|
|
||||||
export default function LoadingScreen ({ className = '', loadingMessage }) {
|
|
||||||
return (
|
|
||||||
<div className={`${className} loading-screen`}>
|
|
||||||
<Spinner color="#1B344D" />
|
|
||||||
<div className="loading-screen__message">{loadingMessage}</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadingScreen.propTypes = {
|
|
||||||
className: PropTypes.string,
|
|
||||||
loadingMessage: PropTypes.string,
|
|
||||||
}
|
|
|
@ -1,135 +0,0 @@
|
||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import Markdown from 'react-markdown'
|
|
||||||
import { connect } from 'react-redux'
|
|
||||||
import { withRouter } from 'react-router-dom'
|
|
||||||
import { compose } from 'recompose'
|
|
||||||
import debounce from 'lodash.debounce'
|
|
||||||
import { markNoticeRead } from '../../../../ui/app/actions'
|
|
||||||
import Identicon from '../../../../ui/app/components/identicon'
|
|
||||||
import Breadcrumbs from './breadcrumbs'
|
|
||||||
import { INITIALIZE_BACKUP_PHRASE_ROUTE } from '../../../../ui/app/routes'
|
|
||||||
import LoadingScreen from './loading-screen'
|
|
||||||
|
|
||||||
class NoticeScreen extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
address: PropTypes.string.isRequired,
|
|
||||||
nextUnreadNotice: PropTypes.shape({
|
|
||||||
title: PropTypes.string,
|
|
||||||
date: PropTypes.string,
|
|
||||||
body: PropTypes.string,
|
|
||||||
}),
|
|
||||||
location: PropTypes.shape({
|
|
||||||
state: PropTypes.shape({
|
|
||||||
next: PropTypes.func.isRequired,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
markNoticeRead: PropTypes.func,
|
|
||||||
history: PropTypes.object,
|
|
||||||
isLoading: PropTypes.bool,
|
|
||||||
noActiveNotices: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
nextUnreadNotice: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
atBottom: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
if (this.props.noActiveNotices) {
|
|
||||||
this.props.history.push(INITIALIZE_BACKUP_PHRASE_ROUTE)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.onScroll()
|
|
||||||
}
|
|
||||||
|
|
||||||
acceptTerms = () => {
|
|
||||||
const { markNoticeRead, nextUnreadNotice, history } = this.props
|
|
||||||
markNoticeRead(nextUnreadNotice)
|
|
||||||
.then(hasActiveNotices => {
|
|
||||||
if (!hasActiveNotices) {
|
|
||||||
history.push(INITIALIZE_BACKUP_PHRASE_ROUTE)
|
|
||||||
} else {
|
|
||||||
this.setState({ atBottom: false })
|
|
||||||
this.onScroll()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onScroll = debounce(() => {
|
|
||||||
if (this.state.atBottom) return
|
|
||||||
|
|
||||||
const target = document.querySelector('.tou__body')
|
|
||||||
const {scrollTop, offsetHeight, scrollHeight} = target
|
|
||||||
const atBottom = scrollTop + offsetHeight >= scrollHeight
|
|
||||||
|
|
||||||
this.setState({atBottom: atBottom})
|
|
||||||
}, 25)
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const {
|
|
||||||
address,
|
|
||||||
nextUnreadNotice: { title, body },
|
|
||||||
isLoading,
|
|
||||||
} = this.props
|
|
||||||
const { atBottom } = this.state
|
|
||||||
|
|
||||||
return (
|
|
||||||
isLoading
|
|
||||||
? <LoadingScreen />
|
|
||||||
: (
|
|
||||||
<div className="first-time-flow">
|
|
||||||
<div className="first-view-main-wrapper">
|
|
||||||
<div className="first-view-main">
|
|
||||||
<div
|
|
||||||
className="tou"
|
|
||||||
onScroll={this.onScroll}
|
|
||||||
>
|
|
||||||
<Identicon address={address} diameter={70} />
|
|
||||||
<div className="tou__title">{title}</div>
|
|
||||||
<Markdown
|
|
||||||
className="tou__body markdown"
|
|
||||||
source={body}
|
|
||||||
skipHtml
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className="first-time-flow__button"
|
|
||||||
onClick={atBottom && this.acceptTerms}
|
|
||||||
disabled={!atBottom}
|
|
||||||
>
|
|
||||||
Accept
|
|
||||||
</button>
|
|
||||||
<Breadcrumbs total={3} currentIndex={2} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = ({ metamask, appState }) => {
|
|
||||||
const { selectedAddress, nextUnreadNotice, noActiveNotices } = metamask
|
|
||||||
const { isLoading } = appState
|
|
||||||
|
|
||||||
return {
|
|
||||||
address: selectedAddress,
|
|
||||||
nextUnreadNotice,
|
|
||||||
noActiveNotices,
|
|
||||||
isLoading,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withRouter,
|
|
||||||
connect(
|
|
||||||
mapStateToProps,
|
|
||||||
dispatch => ({
|
|
||||||
markNoticeRead: notice => dispatch(markNoticeRead(notice)),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)(NoticeScreen)
|
|
|
@ -1,177 +0,0 @@
|
||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { connect } from 'react-redux'
|
|
||||||
import classnames from 'classnames'
|
|
||||||
import { withRouter } from 'react-router-dom'
|
|
||||||
import { compose } from 'recompose'
|
|
||||||
import Identicon from '../../../../ui/app/components/identicon'
|
|
||||||
import {exportAsFile} from '../../../../ui/app/util'
|
|
||||||
import Breadcrumbs from './breadcrumbs'
|
|
||||||
import LoadingScreen from './loading-screen'
|
|
||||||
import { DEFAULT_ROUTE, INITIALIZE_CONFIRM_SEED_ROUTE } from '../../../../ui/app/routes'
|
|
||||||
|
|
||||||
const LockIcon = props => (
|
|
||||||
<svg
|
|
||||||
version="1.1"
|
|
||||||
id="Capa_1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
|
||||||
x="0px"
|
|
||||||
y="0px"
|
|
||||||
width="401.998px"
|
|
||||||
height="401.998px"
|
|
||||||
viewBox="0 0 401.998 401.998"
|
|
||||||
style={{enableBackground: 'new 0 0 401.998 401.998'}}
|
|
||||||
xmlSpace="preserve"
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<g>
|
|
||||||
<path
|
|
||||||
d="M357.45,190.721c-5.331-5.33-11.8-7.993-19.417-7.993h-9.131v-54.821c0-35.022-12.559-65.093-37.685-90.218
|
|
||||||
C266.093,12.563,236.025,0,200.998,0c-35.026,0-65.1,12.563-90.222,37.688C85.65,62.814,73.091,92.884,73.091,127.907v54.821
|
|
||||||
h-9.135c-7.611,0-14.084,2.663-19.414,7.993c-5.33,5.326-7.994,11.799-7.994,19.417V374.59c0,7.611,2.665,14.086,7.994,19.417
|
|
||||||
c5.33,5.325,11.803,7.991,19.414,7.991H338.04c7.617,0,14.085-2.663,19.417-7.991c5.325-5.331,7.994-11.806,7.994-19.417V210.135
|
|
||||||
C365.455,202.523,362.782,196.051,357.45,190.721z M274.087,182.728H127.909v-54.821c0-20.175,7.139-37.402,21.414-51.675
|
|
||||||
c14.277-14.275,31.501-21.411,51.678-21.411c20.179,0,37.399,7.135,51.677,21.411c14.271,14.272,21.409,31.5,21.409,51.675V182.728
|
|
||||||
z"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
|
|
||||||
class BackupPhraseScreen extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
isLoading: PropTypes.bool.isRequired,
|
|
||||||
address: PropTypes.string.isRequired,
|
|
||||||
seedWords: PropTypes.string,
|
|
||||||
history: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
seedWords: '',
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
this.state = {
|
|
||||||
isShowingSecret: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
UNSAFE_componentWillMount () {
|
|
||||||
const { seedWords, history } = this.props
|
|
||||||
|
|
||||||
if (!seedWords) {
|
|
||||||
history.push(DEFAULT_ROUTE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exportSeedWords = () => {
|
|
||||||
const { seedWords } = this.props
|
|
||||||
|
|
||||||
exportAsFile('MetaMask Secret Backup Phrase', seedWords, 'text/plain')
|
|
||||||
}
|
|
||||||
|
|
||||||
renderSecretWordsContainer () {
|
|
||||||
const { isShowingSecret } = this.state
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="backup-phrase__secret">
|
|
||||||
<div className={classnames('backup-phrase__secret-words', {
|
|
||||||
'backup-phrase__secret-words--hidden': !isShowingSecret,
|
|
||||||
})}>
|
|
||||||
{this.props.seedWords}
|
|
||||||
</div>
|
|
||||||
{!isShowingSecret && (
|
|
||||||
<div
|
|
||||||
className="backup-phrase__secret-blocker"
|
|
||||||
onClick={() => this.setState({ isShowingSecret: true })}
|
|
||||||
>
|
|
||||||
<LockIcon width="28px" height="35px" fill="#FFFFFF" />
|
|
||||||
<div
|
|
||||||
className="backup-phrase__reveal-button"
|
|
||||||
>
|
|
||||||
Click here to reveal secret words
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderSecretScreen () {
|
|
||||||
const { isShowingSecret } = this.state
|
|
||||||
const { history } = this.props
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="backup-phrase__content-wrapper">
|
|
||||||
<div className="backup-phrase__phrase">
|
|
||||||
<div className="backup-phrase__title">Secret Backup Phrase</div>
|
|
||||||
<div className="backup-phrase__body-text">
|
|
||||||
Your secret backup phrase makes it easy to back up and restore your account.
|
|
||||||
</div>
|
|
||||||
<div className="backup-phrase__body-text">
|
|
||||||
WARNING: Never disclose your backup phrase. Anyone with this phrase can take your Ether forever.
|
|
||||||
</div>
|
|
||||||
{this.renderSecretWordsContainer()}
|
|
||||||
</div>
|
|
||||||
<div className="backup-phrase__tips">
|
|
||||||
<div className="backup-phrase__tips-text">Tips:</div>
|
|
||||||
<div className="backup-phrase__tips-text">
|
|
||||||
Store this phrase in a password manager like 1Password.
|
|
||||||
</div>
|
|
||||||
<div className="backup-phrase__tips-text">
|
|
||||||
Write this phrase on a piece of paper and store in a secure location. If you want even more security, write it down on multiple pieces of paper and store each in 2 - 3 different locations.
|
|
||||||
</div>
|
|
||||||
<div className="backup-phrase__tips-text">
|
|
||||||
Memorize this phrase.
|
|
||||||
</div>
|
|
||||||
<div className="backup-phrase__tips-text">
|
|
||||||
<strong>
|
|
||||||
<a className="backup-phrase__tips-text--link backup-phrase__tips-text--strong" onClick={this.exportSeedWords}>
|
|
||||||
Download this Secret Backup Phrase
|
|
||||||
</a>
|
|
||||||
</strong> and keep it stored safely on an external encrypted hard drive or storage medium.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="backup-phrase__next-button">
|
|
||||||
<button
|
|
||||||
className="first-time-flow__button"
|
|
||||||
onClick={() => isShowingSecret && history.push(INITIALIZE_CONFIRM_SEED_ROUTE)}
|
|
||||||
disabled={!isShowingSecret}
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</button>
|
|
||||||
<Breadcrumbs total={3} currentIndex={1} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return this.props.isLoading
|
|
||||||
? <LoadingScreen loadingMessage="Creating your new account" />
|
|
||||||
: (
|
|
||||||
<div className="first-view-main-wrapper">
|
|
||||||
<div className="first-view-main">
|
|
||||||
<div className="backup-phrase">
|
|
||||||
<Identicon address={this.props.address} diameter={70} />
|
|
||||||
{this.renderSecretScreen()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withRouter,
|
|
||||||
connect(
|
|
||||||
({ metamask: { selectedAddress, seedWords }, appState: { isLoading } }) => ({
|
|
||||||
seedWords,
|
|
||||||
isLoading,
|
|
||||||
address: selectedAddress,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)(BackupPhraseScreen)
|
|
|
@ -1,70 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
export default function Spinner({ className = '', color = "#000000" }) {
|
|
||||||
return (
|
|
||||||
<div className={`spinner ${className}`}>
|
|
||||||
<svg className="lds-spinner" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" style={{background: 'none'}}>
|
|
||||||
<g transform="rotate(0 50 50)">
|
|
||||||
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
|
||||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.9166666666666666s" repeatCount="indefinite" />
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
<g transform="rotate(30 50 50)">
|
|
||||||
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
|
||||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.8333333333333334s" repeatCount="indefinite" />
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
<g transform="rotate(60 50 50)">
|
|
||||||
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
|
||||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.75s" repeatCount="indefinite" />
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
<g transform="rotate(90 50 50)">
|
|
||||||
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
|
||||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.6666666666666666s" repeatCount="indefinite" />
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
<g transform="rotate(120 50 50)">
|
|
||||||
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
|
||||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.5833333333333334s" repeatCount="indefinite" />
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
<g transform="rotate(150 50 50)">
|
|
||||||
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
|
||||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.5s" repeatCount="indefinite" />
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
<g transform="rotate(180 50 50)">
|
|
||||||
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
|
||||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.4166666666666667s" repeatCount="indefinite" />
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
<g transform="rotate(210 50 50)">
|
|
||||||
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
|
||||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.3333333333333333s" repeatCount="indefinite" />
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
<g transform="rotate(240 50 50)">
|
|
||||||
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
|
||||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.25s" repeatCount="indefinite" />
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
<g transform="rotate(270 50 50)">
|
|
||||||
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
|
||||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.16666666666666666s" repeatCount="indefinite" />
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
<g transform="rotate(300 50 50)">
|
|
||||||
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
|
||||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="-0.08333333333333333s" repeatCount="indefinite" />
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
<g transform="rotate(330 50 50)">
|
|
||||||
<rect x={47} y={16} rx={0} ry={0} width={6} height={20} fill={color}>
|
|
||||||
<animate attributeName="opacity" values="1;0" dur="1s" begin="0s" repeatCount="indefinite" />
|
|
||||||
</rect>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import { withRouter } from 'react-router-dom'
|
|
||||||
import { compose } from 'recompose'
|
|
||||||
import {connect} from 'react-redux'
|
|
||||||
import Identicon from '../../../../ui/app/components/identicon'
|
|
||||||
import Breadcrumbs from './breadcrumbs'
|
|
||||||
import { INITIALIZE_NOTICE_ROUTE } from '../../../../ui/app/routes'
|
|
||||||
|
|
||||||
class UniqueImageScreen extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
address: PropTypes.string,
|
|
||||||
history: PropTypes.object,
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<div className="first-view-main-wrapper">
|
|
||||||
<div className="first-view-main">
|
|
||||||
<div className="unique-image">
|
|
||||||
<Identicon address={this.props.address} diameter={70} />
|
|
||||||
<div className="unique-image__title">Your unique account image</div>
|
|
||||||
<div className="unique-image__body-text">
|
|
||||||
This image was programmatically generated for you by your new account number.
|
|
||||||
</div>
|
|
||||||
<div className="unique-image__body-text">
|
|
||||||
You’ll see this image everytime you need to confirm a transaction.
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
className="first-time-flow__button"
|
|
||||||
onClick={() => this.props.history.push(INITIALIZE_NOTICE_ROUTE)}
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</button>
|
|
||||||
<Breadcrumbs total={3} currentIndex={1} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withRouter,
|
|
||||||
connect(
|
|
||||||
({ metamask: { selectedAddress } }) => ({
|
|
||||||
address: selectedAddress,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)(UniqueImageScreen)
|
|
|
@ -1,219 +0,0 @@
|
||||||
import React, { Component } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import classnames from 'classnames'
|
|
||||||
import {qrcode} from 'qrcode-npm'
|
|
||||||
import {connect} from 'react-redux'
|
|
||||||
import {shapeShiftSubview, pairUpdate, buyWithShapeShift} from '../../../../ui/app/actions'
|
|
||||||
import {isValidAddress} from '../../../../ui/app/util'
|
|
||||||
|
|
||||||
export class ShapeShiftForm extends Component {
|
|
||||||
static propTypes = {
|
|
||||||
selectedAddress: PropTypes.string.isRequired,
|
|
||||||
btnClass: PropTypes.string.isRequired,
|
|
||||||
tokenExchangeRates: PropTypes.object.isRequired,
|
|
||||||
coinOptions: PropTypes.object.isRequired,
|
|
||||||
shapeShiftSubview: PropTypes.func.isRequired,
|
|
||||||
pairUpdate: PropTypes.func.isRequired,
|
|
||||||
buyWithShapeShift: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
depositCoin: 'btc',
|
|
||||||
refundAddress: '',
|
|
||||||
showQrCode: false,
|
|
||||||
depositAddress: '',
|
|
||||||
errorMessage: '',
|
|
||||||
isLoading: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
// eslint-disable-next-line camelcase
|
|
||||||
UNSAFE_componentWillMount () {
|
|
||||||
this.props.shapeShiftSubview()
|
|
||||||
}
|
|
||||||
|
|
||||||
onCoinChange = e => {
|
|
||||||
const coin = e.target.value
|
|
||||||
this.setState({
|
|
||||||
depositCoin: coin,
|
|
||||||
errorMessage: '',
|
|
||||||
})
|
|
||||||
this.props.pairUpdate(coin)
|
|
||||||
}
|
|
||||||
|
|
||||||
onBuyWithShapeShift = () => {
|
|
||||||
this.setState({
|
|
||||||
isLoading: true,
|
|
||||||
showQrCode: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
const {
|
|
||||||
buyWithShapeShift,
|
|
||||||
selectedAddress: withdrawal,
|
|
||||||
} = this.props
|
|
||||||
const {
|
|
||||||
refundAddress: returnAddress,
|
|
||||||
depositCoin,
|
|
||||||
} = this.state
|
|
||||||
const pair = `${depositCoin}_eth`
|
|
||||||
const data = {
|
|
||||||
withdrawal,
|
|
||||||
pair,
|
|
||||||
returnAddress,
|
|
||||||
// Public api key
|
|
||||||
'apiKey': '803d1f5df2ed1b1476e4b9e6bcd089e34d8874595dda6a23b67d93c56ea9cc2445e98a6748b219b2b6ad654d9f075f1f1db139abfa93158c04e825db122c14b6',
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isValidAddress(withdrawal)) {
|
|
||||||
buyWithShapeShift(data)
|
|
||||||
.then(d => this.setState({
|
|
||||||
showQrCode: true,
|
|
||||||
depositAddress: d.deposit,
|
|
||||||
isLoading: false,
|
|
||||||
}))
|
|
||||||
.catch(() => this.setState({
|
|
||||||
showQrCode: false,
|
|
||||||
errorMessage: 'Invalid Request',
|
|
||||||
isLoading: false,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderMetadata (label, value) {
|
|
||||||
return (
|
|
||||||
<div className="shapeshift-form__metadata-wrapper">
|
|
||||||
<div className="shapeshift-form__metadata-label">
|
|
||||||
{label}:
|
|
||||||
</div>
|
|
||||||
<div className="shapeshift-form__metadata-value">
|
|
||||||
{value}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderMarketInfo () {
|
|
||||||
const { depositCoin } = this.state
|
|
||||||
const coinPair = `${depositCoin}_eth`
|
|
||||||
const { tokenExchangeRates } = this.props
|
|
||||||
const {
|
|
||||||
limit,
|
|
||||||
rate,
|
|
||||||
minimum,
|
|
||||||
} = tokenExchangeRates[coinPair] || {}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="shapeshift-form__metadata">
|
|
||||||
{this.renderMetadata('Status', limit ? 'Available' : 'Unavailable')}
|
|
||||||
{this.renderMetadata('Limit', limit)}
|
|
||||||
{this.renderMetadata('Exchange Rate', rate)}
|
|
||||||
{this.renderMetadata('Minimum', minimum)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderQrCode () {
|
|
||||||
const { depositAddress, isLoading } = this.state
|
|
||||||
const qrImage = qrcode(4, 'M')
|
|
||||||
qrImage.addData(depositAddress)
|
|
||||||
qrImage.make()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="shapeshift-form">
|
|
||||||
<div className="shapeshift-form__deposit-instruction">
|
|
||||||
Deposit your BTC to the address bellow:
|
|
||||||
</div>
|
|
||||||
<div className="shapeshift-form__qr-code">
|
|
||||||
{isLoading
|
|
||||||
? <img src="images/loading.svg" style={{ width: '60px' }} />
|
|
||||||
: <div dangerouslySetInnerHTML={{ __html: qrImage.createTableTag(4) }} />
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
{this.renderMarketInfo()}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { coinOptions, btnClass } = this.props
|
|
||||||
const { depositCoin, errorMessage, showQrCode } = this.state
|
|
||||||
const coinPair = `${depositCoin}_eth`
|
|
||||||
const { tokenExchangeRates } = this.props
|
|
||||||
const token = tokenExchangeRates[coinPair]
|
|
||||||
|
|
||||||
return showQrCode ? this.renderQrCode() : (
|
|
||||||
<div>
|
|
||||||
<div className="shapeshift-form">
|
|
||||||
<div className="shapeshift-form__selectors">
|
|
||||||
<div className="shapeshift-form__selector">
|
|
||||||
<div className="shapeshift-form__selector-label">
|
|
||||||
Deposit
|
|
||||||
</div>
|
|
||||||
<select
|
|
||||||
className="shapeshift-form__selector-input"
|
|
||||||
value={this.state.depositCoin}
|
|
||||||
onChange={this.onCoinChange}
|
|
||||||
>
|
|
||||||
{Object.entries(coinOptions).map(([coin]) => (
|
|
||||||
<option key={coin} value={coin.toLowerCase()}>
|
|
||||||
{coin}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="icon shapeshift-form__caret"
|
|
||||||
style={{ backgroundImage: 'url(images/caret-right.svg)'}}
|
|
||||||
/>
|
|
||||||
<div className="shapeshift-form__selector">
|
|
||||||
<div className="shapeshift-form__selector-label">
|
|
||||||
Receive
|
|
||||||
</div>
|
|
||||||
<div className="shapeshift-form__selector-input">
|
|
||||||
ETH
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={classnames('shapeshift-form__address-input-wrapper', {
|
|
||||||
'shapeshift-form__address-input-wrapper--error': errorMessage,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<div className="shapeshift-form__address-input-label">
|
|
||||||
Your Refund Address
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
className="shapeshift-form__address-input"
|
|
||||||
onChange={e => this.setState({
|
|
||||||
refundAddress: e.target.value,
|
|
||||||
errorMessage: '',
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
<div className="shapeshift-form__address-input-error-message">
|
|
||||||
{errorMessage}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{this.renderMarketInfo()}
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
className={btnClass}
|
|
||||||
disabled={!token}
|
|
||||||
onClick={this.onBuyWithShapeShift}
|
|
||||||
>
|
|
||||||
Buy
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
({ metamask: { coinOptions, tokenExchangeRates, selectedAddress } }) => ({
|
|
||||||
coinOptions, tokenExchangeRates, selectedAddress,
|
|
||||||
}),
|
|
||||||
dispatch => ({
|
|
||||||
shapeShiftSubview: () => dispatch(shapeShiftSubview()),
|
|
||||||
pairUpdate: coin => dispatch(pairUpdate(coin)),
|
|
||||||
buyWithShapeShift: data => dispatch(buyWithShapeShift(data)),
|
|
||||||
}),
|
|
||||||
)(ShapeShiftForm)
|
|
|
@ -1,134 +0,0 @@
|
||||||
global.window = global
|
|
||||||
|
|
||||||
const SwGlobalListener = require('sw-stream/lib/sw-global-listener.js')
|
|
||||||
const connectionListener = new SwGlobalListener(global)
|
|
||||||
const setupMultiplex = require('../../app/scripts/lib/stream-utils.js').setupMultiplex
|
|
||||||
|
|
||||||
const DbController = require('idb-global')
|
|
||||||
|
|
||||||
const SwPlatform = require('../../app/scripts/platforms/sw')
|
|
||||||
const MetamaskController = require('../../app/scripts/metamask-controller')
|
|
||||||
|
|
||||||
const Migrator = require('../../app/scripts/lib/migrator/')
|
|
||||||
const migrations = require('../../app/scripts/migrations/')
|
|
||||||
const firstTimeState = require('../../app/scripts/first-time-state')
|
|
||||||
|
|
||||||
const STORAGE_KEY = 'metamask-config'
|
|
||||||
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
|
|
||||||
global.metamaskPopupIsOpen = false
|
|
||||||
|
|
||||||
const log = require('loglevel')
|
|
||||||
global.log = log
|
|
||||||
log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn')
|
|
||||||
|
|
||||||
global.addEventListener('install', function (event) {
|
|
||||||
event.waitUntil(global.skipWaiting())
|
|
||||||
})
|
|
||||||
global.addEventListener('activate', function (event) {
|
|
||||||
event.waitUntil(global.clients.claim())
|
|
||||||
})
|
|
||||||
|
|
||||||
log.debug('inside:open')
|
|
||||||
|
|
||||||
// state persistence
|
|
||||||
const dbController = new DbController({
|
|
||||||
key: STORAGE_KEY,
|
|
||||||
})
|
|
||||||
|
|
||||||
start().catch(log.error)
|
|
||||||
|
|
||||||
async function start () {
|
|
||||||
log.debug('Nifty Wallet initializing...')
|
|
||||||
const initState = await loadStateFromPersistence()
|
|
||||||
await setupController(initState)
|
|
||||||
log.debug('Nifty Wallet initialization complete.')
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// State and Persistence
|
|
||||||
//
|
|
||||||
async function loadStateFromPersistence () {
|
|
||||||
// migrations
|
|
||||||
const migrator = new Migrator({ migrations })
|
|
||||||
const initialState = migrator.generateInitialState(firstTimeState)
|
|
||||||
dbController.initialState = initialState
|
|
||||||
const versionedData = await dbController.open()
|
|
||||||
const migratedData = await migrator.migrateData(versionedData)
|
|
||||||
await dbController.put(migratedData)
|
|
||||||
return migratedData.data
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setupController (initState, client) {
|
|
||||||
|
|
||||||
//
|
|
||||||
// MetaMask Controller
|
|
||||||
//
|
|
||||||
|
|
||||||
const platform = new SwPlatform()
|
|
||||||
|
|
||||||
const controller = new MetamaskController({
|
|
||||||
// platform specific implementation
|
|
||||||
platform,
|
|
||||||
// User confirmation callbacks:
|
|
||||||
showUnconfirmedMessage: noop,
|
|
||||||
unlockAccountMessage: noop,
|
|
||||||
showUnapprovedTx: noop,
|
|
||||||
// initial state
|
|
||||||
initState,
|
|
||||||
})
|
|
||||||
global.metamaskController = controller
|
|
||||||
|
|
||||||
controller.store.subscribe(async (state) => {
|
|
||||||
try {
|
|
||||||
const versionedData = await versionifyData(state)
|
|
||||||
await dbController.put(versionedData)
|
|
||||||
} catch (e) { console.error('METAMASK Error:', e) }
|
|
||||||
})
|
|
||||||
|
|
||||||
async function versionifyData (state) {
|
|
||||||
const rawData = await dbController.get()
|
|
||||||
return {
|
|
||||||
data: state,
|
|
||||||
meta: rawData.meta,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// connect to other contexts
|
|
||||||
//
|
|
||||||
|
|
||||||
connectionListener.on('remote', (portStream, messageEvent) => {
|
|
||||||
log.debug('REMOTE CONECTION FOUND***********')
|
|
||||||
connectRemote(portStream, messageEvent.data.context)
|
|
||||||
})
|
|
||||||
|
|
||||||
function connectRemote (connectionStream, context) {
|
|
||||||
var isMetaMaskInternalProcess = (context === 'popup')
|
|
||||||
if (isMetaMaskInternalProcess) {
|
|
||||||
// communication with popup
|
|
||||||
controller.setupTrustedCommunication(connectionStream, 'MetaMask')
|
|
||||||
global.metamaskPopupIsOpen = true
|
|
||||||
} else {
|
|
||||||
// communication with page
|
|
||||||
setupUntrustedCommunication(connectionStream, context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupUntrustedCommunication (connectionStream, originDomain) {
|
|
||||||
// setup multiplexing
|
|
||||||
var mx = setupMultiplex(connectionStream)
|
|
||||||
// connect features
|
|
||||||
controller.setupProviderConnection(mx.createStream('provider'), originDomain)
|
|
||||||
controller.setupPublicConfig(mx.createStream('publicConfig'))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// // this will be useful later but commented out for linting for now (liiiinting)
|
|
||||||
// function sendMessageToAllClients (message) {
|
|
||||||
// global.clients.matchAll().then(function (clients) {
|
|
||||||
// clients.forEach(function (client) {
|
|
||||||
// client.postMessage(message)
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
|
|
||||||
function noop () {}
|
|
|
@ -1 +0,0 @@
|
||||||
global.metamask = require('metamascara')
|
|
|
@ -1,25 +0,0 @@
|
||||||
const createParentStream = require('iframe-stream').ParentStream
|
|
||||||
const SwController = require('sw-controller')
|
|
||||||
const SwStream = require('sw-stream/lib/sw-stream.js')
|
|
||||||
|
|
||||||
const keepAliveDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000
|
|
||||||
const background = new SwController({
|
|
||||||
fileName: './scripts/background.js',
|
|
||||||
keepAlive: true,
|
|
||||||
keepAliveInterval: 30000,
|
|
||||||
keepAliveDelay,
|
|
||||||
})
|
|
||||||
|
|
||||||
const pageStream = createParentStream()
|
|
||||||
background.on('ready', () => {
|
|
||||||
const swStream = SwStream({
|
|
||||||
serviceWorker: background.controller,
|
|
||||||
context: 'dapp',
|
|
||||||
})
|
|
||||||
pageStream.pipe(swStream).pipe(pageStream)
|
|
||||||
|
|
||||||
})
|
|
||||||
background.on('updatefound', () => window.location.reload())
|
|
||||||
|
|
||||||
background.on('error', console.error)
|
|
||||||
background.startWorker()
|
|
|
@ -1,73 +0,0 @@
|
||||||
const injectCss = require('inject-css')
|
|
||||||
const SwController = require('sw-controller')
|
|
||||||
const SwStream = require('sw-stream')
|
|
||||||
const MetaMaskUiCss = require('../../old-ui/css')
|
|
||||||
const MetamascaraPlatform = require('../../app/scripts/platforms/window')
|
|
||||||
const startPopup = require('../../app/scripts/popup-core')
|
|
||||||
|
|
||||||
// create platform global
|
|
||||||
global.platform = new MetamascaraPlatform()
|
|
||||||
|
|
||||||
var css = MetaMaskUiCss()
|
|
||||||
injectCss(css)
|
|
||||||
const container = document.getElementById('app-content')
|
|
||||||
|
|
||||||
const name = 'popup'
|
|
||||||
window.METAMASK_UI_TYPE = name
|
|
||||||
window.METAMASK_PLATFORM_TYPE = 'mascara'
|
|
||||||
|
|
||||||
const keepAliveDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000
|
|
||||||
|
|
||||||
const swController = new SwController({
|
|
||||||
fileName: './background.js',
|
|
||||||
keepAlive: true,
|
|
||||||
keepAliveDelay,
|
|
||||||
keepAliveInterval: 20000,
|
|
||||||
})
|
|
||||||
|
|
||||||
swController.once('updatefound', windowReload)
|
|
||||||
swController.once('ready', async () => {
|
|
||||||
try {
|
|
||||||
swController.removeListener('updatefound', windowReload)
|
|
||||||
console.log('swController ready')
|
|
||||||
await timeout(1000)
|
|
||||||
console.log('connecting to app')
|
|
||||||
await connectApp()
|
|
||||||
console.log('app connected')
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log('starting service worker')
|
|
||||||
swController.startWorker()
|
|
||||||
|
|
||||||
// Setup listener for when the service worker is read
|
|
||||||
function connectApp () {
|
|
||||||
const connectionStream = SwStream({
|
|
||||||
serviceWorker: swController.getWorker(),
|
|
||||||
context: name,
|
|
||||||
})
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
startPopup({ container, connectionStream }, (err, store) => {
|
|
||||||
console.log('hello from MetaMascara ui!')
|
|
||||||
if (err) reject(err)
|
|
||||||
store.subscribe(() => {
|
|
||||||
const state = store.getState()
|
|
||||||
if (state.appState.shouldClose) window.close()
|
|
||||||
})
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function windowReload () {
|
|
||||||
if (window.METAMASK_SKIP_RELOAD) return
|
|
||||||
window.location.reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
function timeout (time) {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(resolve, time || 1500)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
export default function wait (time) {
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
setTimeout(function () {
|
|
||||||
resolve()
|
|
||||||
}, time * 3 || 1500)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
var fs = require('fs')
|
|
||||||
var path = require('path')
|
|
||||||
var browserify = require('browserify')
|
|
||||||
var tests = fs.readdirSync(path.join(__dirname, 'lib'))
|
|
||||||
var bundlePath = path.join(__dirname, 'test-bundle.js')
|
|
||||||
var b = browserify()
|
|
||||||
|
|
||||||
// Remove old bundle
|
|
||||||
try {
|
|
||||||
fs.unlinkSync(bundlePath)
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
var writeStream = fs.createWriteStream(bundlePath)
|
|
||||||
|
|
||||||
tests.forEach(function (fileName) {
|
|
||||||
b.add(path.join(__dirname, 'lib', fileName))
|
|
||||||
})
|
|
||||||
|
|
||||||
b.bundle().pipe(writeStream)
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,10 +0,0 @@
|
||||||
window.addEventListener('load', () => {
|
|
||||||
window.METAMASK_SKIP_RELOAD = true
|
|
||||||
// inject app container
|
|
||||||
const body = document.body
|
|
||||||
const container = document.createElement('div')
|
|
||||||
container.id = 'app-content'
|
|
||||||
body.appendChild(container)
|
|
||||||
// start ui
|
|
||||||
require('../src/ui.js')
|
|
||||||
})
|
|
|
@ -1,42 +0,0 @@
|
||||||
const EventEmitter = require('events')
|
|
||||||
const IDB = require('idb-global')
|
|
||||||
const KEY = 'metamask-test-config'
|
|
||||||
module.exports = class Helper extends EventEmitter {
|
|
||||||
|
|
||||||
tryToCleanContext () {
|
|
||||||
this.unregister()
|
|
||||||
.then(() => this.clearDb())
|
|
||||||
.then(() => super.emit('complete'))
|
|
||||||
.catch((err) => {
|
|
||||||
if (err) {
|
|
||||||
super.emit('complete')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
unregister () {
|
|
||||||
return global.navigator.serviceWorker.getRegistration()
|
|
||||||
.then((registration) => {
|
|
||||||
if (registration) {
|
|
||||||
return registration.unregister()
|
|
||||||
.then((b) => b ? Promise.resolve() : Promise.reject())
|
|
||||||
} else return Promise.resolve()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
clearDb () {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const deleteRequest = global.indexDB.deleteDatabase(KEY)
|
|
||||||
deleteRequest.addEventListener('success', resolve)
|
|
||||||
deleteRequest.addEventListener('error', reject)
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
mockState (state) {
|
|
||||||
const db = new IDB({
|
|
||||||
version: 2,
|
|
||||||
key: KEY,
|
|
||||||
initialState: state,
|
|
||||||
})
|
|
||||||
return db.open()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>MetaMascara Alpha</title>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app-content"></div>
|
|
||||||
<script src="./scripts/ui.js" type="text/javascript" charset="utf-8"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -6,9 +6,6 @@ const { compose } = require('recompose')
|
||||||
const h = require('react-hyperscript')
|
const h = require('react-hyperscript')
|
||||||
const actions = require('../../ui/app/actions')
|
const actions = require('../../ui/app/actions')
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
// mascara
|
|
||||||
const MascaraFirstTime = require('../../mascara/src/app/first-time').default
|
|
||||||
const MascaraBuyEtherScreen = require('../../mascara/src/app/first-time/buy-ether-screen').default
|
|
||||||
// init
|
// init
|
||||||
const InitializeMenuScreen = require('./first-time/init-menu')
|
const InitializeMenuScreen = require('./first-time/init-menu')
|
||||||
const NewKeyChainScreen = require('./new-keychain')
|
const NewKeyChainScreen = require('./new-keychain')
|
||||||
|
@ -81,7 +78,6 @@ function mapStateToProps (state) {
|
||||||
currentView: state.appState.currentView,
|
currentView: state.appState.currentView,
|
||||||
selectedAddress: state.metamask.selectedAddress,
|
selectedAddress: state.metamask.selectedAddress,
|
||||||
transForward: state.appState.transForward,
|
transForward: state.appState.transForward,
|
||||||
isMascara: state.metamask.isMascara,
|
|
||||||
isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized),
|
isOnboarding: Boolean(!noActiveNotices || seedWords || !isInitialized),
|
||||||
seedWords: state.metamask.seedWords,
|
seedWords: state.metamask.seedWords,
|
||||||
unapprovedTxs: state.metamask.unapprovedTxs,
|
unapprovedTxs: state.metamask.unapprovedTxs,
|
||||||
|
@ -151,11 +147,7 @@ App.prototype.render = function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
App.prototype.renderLoadingIndicator = function ({ isLoading, isLoadingNetwork, loadMessage }) {
|
App.prototype.renderLoadingIndicator = function ({ isLoading, isLoadingNetwork, loadMessage }) {
|
||||||
const { isMascara } = this.props
|
return h(Loading, {
|
||||||
|
|
||||||
return isMascara
|
|
||||||
? null
|
|
||||||
: h(Loading, {
|
|
||||||
isLoading: isLoading || isLoadingNetwork,
|
isLoading: isLoading || isLoadingNetwork,
|
||||||
loadingMessage: loadMessage,
|
loadingMessage: loadMessage,
|
||||||
})
|
})
|
||||||
|
@ -164,11 +156,6 @@ App.prototype.renderLoadingIndicator = function ({ isLoading, isLoadingNetwork,
|
||||||
App.prototype.renderPrimary = function () {
|
App.prototype.renderPrimary = function () {
|
||||||
log.debug('rendering primary')
|
log.debug('rendering primary')
|
||||||
const props = this.props
|
const props = this.props
|
||||||
const {isMascara, isOnboarding} = props
|
|
||||||
|
|
||||||
if (isMascara && isOnboarding) {
|
|
||||||
return h(MascaraFirstTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
// notices
|
// notices
|
||||||
if (!props.noActiveNotices) {
|
if (!props.noActiveNotices) {
|
||||||
|
@ -307,10 +294,6 @@ App.prototype.renderPrimary = function () {
|
||||||
log.debug('rendering buy ether screen')
|
log.debug('rendering buy ether screen')
|
||||||
return h(BuyView, {key: 'buyEthView'})
|
return h(BuyView, {key: 'buyEthView'})
|
||||||
|
|
||||||
case 'onboardingBuyEth':
|
|
||||||
log.debug('rendering onboarding buy ether screen')
|
|
||||||
return h(MascaraBuyEtherScreen, {key: 'buyEthView'})
|
|
||||||
|
|
||||||
case 'qr':
|
case 'qr':
|
||||||
log.debug('rendering show qr screen')
|
log.debug('rendering show qr screen')
|
||||||
return h('div', {
|
return h('div', {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue