Merge branch 'master' into mascara-deploy
5
.babelrc
@ -1 +1,4 @@
|
||||
{ "presets": ["es2015"] }
|
||||
{
|
||||
"presets": ["es2015", "stage-0", "react"],
|
||||
"plugins": ["transform-runtime", "transform-async-to-generator"]
|
||||
}
|
||||
|
@ -1 +1,6 @@
|
||||
app/scripts/lib/extension-instance.js
|
||||
test/integration/bundle.js
|
||||
test/integration/jquery-3.1.0.min.js
|
||||
test/integration/helpers.js
|
||||
test/integration/lib/first-time.js
|
||||
ui/lib/blockies.js
|
21
.eslintrc
@ -1,7 +1,8 @@
|
||||
{
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaVersion": 6,
|
||||
"ecmaVersion": 2017,
|
||||
"ecmaFeatures": {
|
||||
"experimentalObjectRestSpread": true,
|
||||
"impliedStrict": true,
|
||||
@ -10,17 +11,25 @@
|
||||
"arrowFunctions": true,
|
||||
"objectLiteralShorthandMethods": true,
|
||||
"objectLiteralShorthandProperties": true,
|
||||
"templateStrings": true
|
||||
"templateStrings": true,
|
||||
"classes": true,
|
||||
"jsx": true
|
||||
},
|
||||
},
|
||||
|
||||
"extends": ["plugin:react/recommended"],
|
||||
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true,
|
||||
"browser": true
|
||||
"browser": true,
|
||||
"mocha" : true
|
||||
},
|
||||
|
||||
"plugins": [
|
||||
"mocha",
|
||||
"chai",
|
||||
"react"
|
||||
],
|
||||
|
||||
"globals": {
|
||||
@ -47,8 +56,8 @@
|
||||
"eqeqeq": [2, "allow-null"],
|
||||
"generator-star-spacing": [2, { "before": true, "after": true }],
|
||||
"handle-callback-err": [1, "^(err|error)$" ],
|
||||
"indent": [2, 2, { "SwitchCase": 1 }],
|
||||
"jsx-quotes": [2, "prefer-single"],
|
||||
"indent": "off",
|
||||
"jsx-quotes": [2, "prefer-double"],
|
||||
"key-spacing": 1,
|
||||
"keyword-spacing": [2, { "before": true, "after": true }],
|
||||
"new-cap": [2, { "newIsCap": true, "capIsNew": false }],
|
||||
@ -130,7 +139,7 @@
|
||||
"no-with": 2,
|
||||
"one-var": [2, { "initialized": "never" }],
|
||||
"operator-linebreak": [1, "after", { "overrides": { "?": "ignore", ":": "ignore" } }],
|
||||
"padded-blocks": [1, "never"],
|
||||
"padded-blocks": "off",
|
||||
"quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}],
|
||||
"semi": [2, "never"],
|
||||
"semi-spacing": [2, { "before": false, "after": true }],
|
||||
|
33
.gitignore
vendored
@ -1,18 +1,35 @@
|
||||
dist
|
||||
npm-debug.log
|
||||
node_modules
|
||||
temp
|
||||
.tmp
|
||||
.sass-cache
|
||||
package-lock.json
|
||||
|
||||
app/bower_components
|
||||
test/bower_components
|
||||
package
|
||||
|
||||
.idea
|
||||
|
||||
temp
|
||||
.tmp
|
||||
.sass-cache
|
||||
.DS_Store
|
||||
app/.DS_Store
|
||||
|
||||
dist
|
||||
builds/
|
||||
disc/
|
||||
notes.txt
|
||||
app/.DS_Store
|
||||
development/bundle.js
|
||||
builds.zip
|
||||
test/integration/bundle.js
|
||||
|
||||
development/bundle.js
|
||||
development/states.js
|
||||
test/integration/bundle.js
|
||||
test/background.js
|
||||
test/bundle.js
|
||||
test/test-bundle.js
|
||||
|
||||
#ignore css output and sourcemaps
|
||||
ui/app/css/output/
|
||||
|
||||
notes.txt
|
||||
|
||||
.coveralls.yml
|
||||
.nyc_output
|
10
.stylelintignore
Normal file
@ -0,0 +1,10 @@
|
||||
app/
|
||||
development/
|
||||
dist/
|
||||
docs/
|
||||
fonts/
|
||||
images/
|
||||
mascara/
|
||||
node_modules/
|
||||
notices/
|
||||
test/
|
50
.stylelintrc
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"extends": "stylelint-config-standard",
|
||||
"rules": {
|
||||
"color-named": "never",
|
||||
"font-family-name-quotes": "always-where-recommended",
|
||||
"font-weight-notation": "numeric",
|
||||
"function-url-quotes": "always",
|
||||
"number-leading-zero": "never",
|
||||
"value-no-vendor-prefix": true,
|
||||
"value-list-comma-newline-before": "never-multi-line",
|
||||
"custom-property-empty-line-before": "never",
|
||||
"property-no-unknown": [
|
||||
true,
|
||||
{
|
||||
"ignoreProperties": [
|
||||
"composes",
|
||||
"all",
|
||||
"-webkit-appearance"
|
||||
]
|
||||
}
|
||||
],
|
||||
"declaration-block-semicolon-newline-after": "always",
|
||||
"block-opening-brace-newline-after": "always",
|
||||
"selector-attribute-quotes": "always",
|
||||
"selector-max-specificity": "0,5,2",
|
||||
"selector-pseudo-class-no-unknown": [
|
||||
true,
|
||||
{
|
||||
"ignorePseudoClasses": ["local", "global"]
|
||||
}
|
||||
],
|
||||
"at-rule-empty-line-before": [
|
||||
"always",
|
||||
{
|
||||
"ignore": [
|
||||
"after-comment",
|
||||
]
|
||||
}
|
||||
],
|
||||
"indentation": [
|
||||
2,
|
||||
{
|
||||
"indentInsideParens": "once-at-root-twice-in-block"
|
||||
}
|
||||
],
|
||||
"max-nesting-depth": 3,
|
||||
"no-duplicate-selectors": true,
|
||||
"no-unknown-animations": true
|
||||
}
|
||||
}
|
409
CHANGELOG.md
@ -2,8 +2,405 @@
|
||||
|
||||
## Current Master
|
||||
|
||||
## 4.1.0 2018-2-27
|
||||
|
||||
- Report failed txs to Sentry with more specific message
|
||||
- Fix internal feature flags being sometimes undefined
|
||||
- Standardized license to MIT
|
||||
|
||||
## 4.0.0 2018-2-22
|
||||
|
||||
- Introduce new MetaMask user interface.
|
||||
|
||||
## 3.14.2 2018-2-15
|
||||
|
||||
- Fix bug where log subscriptions would break when switching network.
|
||||
- Fix bug where storage values were cached across blocks.
|
||||
- Add MetaMask light client [testing container](https://github.com/MetaMask/mesh-testing)
|
||||
|
||||
## 3.14.1 2018-2-1
|
||||
|
||||
- Further fix scrolling for Firefox.
|
||||
|
||||
## 3.14.0 2018-2-1
|
||||
|
||||
- Removed unneeded data from storage
|
||||
- Add a "reset account" feature to Settings
|
||||
- Add warning for importing some kinds of files.
|
||||
- Scrollable Setting view for Firefox.
|
||||
|
||||
## 3.13.8 2018-1-29
|
||||
|
||||
- Fix provider for Kovan network.
|
||||
- Bump limit for EventEmitter listeners before warning.
|
||||
- Display Error when empty string is entered as a token address.
|
||||
|
||||
## 3.13.7 2018-1-22
|
||||
|
||||
- Add ability to bypass gas estimation loading indicator.
|
||||
- Forward failed transactions to Sentry error reporting service
|
||||
- Re-add changes from 3.13.5
|
||||
|
||||
## 3.13.6 2017-1-18
|
||||
|
||||
- Roll back changes to 3.13.4 to fix some issues with the new Infura REST provider.
|
||||
|
||||
## 3.13.5 2018-1-16
|
||||
|
||||
- Estimating gas limit for simple ether sends now faster & cheaper, by avoiding VM usage on recipients with no code.
|
||||
- Add an extra px to address for Firefox clipping.
|
||||
- Fix Firefox scrollbar.
|
||||
- Open metamask popup for transaction confirmation before gas estimation finishes and add a loading screen over transaction confirmation.
|
||||
- Fix bug that prevented eth_signTypedData from signing bytes.
|
||||
- Further improve gas price estimation.
|
||||
|
||||
## 3.13.4 2018-1-9
|
||||
|
||||
- Remove recipient field if application initializes a tx with an empty string, or 0x, and tx data. Throw an error with the same condition, but without tx data.
|
||||
- Improve gas price suggestion to be closer to the lowest that will be accepted.
|
||||
- Throw an error if a application tries to submit a tx whose value is a decimal, and inform that it should be in wei.
|
||||
- Fix bug that prevented updating custom token details.
|
||||
- No longer mark long-pending transactions as failed, since we now have button to retry with higher gas.
|
||||
- Fix rounding error when specifying an ether amount that has too much precision.
|
||||
- Fix bug where incorrectly inputting seed phrase would prevent any future attempts from succeeding.
|
||||
|
||||
## 3.13.3 2017-12-14
|
||||
|
||||
- Show tokens that are held that have no balance.
|
||||
- Reduce load on Infura by using a new block polling endpoint.
|
||||
|
||||
## 3.13.2 2017-12-9
|
||||
|
||||
- Reduce new block polling interval to 8000 ms, to ease server load.
|
||||
|
||||
## 3.13.1 2017-12-7
|
||||
|
||||
- Allow Dapps to specify a transaction nonce, allowing dapps to propose resubmit and force-cancel transactions.
|
||||
|
||||
## 3.13.0 2017-12-7
|
||||
|
||||
- Allow resubmitting transactions that are taking long to complete.
|
||||
|
||||
## 3.12.1 2017-11-29
|
||||
|
||||
- Fix bug where a user could be shown two different seed phrases.
|
||||
- Detect when multiple web3 extensions are active, and provide useful error.
|
||||
- Adds notice about seed phrase backup.
|
||||
|
||||
## 3.12.0 2017-10-25
|
||||
|
||||
- Add support for alternative ENS TLDs (Ethereum Name Service Top-Level Domains).
|
||||
- Lower minimum gas price to 0.1 GWEI.
|
||||
- Remove web3 injection message from production (thanks to @ChainsawBaby)
|
||||
- Add additional debugging info to our state logs, specifically OS version and browser version.
|
||||
|
||||
## 3.11.2 2017-10-21
|
||||
|
||||
- Fix bug where reject button would sometimes not work.
|
||||
- Fixed bug where sometimes MetaMask's connection to a page would be unreliable.
|
||||
|
||||
## 3.11.1 2017-10-20
|
||||
|
||||
- Fix bug where log filters were not populated correctly
|
||||
- Fix bug where web3 API was sometimes injected after the page loaded.
|
||||
- Fix bug where first account was sometimes not selected correctly after creating or restoring a vault.
|
||||
- Fix bug where imported accounts could not use new eth_signTypedData method.
|
||||
|
||||
## 3.11.0 2017-10-11
|
||||
|
||||
- Add support for new eth_signTypedData method per EIP 712.
|
||||
- Fix bug where some transactions would be shown as pending forever, even after successfully mined.
|
||||
- Fix bug where a transaction might be shown as pending forever if another tx with the same nonce was mined.
|
||||
- Fix link to support article on token addresses.
|
||||
|
||||
## 3.10.9 2017-10-5
|
||||
|
||||
- Only rebrodcast transactions for a day not a days worth of blocks
|
||||
- Remove Slack link from info page, since it is a big phishing target.
|
||||
- Stop computing balance based on pending transactions, to avoid edge case where users are unable to send transactions.
|
||||
|
||||
## 3.10.8 2017-9-28
|
||||
|
||||
- Fixed usage of new currency fetching API.
|
||||
|
||||
## 3.10.7 2017-9-28
|
||||
|
||||
- Fixed bug where sometimes the current account was not correctly set and exposed to web apps.
|
||||
- Added AUD, HKD, SGD, IDR, PHP to currency conversion list
|
||||
|
||||
## 3.10.6 2017-9-27
|
||||
|
||||
- Fix bug where newly created accounts were not selected.
|
||||
- Fix bug where selected account was not persisted between lockings.
|
||||
|
||||
## 3.10.5 2017-9-27
|
||||
|
||||
- Fix block gas limit estimation.
|
||||
|
||||
## 3.10.4 2017-9-27
|
||||
|
||||
- Fix bug that could mis-render token balances when very small. (Not actually included in 3.9.9)
|
||||
- Fix memory leak warning.
|
||||
- Fix bug where new event filters would not include historical events.
|
||||
|
||||
## 3.10.3 2017-9-21
|
||||
|
||||
- Fix bug where metamask-dapp connections are lost on rpc error
|
||||
- Fix bug that would sometimes display transactions as failed that could be successfully mined.
|
||||
|
||||
## 3.10.2 2017-9-18
|
||||
|
||||
rollback to 3.10.0 due to bug
|
||||
|
||||
## 3.10.1 2017-9-18
|
||||
|
||||
- Add ability to export private keys as a file.
|
||||
- Add ability to export seed words as a file.
|
||||
- Changed state logs to a file download than a clipboard copy.
|
||||
- Add specific error for failed recipient address checksum.
|
||||
- Fixed a long standing memory leak associated with filters installed by dapps
|
||||
- Fix link to support center.
|
||||
- Fixed tooltip icon locations to avoid overflow.
|
||||
- Warn users when a dapp proposes a high gas limit (90% of blockGasLimit or higher
|
||||
- Sort currencies by currency name (thanks to strelok1: https://github.com/strelok1).
|
||||
|
||||
## 3.10.0 2017-9-11
|
||||
|
||||
- Readded loose keyring label back into the account list.
|
||||
- Remove cryptonator from chrome permissions.
|
||||
- Add info on token contract addresses.
|
||||
- Add validation preventing users from inputting their own addresses as token tracking addresses.
|
||||
- Added button to reject all transactions (thanks to davidp94! https://github.com/davidp94)
|
||||
|
||||
|
||||
## 3.9.13 2017-9-8
|
||||
|
||||
- Changed the way we initialize the inpage provider to fix a bug affecting some developers.
|
||||
|
||||
## 3.9.12 2017-9-6
|
||||
|
||||
- Fix bug that prevented Web3 1.0 compatibility
|
||||
- Make eth_sign deprecation warning less noisy
|
||||
- Add useful link to eth_sign deprecation warning.
|
||||
- Fix bug with network version serialization over synchronous RPC
|
||||
- Add MetaMask version to state logs.
|
||||
- Add the total amount of tokens when multiple tokens are added under the token list
|
||||
- Use HTTPS links for Etherscan.
|
||||
- Update Support center link to new one with HTTPS.
|
||||
- Make web3 deprecation notice more useful by linking to a descriptive article.
|
||||
|
||||
## 3.9.11 2017-8-24
|
||||
|
||||
- Fix nonce calculation bug that would sometimes generate very wrong nonces.
|
||||
- Give up resubmitting a transaction after 3500 blocks.
|
||||
|
||||
## 3.9.10 2017-8-23
|
||||
|
||||
- Improve nonce calculation, to prevent bug where people are unable to send transactions reliably.
|
||||
- Remove link to eth-tx-viz from identicons in tx history.
|
||||
|
||||
## 3.9.9 2017-8-18
|
||||
|
||||
- Fix bug where some transaction submission errors would show an empty screen.
|
||||
- Fix bug that could mis-render token balances when very small.
|
||||
- Fix formatting of eth_sign "Sign Message" view.
|
||||
- Add deprecation warning to eth_sign "Sign Message" view.
|
||||
|
||||
## 3.9.8 2017-8-16
|
||||
|
||||
- Reenable token list.
|
||||
- Remove default tokens.
|
||||
|
||||
## 3.9.7 2017-8-15
|
||||
|
||||
- hotfix - disable token list
|
||||
- Added a deprecation warning for web3 https://github.com/ethereum/mist/releases/tag/v0.9.0
|
||||
|
||||
## 3.9.6 2017-8-09
|
||||
|
||||
- Replace account screen with an account drop-down menu.
|
||||
- Replace account buttons with a new account-specific drop-down menu.
|
||||
|
||||
## 3.9.5 2017-8-04
|
||||
|
||||
- Improved phishing detection configuration update rate
|
||||
|
||||
## 3.9.4 2017-8-03
|
||||
|
||||
- Fixed bug that prevented transactions from being rejected.
|
||||
|
||||
## 3.9.3 2017-8-03
|
||||
|
||||
- Add support for EGO ujo token
|
||||
- Continuously update blacklist for known phishing sites in background.
|
||||
- Automatically detect suspicious URLs too similar to common phishing targets, and blacklist them.
|
||||
|
||||
## 3.9.2 2017-7-26
|
||||
|
||||
- Fix bugs that could sometimes result in failed transactions after switching networks.
|
||||
- Include stack traces in txMeta's to better understand the life cycle of transactions
|
||||
- Enhance blacklister functionality to include levenshtein logic. (credit to @sogoiii and @409H for their help!)
|
||||
|
||||
## 3.9.1 2017-7-19
|
||||
|
||||
- No longer automatically request 1 ropsten ether for the first account in a new vault.
|
||||
- Now redirects from known malicious sites faster.
|
||||
- Added a link to our new support page to the help screen.
|
||||
- Fixed bug where a new transaction would be shown over the current transaction, creating a possible timing attack against user confirmation.
|
||||
- Fixed bug in nonce tracker where an incorrect nonce would be calculated.
|
||||
- Lowered minimum gas price to 1 Gwei.
|
||||
|
||||
## 3.9.0 2017-7-12
|
||||
|
||||
- Now detects and blocks known phishing sites.
|
||||
|
||||
## 3.8.6 2017-7-11
|
||||
|
||||
- Make transaction resubmission more resilient.
|
||||
- No longer validate nonce client-side in retry loop.
|
||||
- Fix bug where insufficient balance error was sometimes shown on successful transactions.
|
||||
|
||||
## 3.8.5 2017-7-7
|
||||
|
||||
- Fix transaction resubmit logic to fail slightly less eagerly.
|
||||
|
||||
## 3.8.4 2017-7-7
|
||||
|
||||
- Improve transaction resubmit logic to fail more eagerly when a user would expect it to.
|
||||
|
||||
## 3.8.3 2017-7-6
|
||||
|
||||
- Re-enable default token list.
|
||||
- Add origin header to dapp-bound requests to allow providers to throttle sites.
|
||||
- Fix bug that could sometimes resubmit a transaction that had been stalled due to low balance after balance was restored.
|
||||
|
||||
## 3.8.2 2017-7-3
|
||||
|
||||
- No longer show network loading indication on config screen, to allow selecting custom RPCs.
|
||||
- Visually indicate that network spinner is a menu.
|
||||
- Indicate what network is being searched for when disconnected.
|
||||
|
||||
## 3.8.1 2017-6-30
|
||||
|
||||
- Temporarily disabled loading popular tokens by default to improve performance.
|
||||
- Remove SEND token button until a better token sending form can be built, due to some precision issues.
|
||||
- Fix precision bug in token balances.
|
||||
- Cache token symbol and precisions to reduce network load.
|
||||
- Transpile some newer JavaScript, restores compatibility with some older browsers.
|
||||
|
||||
## 3.8.0 2017-6-28
|
||||
|
||||
- No longer stop rebroadcasting transactions
|
||||
- Add list of popular tokens held to the account detail view.
|
||||
- Add ability to add Tokens to token list.
|
||||
- Add a warning to JSON file import.
|
||||
- Add "send" link to token list, which goes to TokenFactory.
|
||||
- Fix bug where slowly mined txs would sometimes be incorrectly marked as failed.
|
||||
- Fix bug where badge count did not reflect personal_sign pending messages.
|
||||
- Seed word confirmation wording is now scarier.
|
||||
- Fix error for invalid seed words.
|
||||
- Prevent users from submitting two duplicate transactions by disabling submit.
|
||||
- Allow Dapps to specify gas price as hex string.
|
||||
- Add button for copying state logs to clipboard.
|
||||
|
||||
## 3.7.8 2017-6-12
|
||||
|
||||
- Add an `ethereum:` prefix to the QR code address
|
||||
- The default network on installation is now MainNet
|
||||
- Fix currency API URL from cryptonator.
|
||||
- Update gasLimit params with every new block seen.
|
||||
- Fix ENS resolver symbol UI.
|
||||
|
||||
## 3.7.7 2017-6-8
|
||||
|
||||
- Fix bug where metamask would show old data after computer being asleep or disconnected from the internet.
|
||||
|
||||
## 3.7.6 2017-6-5
|
||||
|
||||
- Fix bug that prevented publishing contracts.
|
||||
|
||||
## 3.7.5 2017-6-5
|
||||
|
||||
- Prevent users from sending to the `0x0` address.
|
||||
- Provide useful errors when entering bad characters in ENS name.
|
||||
- Add ability to copy addresses from transaction confirmation view.
|
||||
|
||||
## 3.7.4 2017-6-2
|
||||
|
||||
- Fix bug with inflight cache that caused some block lookups to return bad values (affected OasisDex).
|
||||
- Fixed bug with gas limit calculation that would sometimes create unsubmittable gas limits.
|
||||
|
||||
## 3.7.3 2017-6-1
|
||||
|
||||
- Rebuilt to fix cache clearing bug.
|
||||
|
||||
## 3.7.2 2017-5-31
|
||||
|
||||
- Now when switching networks sites that use web3 will reload
|
||||
- Now when switching networks the extension does not restart
|
||||
- Cleanup decimal bugs in our gas inputs.
|
||||
- Fix bug where submit button was enabled for invalid gas inputs.
|
||||
- Now enforce 95% of block's gasLimit to protect users.
|
||||
- Removing provider-engine from the inpage provider. This fixes some error handling inconsistencies introduced in 3.7.0.
|
||||
- Added "inflight cache", which prevents identical requests from clogging up the network, dramatically improving ENS performance.
|
||||
- Fixed bug where filter subscriptions would sometimes fail to unsubscribe.
|
||||
- Some contracts will now display logos instead of jazzicons.
|
||||
- Some contracts will now have names displayed in the confirmation view.
|
||||
|
||||
## 3.7.0 2017-5-23
|
||||
|
||||
- Add Transaction Number (nonce) to transaction list.
|
||||
- Label the pending tx icon with a tooltip.
|
||||
- Fix bug where website filters would pile up and not deallocate when leaving a site.
|
||||
- Continually resubmit pending txs for a period of time to ensure successful broadcast.
|
||||
- ENS names will no longer resolve to their owner if no resolver is set. Resolvers must be explicitly set and configured.
|
||||
|
||||
## 3.6.5 2017-5-17
|
||||
|
||||
- Fix bug where edited gas parameters would not take effect.
|
||||
- Trim currency list.
|
||||
- Enable decimals in our gas prices.
|
||||
- Fix reset button.
|
||||
- Fix event filter bug introduced by newer versions of Geth.
|
||||
- Fix bug where decimals in gas inputs could result in strange values.
|
||||
|
||||
## 3.6.4 2017-5-8
|
||||
|
||||
- Fix main-net ENS resolution.
|
||||
|
||||
## 3.6.3 2017-5-8
|
||||
|
||||
- Fix bug that could stop newer versions of Geth from working with MetaMask.
|
||||
|
||||
## 3.6.2 2017-5-8
|
||||
|
||||
- Input gas price in Gwei.
|
||||
- Enforce Safe Gas Minimum recommended by EthGasStation.
|
||||
- Fix bug where block-tracker could stop polling for new blocks.
|
||||
- Reduce UI size by removing internal web3.
|
||||
- Fix bug where gas parameters would not properly update on adjustment.
|
||||
|
||||
## 3.6.1 2017-4-30
|
||||
|
||||
- Made fox less nosy.
|
||||
- Fix bug where error was reported in debugger console when Chrome opened a new window.
|
||||
|
||||
## 3.6.0 2017-4-26
|
||||
|
||||
- Add Rinkeby Test Network to our network list.
|
||||
|
||||
## 3.5.4 2017-4-25
|
||||
|
||||
- Fix occasional nonce tracking issue.
|
||||
- Fix bug where some events would not be emitted by web3.
|
||||
- Fix bug where an error would be thrown when composing signatures for networks with large ID values.
|
||||
|
||||
## 3.5.3 2017-4-24
|
||||
|
||||
- Popup new transactions in Firefox.
|
||||
- Fix transition issue from account detail screen.
|
||||
- Revise buy screen for more modularity.
|
||||
- Fixed some other small bugs.
|
||||
|
||||
## 3.5.2 2017-3-28
|
||||
|
||||
@ -64,7 +461,7 @@
|
||||
|
||||
- Add ability to import accounts in JSON file format (used by Mist, Geth, MyEtherWallet, and more!)
|
||||
- Fix unapproved messages not being included in extension badge.
|
||||
- Fix rendering bug where the Confirm transaction view would lets you approve transactions when the account has insufficient balance.
|
||||
- Fix rendering bug where the Confirm transaction view would let you approve transactions when the account has insufficient balance.
|
||||
|
||||
## 3.1.2 2017-1-24
|
||||
|
||||
@ -87,8 +484,8 @@
|
||||
## 3.0.0 2017-1-16
|
||||
|
||||
- Fix seed word account generation (https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd#.t4i1qmmsz).
|
||||
- Fix Bug where you see a empty transaction flash by on the confirm transaction view.
|
||||
- Create visible difference in transaction history between a approved but not yet included in a block transaction and a transaction who has been confirmed.
|
||||
- Fix Bug where you see an empty transaction flash by on the confirm transaction view.
|
||||
- Create visible difference in transaction history between an approved but not yet included in a block transaction and a transaction who has been confirmed.
|
||||
- Fix memory leak in RPC Cache
|
||||
- Override RPC commands eth_syncing and web3_clientVersion
|
||||
- Remove certain non-essential permissions from certain builds.
|
||||
@ -143,7 +540,7 @@
|
||||
|
||||
- Fix bug where gas estimate would sometimes be very high.
|
||||
- Increased our gas estimate from 100k gas to 20% of estimate.
|
||||
- Fix github link on info page to point at current repository.
|
||||
- Fix GitHub link on info page to point at current repository.
|
||||
|
||||
## 2.13.6 2016-10-26
|
||||
|
||||
@ -219,7 +616,7 @@ popup notification opens up.
|
||||
- Block negative values from transactions.
|
||||
- Fixed a memory leak.
|
||||
- MetaMask logo now renders as super lightweight SVG, improving compatibility and performance.
|
||||
- Now showing loading indication during vault unlocking, to clarify behavior for users who are experience slow unlocks.
|
||||
- Now showing loading indication during vault unlocking, to clarify behavior for users who are experiencing slow unlocks.
|
||||
- Now only initially creates one wallet when restoring a vault, to reduce some users' confusion.
|
||||
|
||||
## 2.10.2 2016-09-02
|
||||
@ -251,7 +648,7 @@ popup notification opens up.
|
||||
- Added info link on account screen that visits Etherscan.
|
||||
- Fixed bug where a message signing request would be lost if the vault was locked.
|
||||
- Added shortcut to open MetaMask (Ctrl+Alt+M or Cmd+Opt/Alt+M)
|
||||
- Prevent API calls in tests.
|
||||
- Prevent API calls in tests.
|
||||
- Fixed bug where sign message confirmation would sometimes render blank.
|
||||
|
||||
## 2.9.0 2016-08-22
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM node:6
|
||||
FROM node:7
|
||||
MAINTAINER kumavis
|
||||
|
||||
# setup app dir
|
||||
|
15
ISSUE_TEMPLATE
Normal file
@ -0,0 +1,15 @@
|
||||
<!--
|
||||
|
||||
BEFORE SUBMITTING, please make sure your question hasn't been answered in our support center: https://support.metamask.io
|
||||
Common questions such as "Where is my ether?" or "Where did my tokens go?" are answered there.
|
||||
|
||||
Bug Reports:
|
||||
|
||||
Briefly describe the issue you've encountered
|
||||
* Expected Behavior
|
||||
* Actual Behavior
|
||||
* Browser Used
|
||||
* Operating System Used
|
||||
|
||||
Screenshots are very helpful and will expedite your issue being resolved!
|
||||
-->
|
40
LICENSE
@ -1,34 +1,20 @@
|
||||
Copyright (c) 2016 MetaMask
|
||||
MIT License
|
||||
|
||||
The Ethereum Project Contributor Asset Distribution Terms ( MIT + Share-alike )
|
||||
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||
|
||||
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||
Copyright (c) 2018 MetaMask
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
|
||||
portions of the Software.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
|
||||
|
||||
THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
These licence terms have been adapted from the MIT licence.
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
|
151
README.md
@ -1,10 +1,22 @@
|
||||
# MetaMask Plugin [![Build Status](https://circleci.com/gh/MetaMask/metamask-plugin.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-plugin)
|
||||
# MetaMask Browser Extension
|
||||
[![Build Status](https://circleci.com/gh/MetaMask/metamask-extension.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-extension) [![Coverage Status](https://coveralls.io/repos/github/MetaMask/metamask-extension/badge.svg?branch=master)](https://coveralls.io/github/MetaMask/metamask-extension?branch=master) [![Greenkeeper badge](https://badges.greenkeeper.io/MetaMask/metamask-extension.svg)](https://greenkeeper.io/) [![Stories in Ready](https://badge.waffle.io/MetaMask/metamask-extension.png?label=in%20progress&title=waffle.io)](http://waffle.io/MetaMask/metamask-extension)
|
||||
|
||||
|
||||
## Support
|
||||
|
||||
If you're a user seeking support, [here is our support site](https://metamask.helpscoutdocs.com/).
|
||||
|
||||
## Developing Compatible Dapps
|
||||
|
||||
If you're a web dapp developer, we've got two types of guides for you:
|
||||
|
||||
- If you've never built a Dapp before, we've got a gentle introduction on [Developing Dapps with Truffle and MetaMask](https://blog.metamask.io/developing-for-metamask-with-truffle/).
|
||||
### New Dapp Developers
|
||||
|
||||
- We recommend this [Learning Solidity](https://karl.tech/learning-solidity-part-1-deploy-a-contract/) tutorial series by Karl Floersch.
|
||||
- We wrote a (slightly outdated now) gentle introduction on [Developing Dapps with Truffle and MetaMask](https://medium.com/metamask/developing-ethereum-dapps-with-truffle-and-metamask-aa8ad7e363ba).
|
||||
|
||||
### Current Dapp Developers
|
||||
|
||||
- If you have a Dapp, and you want to ensure compatibility, [here is our guide on building MetaMask-compatible Dapps](https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md)
|
||||
|
||||
## Building locally
|
||||
@ -18,11 +30,15 @@ If you're a web dapp developer, we've got two types of guides for you:
|
||||
|
||||
Uncompressed builds can be found in `/dist`, compressed builds can be found in `/builds` once they're built.
|
||||
|
||||
## Installing Local Builds on Chrome
|
||||
### Running Tests
|
||||
|
||||
To install your locally built extension on Chrome, [follow this guide](http://stackoverflow.com/a/24577660/272576).
|
||||
Requires `mocha` installed. Run `npm install -g mocha`.
|
||||
|
||||
The built extension is stored in `./dist/chrome/`.
|
||||
Then just run `npm test`.
|
||||
|
||||
You can also test with a continuously watching process, via `npm run watch`.
|
||||
|
||||
You can run the linter by itself with `gulp lint`.
|
||||
|
||||
## Architecture
|
||||
|
||||
@ -41,126 +57,23 @@ npm start
|
||||
npm run dist
|
||||
```
|
||||
|
||||
#### In Chrome
|
||||
|
||||
Open `Settings` > `Extensions`.
|
||||
|
||||
Check "Developer mode".
|
||||
|
||||
At the top, click `Load Unpacked Extension`.
|
||||
|
||||
Navigate to your `metamask-plugin/dist/chrome` folder.
|
||||
|
||||
Click `Select`.
|
||||
|
||||
You now have the plugin, and can click 'inspect views: background plugin' to view its dev console.
|
||||
|
||||
#### In Firefox
|
||||
|
||||
Go to the url `about:debugging`.
|
||||
|
||||
Click the button `Load Temporary Add-On`.
|
||||
|
||||
Select the file `dist/firefox/manifest.json`.
|
||||
|
||||
You can optionally enable debugging, and click `Debug`, for a console window that logs all of Metamask's processes to a single console.
|
||||
|
||||
If you have problems debugging, try connecting to the IRC channel `#webextensions` on `irc.mozilla.org`.
|
||||
|
||||
For longer questions, use the StackOverfow tag `firefox-addons`.
|
||||
|
||||
### Developing on UI Only
|
||||
|
||||
You can run `npm run ui`, and your browser should open a live-reloading demo version of the plugin UI.
|
||||
|
||||
Some actions will crash the app, so this is only for tuning aesthetics, but it allows live-reloading styles, which is a much faster feedback loop than reloading the full extension.
|
||||
|
||||
### Developing on UI with Mocked Background Process
|
||||
|
||||
You can run `npm run mock` and your browser should open a live-reloading demo version of the plugin UI, just like the `npm run ui`, except that it tries to actually perform all normal operations.
|
||||
|
||||
It does not yet connect to a real blockchain (this could be a good test feature later, connecting to a test blockchain), so only local operations work.
|
||||
|
||||
You can reset the mock ui at any time with the `Reset` button at the top of the screen.
|
||||
|
||||
### Developing on Dependencies
|
||||
|
||||
To enjoy the live-reloading that `gulp dev` offers while working on the `web3-provider-engine` or other dependencies:
|
||||
|
||||
1. Clone the dependency locally.
|
||||
2. `npm install` in its folder.
|
||||
3. Run `npm link` in its folder.
|
||||
4. Run `npm link $DEP_NAME` in this project folder.
|
||||
5. Next time you `npm start` it will watch the dependency for changes as well!
|
||||
|
||||
### Running Tests
|
||||
|
||||
Requires `mocha` installed. Run `npm install -g mocha`.
|
||||
|
||||
Then just run `npm test`.
|
||||
|
||||
You can also test with a continuously watching process, via `npm run watch`.
|
||||
|
||||
You can run the linter by itself with `gulp lint`.
|
||||
|
||||
#### Writing Browser Tests
|
||||
|
||||
To write tests that will be run in the browser using QUnit, add your test files to `test/integration/lib`.
|
||||
|
||||
### Deploying the UI
|
||||
## Other Docs
|
||||
|
||||
You must be authorized already on the MetaMask plugin.
|
||||
|
||||
0. Update the version in `app/manifest.json` and the Changelog in `CHANGELOG.md`.
|
||||
1. Visit [the chrome developer dashboard](https://chrome.google.com/webstore/developer/dashboard?authuser=2).
|
||||
2. Run `gulp dist` (or `gulp zip` if you've already built)
|
||||
3. Upload the latest zip file from `builds/metamask-$PLATFORM-$VERSION.zip` as the updated package.
|
||||
- [How to add custom build to Chrome](./docs/add-to-chrome.md)
|
||||
- [How to add custom build to Firefox](./docs/add-to-firefox.md)
|
||||
- [How to develop a live-reloading UI](./docs/ui-dev-mode.md)
|
||||
- [Publishing Guide](./docs/publishing.md)
|
||||
- [How to develop an in-browser mocked UI](./docs/ui-mock-mode.md)
|
||||
- [How to live reload on local dependency changes](./docs/developing-on-deps.md)
|
||||
- [How to add new networks to the Provider Menu](./docs/adding-new-networks.md)
|
||||
- [How to manage notices that appear when the app starts up](./docs/notices.md)
|
||||
- [How to port MetaMask to a new platform](./docs/porting_to_new_environment.md)
|
||||
- [How to generate a visualization of this repository's development](./docs/development-visualization.md)
|
||||
|
||||
[1]: http://www.nomnoml.com/#view/%5B%3Cactor%3Euser%5D%0A%0A%5Bmetamask-ui%7C%0A%20%20%20%5Btools%7C%0A%20%20%20%20%20react%0A%20%20%20%20%20redux%0A%20%20%20%20%20thunk%0A%20%20%20%20%20ethUtils%0A%20%20%20%20%20jazzicon%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20account-detail%0A%20%20%20%20%20accounts%0A%20%20%20%20%20locked-screen%0A%20%20%20%20%20restore-vault%0A%20%20%20%20%20identicon%0A%20%20%20%20%20config%0A%20%20%20%20%20info%0A%20%20%20%5D%0A%20%20%20%5Breducers%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20metamask%0A%20%20%20%20%20identities%0A%20%20%20%5D%0A%20%20%20%5Bactions%7C%0A%20%20%20%20%20%5BaccountManager%5D%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%5D%3A-%3E%5Bactions%5D%0A%20%20%20%5Bactions%5D%3A-%3E%5Breducers%5D%0A%20%20%20%5Breducers%5D%3A-%3E%5Bcomponents%5D%0A%5D%0A%0A%5Bweb%20dapp%7C%0A%20%20%5Bui%20code%5D%0A%20%20%5Bweb3%5D%0A%20%20%5Bmetamask-inpage%5D%0A%20%20%0A%20%20%5B%3Cactor%3Eui%20developer%5D%0A%20%20%5Bui%20developer%5D-%3E%5Bui%20code%5D%0A%20%20%5Bui%20code%5D%3C-%3E%5Bweb3%5D%0A%20%20%5Bweb3%5D%3C-%3E%5Bmetamask-inpage%5D%0A%5D%0A%0A%5Bmetamask-background%7C%0A%20%20%5Bprovider-engine%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bid%20store%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%3E%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%3C-%3E%5Bid%20store%5D%0A%20%20%5Bconfig%20manager%7C%0A%20%20%20%20%5Brpc%20configuration%5D%0A%20%20%20%20%5Bencrypted%20keys%5D%0A%20%20%20%20%5Bwallet%20nicknames%5D%0A%20%20%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%5Bconfig%20manager%5D%0A%20%20%5Bid%20store%5D%3C-%3E%5Bconfig%20manager%5D%0A%5D%0A%0A%5Buser%5D%3C-%3E%5Bmetamask-ui%5D%0A%0A%5Buser%5D%3C%3A--%3A%3E%5Bweb%20dapp%5D%0A%0A%5Bmetamask-contentscript%7C%0A%20%20%5Bplugin%20restart%20detector%5D%0A%20%20%5Brpc%20passthrough%5D%0A%5D%0A%0A%5Brpc%20%7C%0A%20%20%5Bethereum%20blockchain%20%7C%0A%20%20%20%20%5Bcontracts%5D%0A%20%20%20%20%5Baccounts%5D%0A%20%20%5D%0A%5D%0A%0A%5Bweb%20dapp%5D%3C%3A--%3A%3E%5Bmetamask-contentscript%5D%0A%5Bmetamask-contentscript%5D%3C-%3E%5Bmetamask-background%5D%0A%5Bmetamask-background%5D%3C-%3E%5Bmetamask-ui%5D%0A%5Bmetamask-background%5D%3C-%3E%5Brpc%5D%0A
|
||||
|
||||
|
||||
### Generate Development Visualization
|
||||
|
||||
This will generate a video of the repo commit history.
|
||||
|
||||
Install preqs:
|
||||
```
|
||||
brew install gource
|
||||
brew install ffmpeg
|
||||
```
|
||||
|
||||
From the repo dir, pipe `gource` into `ffmpeg`:
|
||||
```
|
||||
gource \
|
||||
--seconds-per-day .1 \
|
||||
--user-scale 1.5 \
|
||||
--default-user-image "./images/icon-512.png" \
|
||||
--viewport 1280x720 \
|
||||
--auto-skip-seconds .1 \
|
||||
--multi-sampling \
|
||||
--stop-at-end \
|
||||
--highlight-users \
|
||||
--hide mouse,progress \
|
||||
--file-idle-time 0 \
|
||||
--max-files 0 \
|
||||
--background-colour 000000 \
|
||||
--font-size 18 \
|
||||
--date-format "%b %d, %Y" \
|
||||
--highlight-dirs \
|
||||
--user-friction 0.1 \
|
||||
--title "MetaMask Development History" \
|
||||
--output-ppm-stream - \
|
||||
--output-framerate 30 \
|
||||
| ffmpeg -y -r 30 -f image2pipe -vcodec ppm -i - -b 65536K metamask-dev-history.mp4
|
||||
```
|
||||
|
||||
## Generating Notices
|
||||
|
||||
To add a notice:
|
||||
```
|
||||
npm run generateNotice
|
||||
```
|
||||
To delete a notice:
|
||||
```
|
||||
npm run deleteNotice
|
||||
```
|
||||
|
10
app/_locales/ko/messages.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"appName": {
|
||||
"message": "MetaMask",
|
||||
"description": "The name of the application"
|
||||
},
|
||||
"appDescription": {
|
||||
"message": "이더리움 계좌 관리",
|
||||
"description": "The description of the application"
|
||||
}
|
||||
}
|
BIN
app/fonts/DIN Next/DIN Next W01 Bold.otf
Normal file
BIN
app/fonts/DIN Next/DIN Next W01 Regular.otf
Normal file
BIN
app/fonts/DIN Next/DIN Next W10 Black.otf
Normal file
BIN
app/fonts/DIN Next/DIN Next W10 Italic.otf
Normal file
BIN
app/fonts/DIN Next/DIN Next W10 Light.otf
Normal file
BIN
app/fonts/DIN Next/DIN Next W10 Medium.otf
Normal file
BIN
app/fonts/DIN_OT/DINOT-2.otf
Normal file
BIN
app/fonts/DIN_OT/DINOT-Bold 2.otf
Normal file
BIN
app/fonts/DIN_OT/DINOT-BoldItalic.otf
Normal file
BIN
app/fonts/DIN_OT/DINOT-Italic 2.otf
Normal file
BIN
app/fonts/DIN_OT/DINOT-Medium 2.otf
Normal file
BIN
app/fonts/DIN_OT/DINOT-MediumItalic 2.otf
Normal file
BIN
app/fonts/Lato/Lato-Black.ttf
Executable file
BIN
app/fonts/Lato/Lato-BlackItalic.ttf
Executable file
BIN
app/fonts/Lato/Lato-Bold.ttf
Executable file
BIN
app/fonts/Lato/Lato-BoldItalic.ttf
Executable file
BIN
app/fonts/Lato/Lato-Hairline.ttf
Executable file
BIN
app/fonts/Lato/Lato-HairlineItalic.ttf
Executable file
BIN
app/fonts/Lato/Lato-Italic.ttf
Executable file
BIN
app/fonts/Lato/Lato-Light.ttf
Executable file
BIN
app/fonts/Lato/Lato-LightItalic.ttf
Executable file
BIN
app/fonts/Lato/Lato-Regular.ttf
Executable file
93
app/fonts/Lato/OFL.txt
Executable file
@ -0,0 +1,93 @@
|
||||
Copyright (c) 2010-2014 by tyPoland Lukasz Dziedzic (team@latofonts.com) with Reserved Font Name "Lato"
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
BIN
app/fonts/Roboto/Roboto-Black.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-BlackItalic.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-Bold.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-BoldItalic.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-Italic.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-Light.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-LightItalic.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-Medium.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-MediumItalic.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-Regular.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-Thin.ttf
Normal file
BIN
app/fonts/Roboto/Roboto-ThinItalic.ttf
Normal file
BIN
app/fonts/Roboto/RobotoCondensed-Bold.ttf
Normal file
BIN
app/fonts/Roboto/RobotoCondensed-BoldItalic.ttf
Normal file
BIN
app/fonts/Roboto/RobotoCondensed-Italic.ttf
Normal file
BIN
app/fonts/Roboto/RobotoCondensed-Light.ttf
Normal file
BIN
app/fonts/Roboto/RobotoCondensed-LightItalic.ttf
Normal file
BIN
app/fonts/Roboto/RobotoCondensed-Regular.ttf
Normal file
12
app/home.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=no">
|
||||
<title>MetaMask Plugin</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app-content"></div>
|
||||
<script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script>
|
||||
</body>
|
||||
</html>
|
BIN
app/images/.DS_Store
vendored
76
app/images/caret-right.svg
Normal file
@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1000 1000" style="enable-background:new 0 0 1000 1000;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#231F20;}
|
||||
.st1{fill:none;stroke:#000000;stroke-width:35;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
|
||||
.st2{fill:none;stroke:#000000;stroke-width:35;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:25,61;}
|
||||
.st3{display:none;}
|
||||
.st4{display:inline;}
|
||||
.st5{fill:#EC008C;}
|
||||
.st6{display:inline;fill:#FFF200;}
|
||||
</style>
|
||||
<g id="Layer_4">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M380.4,756.7c-4.5,0-9-1.7-12.4-5.1c-6.8-6.8-6.8-17.9,0-24.7L594.9,500L368,273.2
|
||||
c-6.8-6.8-6.8-17.9,0-24.7c6.8-6.8,17.9-6.8,24.7,0L632,487.6c6.8,6.8,6.8,17.9,0,24.7L392.8,751.6
|
||||
C389.3,755,384.9,756.7,380.4,756.7z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Layer_2" class="st3">
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
14
app/images/check-white.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="13px" viewBox="0 0 16 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 47 (45396) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>check-white</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="MetaMascara-v2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="account-dropdown-top-bar-IXD" transform="translate(-17.000000, -80.000000)" fill-rule="nonzero" fill="#FFFFFF">
|
||||
<g id="Group-11" transform="translate(18.000000, 74.000000)">
|
||||
<polygon id="check-white" points="4.2 15.5712828 0.714212839 12.0143571 -0.714212839 13.4142143 4.2 18.4287172 14.7142128 7.69992858 13.2857872 6.30007142"></polygon>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 872 B |
BIN
app/images/coinbase logo.png
Normal file
After Width: | Height: | Size: 9.5 KiB |
11
app/images/eth_logo.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="256px" height="417px" viewBox="0 0 256 417" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g>
|
||||
<polygon fill="#343434" points="127.9611 0 125.1661 9.5 125.1661 285.168 127.9611 287.958 255.9231 212.32"/>
|
||||
<polygon fill="#8C8C8C" points="127.962 0 0 212.32 127.962 287.959 127.962 154.158"/>
|
||||
<polygon fill="#3C3C3B" points="127.9611 312.1866 126.3861 314.1066 126.3861 412.3056 127.9611 416.9066 255.9991 236.5866"/>
|
||||
<polygon fill="#8C8C8C" points="127.962 416.9052 127.962 312.1852 0 236.5852"/>
|
||||
<polygon fill="#141414" points="127.9611 287.9577 255.9211 212.3207 127.9611 154.1587"/>
|
||||
<polygon fill="#393939" points="0.0009 212.3208 127.9609 287.9578 127.9609 154.1588"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 854 B |
18
app/images/import-account.svg
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="15px" height="15px" viewBox="0 0 15 15" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 47 (45396) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>import-account</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="MetaMascara-v2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="account-dropdown-top-bar-IXD" transform="translate(-25.000000, -718.000000)">
|
||||
<g id="Group-6" transform="translate(4.000000, 646.000000)">
|
||||
<g id="import-account" transform="translate(21.000000, 72.000000)">
|
||||
<rect id="Rectangle-49" fill="#FFFFFF" x="0" y="13.1721326" width="14.4893459" height="1.08397642"></rect>
|
||||
<rect id="Rectangle" fill="#FFFFFF" x="6.5860663" y="0" width="1.08397642" height="10.5377061"></rect>
|
||||
<polyline id="Path-12" stroke="#FFFFFF" points="2.63442652 6.5860663 7.24467293 10.5377061 11.8549193 6.5860663"></polyline>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
app/images/info-logo.png
Normal file
After Width: | Height: | Size: 32 KiB |
128
app/images/metamask-fox.svg
Normal file
@ -0,0 +1,128 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns:ev="http://www.w3.org/2001/xml-events"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 318.6 318.6"
|
||||
style="enable-background:new 0 0 318.6 318.6;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#161616;stroke:#161616;}
|
||||
.st1{fill:#E4761B;stroke:#E4761B;stroke-linecap:round;stroke-linejoin:round;}
|
||||
.st2{fill:#763D16;stroke:#763D16;stroke-linecap:round;stroke-linejoin:round;}
|
||||
.st3{fill:#F6851B;stroke:#F6851B;stroke-linecap:round;stroke-linejoin:round;}
|
||||
.st4{fill:#E2761B;stroke:#E2761B;stroke-linecap:round;stroke-linejoin:round;}
|
||||
.st5{fill:#CD6116;stroke:#CD6116;stroke-linecap:round;stroke-linejoin:round;}
|
||||
.st6{fill:#C0AD9E;stroke:#C0AD9E;stroke-linecap:round;stroke-linejoin:round;}
|
||||
.st7{fill:#D7C1B3;stroke:#D7C1B3;stroke-linecap:round;stroke-linejoin:round;}
|
||||
.st8{fill:#E4751F;stroke:#E4751F;stroke-linecap:round;stroke-linejoin:round;}
|
||||
.st9{fill:#233447;stroke:#233447;stroke-linecap:round;stroke-linejoin:round;}
|
||||
.st10{fill:#161616;stroke:#161616;stroke-linecap:round;stroke-linejoin:round;}
|
||||
</style>
|
||||
<polygon class="st0" points="277.3,145.6 272.3,142 280.3,134.7 274.2,129.9 282.2,123.8 276.9,119.8 285.3,79 272.7,41.1
|
||||
191.6,71.4 124.1,71.4 43,41.1 30.4,79 38.9,119.8 33.5,123.8 41.5,129.9 35.4,134.7 43.4,142 38.4,145.6 49.9,159.1 32.5,213.3
|
||||
48.6,268.6 105.3,253 116.3,262 138.7,277.5 177,277.5 199.4,262 210.4,253 267.1,268.6 283.3,213.3 265.8,159.1 "/>
|
||||
<g>
|
||||
<polygon class="st1" points="105.3,253 48.6,268.6 32.5,213.3 "/>
|
||||
<polygon class="st1" points="283.3,213.3 267.1,268.6 210.4,253 "/>
|
||||
<polygon class="st2" points="265.8,159.1 213.5,143.8 231.8,139 "/>
|
||||
<polygon class="st2" points="49.9,159.1 84,139 102.2,143.8 "/>
|
||||
<polygon class="st2" points="43.4,142 41.5,129.9 84,139 "/>
|
||||
<polygon class="st2" points="272.3,142 231.8,139 274.2,129.9 "/>
|
||||
<polygon class="st2" points="272.3,142 265.8,159.1 231.8,139 "/>
|
||||
<polygon class="st2" points="43.4,142 84,139 49.9,159.1 "/>
|
||||
<polygon class="st2" points="231.8,139 276.9,119.8 274.2,129.9 "/>
|
||||
<polygon class="st2" points="84,139 41.5,129.9 38.9,119.8 "/>
|
||||
<polygon class="st3" points="124.1,71.4 191.6,71.4 176.5,112.5 "/>
|
||||
<polygon class="st3" points="176.5,112.5 139.2,112.5 124.1,71.4 "/>
|
||||
<polygon class="st2" points="276.9,119.8 231.8,139 231,87.4 "/>
|
||||
<polygon class="st2" points="102.2,143.8 84,139 84.7,87.4 "/>
|
||||
<polygon class="st2" points="84.7,87.4 84,139 38.9,119.8 "/>
|
||||
<polygon class="st2" points="231,87.4 231.8,139 213.5,143.8 "/>
|
||||
<polygon class="st1" points="139.2,112.5 43,41.1 124.1,71.4 "/>
|
||||
<polygon class="st4" points="272.7,41.1 176.5,112.5 191.6,71.4 "/>
|
||||
<polygon class="st1" points="210.4,253 236.9,213.3 283.3,213.3 "/>
|
||||
<polygon class="st1" points="32.5,213.3 78.9,213.3 105.3,253 "/>
|
||||
<polygon class="st3" points="229.3,167.7 283.3,213.3 236.9,213.3 "/>
|
||||
<polygon class="st3" points="86.4,167.7 32.5,213.3 49.9,159.1 "/>
|
||||
<polygon class="st3" points="78.9,213.3 32.5,213.3 86.4,167.7 "/>
|
||||
<polygon class="st3" points="229.3,167.7 265.8,159.1 283.3,213.3 "/>
|
||||
<polygon class="st2" points="84.7,87.4 139.2,112.5 102.2,143.8 "/>
|
||||
<polygon class="st2" points="213.5,143.8 176.5,112.5 231,87.4 "/>
|
||||
<polygon class="st2" points="265.8,159.1 272.3,142 277.3,145.6 "/>
|
||||
<polygon class="st2" points="49.9,159.1 38.4,145.6 43.4,142 "/>
|
||||
<polygon class="st2" points="272.3,142 274.2,129.9 280.3,134.7 "/>
|
||||
<polygon class="st2" points="43.4,142 35.4,134.7 41.5,129.9 "/>
|
||||
<polygon class="st2" points="33.5,123.8 38.9,119.8 41.5,129.9 "/>
|
||||
<polygon class="st2" points="282.2,123.8 274.2,129.9 276.9,119.8 "/>
|
||||
<polygon class="st3" points="49.9,159.1 102.2,143.8 86.4,167.7 "/>
|
||||
<polygon class="st3" points="265.8,159.1 229.3,167.7 213.5,143.8 "/>
|
||||
<polygon class="st2" points="38.9,119.8 30.4,79 84.7,87.4 "/>
|
||||
<polygon class="st2" points="231,87.4 285.3,79 276.9,119.8 "/>
|
||||
<polygon class="st1" points="102.2,143.8 139.2,112.5 142.6,170.2 "/>
|
||||
<polygon class="st1" points="213.5,143.8 229.3,167.7 173.1,170.2 "/>
|
||||
<polygon class="st1" points="173.1,170.2 176.5,112.5 213.5,143.8 "/>
|
||||
<polygon class="st1" points="142.6,170.2 86.4,167.7 102.2,143.8 "/>
|
||||
<polygon class="st2" points="272.7,41.1 285.3,79 231,87.4 "/>
|
||||
<polygon class="st2" points="43,41.1 139.2,112.5 84.7,87.4 "/>
|
||||
<polygon class="st2" points="231,87.4 176.5,112.5 272.7,41.1 "/>
|
||||
<polygon class="st2" points="84.7,87.4 30.4,79 43,41.1 "/>
|
||||
<polygon class="st5" points="105.3,253 78.9,213.3 110,213.7 "/>
|
||||
<polygon class="st5" points="210.4,253 205.7,213.7 236.9,213.3 "/>
|
||||
<polygon class="st3" points="173.1,170.2 142.6,170.2 139.2,112.5 "/>
|
||||
<polygon class="st3" points="139.2,112.5 176.5,112.5 173.1,170.2 "/>
|
||||
<polygon class="st6" points="116.3,262 105.3,253 136.8,267.9 "/>
|
||||
<polygon class="st6" points="178.9,267.9 210.4,253 199.4,262 "/>
|
||||
<polygon class="st7" points="136.6,258.6 136.8,267.9 105.3,253 "/>
|
||||
<polygon class="st7" points="179.2,258.6 210.4,253 178.9,267.9 "/>
|
||||
<polygon class="st3" points="86.4,167.7 110,213.7 78.9,213.3 "/>
|
||||
<polygon class="st3" points="236.9,213.3 205.7,213.7 229.3,167.7 "/>
|
||||
<polygon class="st8" points="86.4,167.7 109.2,190.8 110,213.7 "/>
|
||||
<polygon class="st8" points="229.3,167.7 205.7,213.7 206.6,190.8 "/>
|
||||
<polygon class="st7" points="105.3,253 139.2,236.5 136.6,258.6 "/>
|
||||
<polygon class="st7" points="210.4,253 179.2,258.6 176.5,236.5 "/>
|
||||
<polygon class="st1" points="139.2,236.5 105.3,253 110,213.7 "/>
|
||||
<polygon class="st1" points="176.5,236.5 205.7,213.7 210.4,253 "/>
|
||||
<polygon class="st5" points="173.1,170.2 229.3,167.7 206.6,190.8 "/>
|
||||
<polygon class="st5" points="109.2,190.8 86.4,167.7 142.6,170.2 "/>
|
||||
<polygon class="st5" points="142.6,170.2 129.1,181.7 109.2,190.8 "/>
|
||||
<polygon class="st5" points="206.6,190.8 186.6,181.7 173.1,170.2 "/>
|
||||
<polygon class="st3" points="205.7,213.7 178.3,199.1 206.6,190.8 "/>
|
||||
<polygon class="st3" points="110,213.7 109.2,190.8 137.4,199.1 "/>
|
||||
<polygon class="st9" points="137.4,199.1 109.2,190.8 129.1,181.7 "/>
|
||||
<polygon class="st9" points="178.3,199.1 186.6,181.7 206.6,190.8 "/>
|
||||
<polygon class="st5" points="186.6,181.7 178.3,199.1 173.1,170.2 "/>
|
||||
<polygon class="st5" points="129.1,181.7 142.6,170.2 137.4,199.1 "/>
|
||||
<polygon class="st6" points="199.4,262 177,277.5 178.9,267.9 "/>
|
||||
<polygon class="st6" points="136.8,267.9 138.7,277.5 116.3,262 "/>
|
||||
<polygon class="st4" points="178.3,199.1 171.8,188.4 173.1,170.2 "/>
|
||||
<polygon class="st8" points="137.4,199.1 142.6,170.2 143.9,188.4 "/>
|
||||
<polygon class="st3" points="173.1,170.2 171.8,188.4 143.9,188.4 "/>
|
||||
<polygon class="st3" points="143.9,188.4 142.6,170.2 173.1,170.2 "/>
|
||||
<polygon class="st3" points="178.3,199.1 205.7,213.7 176.5,236.5 "/>
|
||||
<polygon class="st3" points="139.2,236.5 110,213.7 137.4,199.1 "/>
|
||||
<polygon class="st3" points="137.4,199.1 144,233.2 139.2,236.5 "/>
|
||||
<polygon class="st3" points="176.5,236.5 171.7,233.2 178.3,199.1 "/>
|
||||
<polygon class="st8" points="171.8,188.4 178.3,199.1 171.7,233.2 "/>
|
||||
<polygon class="st8" points="143.9,188.4 144,233.2 137.4,199.1 "/>
|
||||
<polygon class="st3" points="143.9,188.4 171.8,188.4 171.7,233.2 "/>
|
||||
<polygon class="st3" points="171.7,233.2 144,233.2 143.9,188.4 "/>
|
||||
<polygon class="st6" points="179.2,258.6 178.9,267.9 177,277.5 "/>
|
||||
<polygon class="st6" points="138.7,277.5 136.8,267.9 136.6,258.6 "/>
|
||||
<polygon class="st6" points="136.6,258.6 139,256.4 138.7,277.5 "/>
|
||||
<polygon class="st6" points="177,277.5 176.7,256.4 179.2,258.6 "/>
|
||||
<polygon class="st6" points="138.7,277.5 139,256.4 176.7,256.4 "/>
|
||||
<polygon class="st6" points="176.7,256.4 177,277.5 138.7,277.5 "/>
|
||||
<polygon class="st10" points="176.5,236.5 179.2,258.6 176.7,256.4 "/>
|
||||
<polygon class="st10" points="139,256.4 136.6,258.6 139.2,236.5 "/>
|
||||
<polygon class="st10" points="139.2,236.5 140.7,241.2 139,256.4 "/>
|
||||
<polygon class="st10" points="176.7,256.4 175,241.2 176.5,236.5 "/>
|
||||
<polygon class="st10" points="143.7,237.7 140.7,241.2 139.2,236.5 "/>
|
||||
<polygon class="st10" points="176.5,236.5 175,241.2 172,237.7 "/>
|
||||
<polygon class="st10" points="172,237.7 171.7,233.2 176.5,236.5 "/>
|
||||
<polygon class="st10" points="139.2,236.5 144,233.2 143.7,237.7 "/>
|
||||
<polygon class="st10" points="171.7,233.2 172,237.7 143.7,237.7 "/>
|
||||
<polygon class="st10" points="143.7,237.7 144,233.2 171.7,233.2 "/>
|
||||
<polygon class="st10" points="140.7,241.2 175,241.2 176.7,256.4 "/>
|
||||
<polygon class="st10" points="176.7,256.4 139,256.4 140.7,241.2 "/>
|
||||
<polygon class="st10" points="140.7,241.2 143.7,237.7 172,237.7 "/>
|
||||
<polygon class="st10" points="172,237.7 175,241.2 140.7,241.2 "/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.6 KiB |
11
app/images/mm-bolt.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 252 251.7" style="enable-background:new 0 0 252 251.7;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#757575;}
|
||||
</style>
|
||||
<path class="st0" d="M211.3,103.9h-60.7c-2,0-3.6-1.6-3.6-3.6V3.6c0-3.5-4.5-5-6.6-2.2l-102.7,140c-1.8,2.4,0,5.8,2.9,5.8h60.7
|
||||
c2,0,3.6,1.6,3.6,3.6v96.6c0,3.5,4.5,5,6.6,2.2l102.7-140C216,107.3,214.3,103.9,211.3,103.9z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 732 B |
11
app/images/mm-info-icon.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 10 10" style="enable-background:new 0 0 10 10;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#B8B8B8;}
|
||||
</style>
|
||||
<path class="st0" d="M5,0C2.2,0,0,2.2,0,5s2.2,5,5,5s5-2.2,5-5S7.8,0,5,0z M5,2c0.4,0,0.7,0.3,0.7,0.7c0,0.4-0.3,0.7-0.7,0.7
|
||||
S4.3,3.2,4.3,2.8C4.3,2.4,4.6,2,5,2z M5.7,8H4.3V4.3h1.5V8z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 689 B |
15
app/images/open.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="28px" height="28px" viewBox="0 0 28 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>open</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Mobile-screens" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="MetaMascara-Mobile---structured" transform="translate(-329.000000, -93.000000)">
|
||||
<g id="open" transform="translate(330.000000, 94.000000)">
|
||||
<path d="M26,13 C26,20.1799 20.1799,26 13,26 C5.8201,26 0,20.1799 0,13 C0,5.8201 5.8201,0 13,0 C20.1799,0 26,5.8201 26,13 Z" id="Stroke-3" stroke="#4A4A4A"></path>
|
||||
<path d="M6,17 C6,17 7.78735344,10.8360387 13.7616996,10.8360387 L13.7616996,8 L19,12.3733433 L13.7616996,17 L13.7616996,14.1639613 C13.7616996,14.1639613 9.54083576,13.4629933 6,17" id="Fill-5" fill="#4A4A4A"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
17
app/images/plus-btn-white.svg
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 47 (45396) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>plus-btn-white</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="MetaMascara-v2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="account-dropdown-top-bar-IXD" transform="translate(-24.000000, -669.000000)" fill="#FFFFFF">
|
||||
<g id="Group-6" transform="translate(4.000000, 646.000000)">
|
||||
<g id="plus-btn-white" transform="translate(20.000000, 23.000000)">
|
||||
<rect id="Rectangle-48" x="7.38461538" y="0" width="1.23076923" height="16"></rect>
|
||||
<rect id="Rectangle-48" transform="translate(8.000000, 8.000000) rotate(-90.000000) translate(-8.000000, -8.000000) " x="7.38461538" y="0" width="1.23076923" height="16"></rect>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
21
app/images/popout.svg
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="22px" height="22px" viewBox="0 0 22 22" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>popout</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<polygon id="path-1" points="-0.00035 0 10.9999 0 10.9999 10.9997 -0.00035 10.9997"></polygon>
|
||||
</defs>
|
||||
<g id="MetaMascara-Mobile---structured-TOKEN" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(-327.000000, -96.000000)">
|
||||
<g id="popout" transform="translate(327.000000, 96.000000)">
|
||||
<g id="Group-3" transform="translate(11.000000, 0.000000)">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<g id="Clip-2"></g>
|
||||
<path d="M10.9229,0.6177 C10.8209,0.3737 10.6269,0.1787 10.3819,0.0767 C10.2599,0.0267 10.1309,-0.0003 9.9999,-0.0003 L3.9999,-0.0003 C3.4479,-0.0003 2.9999,0.4477 2.9999,0.9997 C2.9999,1.5527 3.4479,1.9997 3.9999,1.9997 L7.5859,1.9997 L0.2929,9.2927 C-0.0981,9.6837 -0.0981,10.3167 0.2929,10.7067 C0.4879,10.9027 0.7439,10.9997 0.9999,10.9997 C1.2559,10.9997 1.5119,10.9027 1.7069,10.7067 L8.9999,3.4137 L8.9999,6.9997 C8.9999,7.5527 9.4479,7.9997 9.9999,7.9997 C10.5519,7.9997 10.9999,7.5527 10.9999,6.9997 L10.9999,0.9997 C10.9999,0.8697 10.9739,0.7407 10.9229,0.6177" id="Fill-1" fill="#4A4A4A" mask="url(#mask-2)"></path>
|
||||
</g>
|
||||
<path d="M19,10 C18.448,10 18,10.448 18,11 L18,19 C18,19.551 17.551,20 17,20 L3,20 C2.449,20 2,19.551 2,19 L2,5 C2,4.449 2.449,4 3,4 L11,4 C11.552,4 12,3.552 12,3 C12,2.448 11.552,2 11,2 L3,2 C1.346,2 0,3.346 0,5 L0,19 C0,20.654 1.346,22 3,22 L17,22 C18.654,22 20,20.654 20,19 L20,11 C20,10.448 19.552,10 19,10" id="Fill-4" fill="#4A4A4A"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
@ -1,24 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="24.088px" height="24px" viewBox="0 0 24.088 24" enable-background="new 0 0 24.088 24" xml:space="preserve">
|
||||
<path d="M21.525,10.147c-0.41-0.059-0.847-0.428-0.974-0.82l-0.608-1.481c-0.191-0.365-0.146-0.935,0.1-1.264l0.99-1.318
|
||||
c0.246-0.33,0.227-0.854-0.047-1.162l-1.084-1.086c-0.31-0.272-0.832-0.293-1.164-0.045l-1.316,0.988
|
||||
c-0.33,0.248-0.898,0.293-1.264,0.101l-1.48-0.609c-0.395-0.126-0.764-0.562-0.82-0.971l-0.233-1.629
|
||||
c-0.058-0.409-0.44-0.778-0.851-0.822c0,0-0.254-0.026-0.77-0.026c-0.514,0-0.77,0.026-0.77,0.026
|
||||
c-0.41,0.044-0.793,0.413-0.852,0.822L10.15,2.48c-0.059,0.409-0.428,0.845-0.82,0.971L7.85,4.06
|
||||
C7.484,4.251,6.916,4.207,6.586,3.959L5.268,2.97c-0.33-0.248-0.854-0.228-1.162,0.045L3.021,4.101
|
||||
C2.749,4.41,2.727,4.933,2.975,5.263l0.988,1.318c0.249,0.33,0.293,0.899,0.102,1.264l-0.61,1.482
|
||||
c-0.125,0.393-0.562,0.762-0.972,0.82l-1.629,0.231c-0.408,0.059-0.776,0.442-0.82,0.853c0,0-0.026,0.255-0.026,0.77
|
||||
c0,0.516,0.026,0.77,0.026,0.77c0.044,0.412,0.412,0.793,0.82,0.853l1.629,0.231c0.408,0.06,0.847,0.429,0.972,0.82l0.61,1.48
|
||||
c0.191,0.365,0.146,0.936-0.102,1.264l-0.988,1.318c-0.248,0.33-0.308,0.779-0.132,0.994c0.175,0.217,0.677,0.752,0.679,0.754
|
||||
c0,0.002,0.17,0.156,0.375,0.344c0.203,0.188,1.041,0.449,1.371,0.203l1.317-0.99c0.33-0.246,0.897-0.293,1.265-0.1l1.479,0.608
|
||||
c0.394,0.125,0.763,0.562,0.819,0.972l0.233,1.629c0.058,0.408,0.44,0.779,0.853,0.822c0,0,0.254,0.026,0.769,0.026
|
||||
s0.771-0.026,0.771-0.026c0.408-0.043,0.793-0.414,0.85-0.822l0.234-1.629c0.057-0.408,0.426-0.847,0.819-0.972l1.479-0.61
|
||||
c0.365-0.191,0.935-0.146,1.265,0.102l1.317,0.99c0.332,0.246,0.854,0.227,1.164-0.047l1.082-1.084
|
||||
c0.273-0.312,0.293-0.834,0.047-1.164l-0.989-1.318c-0.246-0.328-0.291-0.898-0.101-1.264l0.609-1.48
|
||||
c0.127-0.393,0.562-0.762,0.973-0.82l1.627-0.231c0.41-0.06,0.779-0.44,0.822-0.853c0,0,0.027-0.254,0.027-0.77
|
||||
c0-0.515-0.027-0.77-0.027-0.77c-0.043-0.41-0.412-0.794-0.822-0.853L21.525,10.147z M12.004,15.001c-1.657,0-3-1.344-3-3
|
||||
c0-1.657,1.343-3,3-3s3,1.344,3,3S13.66,15.001,12.004,15.001z"/>
|
||||
</svg>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="20px" height="20px" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 47 (45396) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>settings</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs>
|
||||
<polygon id="path-1" points="20 10 20 19.9998 0 19.9998 0 10 0 0.0002 20 0.0002"></polygon>
|
||||
</defs>
|
||||
<g id="MetaMascara-v2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="account-dropdown-top-bar-IXD" transform="translate(-25.000000, -826.000000)">
|
||||
<g id="Group-6" transform="translate(4.000000, 646.000000)">
|
||||
<g id="settings" transform="translate(21.000000, 180.000000)">
|
||||
<mask id="mask-2" fill="white">
|
||||
<use xlink:href="#path-1"></use>
|
||||
</mask>
|
||||
<g id="Clip-2"></g>
|
||||
<path d="M10,13.6602 C7.979,13.6602 6.34,12.0212 6.34,10.0002 C6.34,7.9782 7.979,6.3402 10,6.3402 C12.021,6.3402 13.66,7.9782 13.66,10.0002 C13.66,12.0212 12.021,13.6602 10,13.6602 L10,13.6602 Z M19.157,11.8112 C19.53,11.8112 19.878,11.5092 19.929,11.1392 C19.929,11.1392 20,10.6182 20,10.0002 C20,9.3822 19.929,8.8622 19.929,8.8622 C19.878,8.4922 19.53,8.1892 19.157,8.1892 L17.228,8.1892 C16.854,8.1892 16.466,7.9512 16.365,7.6602 C16.265,7.3682 16.127,6.4352 16.391,6.1712 L17.755,4.8072 C18.019,4.5432 18.039,4.0922 17.8,3.8052 L16.195,2.2002 C15.908,1.9602 15.458,1.9812 15.193,2.2452 L13.829,3.6092 C13.565,3.8732 13.125,3.9802 12.852,3.8462 C12.578,3.7122 11.812,3.1462 11.812,2.7732 L11.812,0.8432 C11.812,0.4702 11.509,0.1222 11.139,0.0722 C11.139,0.0722 10.619,0.0002 10,0.0002 C9.382,0.0002 8.862,0.0722 8.862,0.0722 C8.492,0.1222 8.189,0.4702 8.189,0.8432 L8.189,2.7732 C8.189,3.1462 7.951,3.5352 7.66,3.6352 C7.369,3.7352 6.435,3.8732 6.171,3.6092 L4.807,2.2452 C4.542,1.9812 4.092,1.9612 3.805,2.2002 L2.2,3.8052 C1.96,4.0922 1.981,4.5432 2.245,4.8072 L3.609,6.1712 C3.873,6.4352 3.98,6.8752 3.846,7.1482 C3.711,7.4222 3.146,8.1892 2.773,8.1892 L0.843,8.1892 C0.47,8.1892 0.123,8.4922 0.072,8.8622 C0.072,8.8622 0,9.3822 0,10.0002 C0,10.6182 0.072,11.1392 0.072,11.1392 C0.123,11.5092 0.47,11.8112 0.843,11.8112 L2.773,11.8112 C3.146,11.8112 3.535,12.0502 3.635,12.3412 C3.735,12.6322 3.874,13.5642 3.609,13.8292 L2.246,15.1932 C1.981,15.4572 1.961,15.9082 2.2,16.1952 L3.805,17.8002 C4.092,18.0392 4.542,18.0192 4.807,17.7552 L6.171,16.3902 C6.435,16.1272 6.875,16.0202 7.148,16.1542 C7.422,16.2882 8.189,16.8532 8.189,17.2272 L8.189,19.1572 C8.189,19.5302 8.492,19.8782 8.862,19.9292 C8.862,19.9292 9.382,20.0002 10,20.0002 C10.619,20.0002 11.139,19.9292 11.139,19.9292 C11.509,19.8772 11.812,19.5302 11.812,19.1572 L11.812,17.2272 C11.812,16.8532 12.05,16.4662 12.341,16.3652 C12.632,16.2642 13.565,16.1272 13.829,16.3902 L15.193,17.7552 C15.458,18.0182 15.908,18.0392 16.195,17.8002 L17.8,16.1952 C18.039,15.9082 18.02,15.4582 17.755,15.1932 L16.391,13.8292 C16.127,13.5652 16.021,13.1252 16.154,12.8512 C16.288,12.5782 16.854,11.8112 17.228,11.8112 L19.157,11.8112 Z" id="Fill-1" fill="#B3B3B3" mask="url(#mask-2)"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 3.2 KiB |
BIN
app/images/shapeshift logo.png
Normal file
After Width: | Height: | Size: 17 KiB |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "MetaMask",
|
||||
"short_name": "Metamask",
|
||||
"version": "3.5.2",
|
||||
"version": "4.1.0",
|
||||
"manifest_version": 2,
|
||||
"author": "https://metamask.io",
|
||||
"description": "Ethereum Browser Extension",
|
||||
@ -58,8 +58,8 @@
|
||||
"storage",
|
||||
"clipboardWrite",
|
||||
"http://localhost:8545/",
|
||||
"https://www.cryptonator.com/"
|
||||
],
|
||||
"https://*.infura.io/"
|
||||
],
|
||||
"web_accessible_resources": [
|
||||
"scripts/inpage.js"
|
||||
],
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<html style="height:600px;">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>MetaMask Notification</title>
|
||||
@ -9,7 +9,7 @@
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<body class="notification" style="height:600px;">
|
||||
<div id="app-content"></div>
|
||||
<script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script>
|
||||
</body>
|
||||
|
@ -1,11 +1,12 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<html style="width:357px; height:600px;">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=no">
|
||||
<title>MetaMask Plugin</title>
|
||||
</head>
|
||||
<body>
|
||||
<body style="width:357px; height:600px;">
|
||||
<div id="app-content"></div>
|
||||
<script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
@ -4,7 +4,7 @@ const ethUtil = require('ethereumjs-util')
|
||||
|
||||
const accountImporter = {
|
||||
|
||||
importAccount(strategy, args) {
|
||||
importAccount (strategy, args) {
|
||||
try {
|
||||
const importer = this.strategies[strategy]
|
||||
const privateKeyHex = importer.apply(null, args)
|
||||
|
@ -1,22 +1,24 @@
|
||||
const urlUtil = require('url')
|
||||
const endOfStream = require('end-of-stream')
|
||||
const asyncQ = require('async-q')
|
||||
const pipe = require('pump')
|
||||
const pump = require('pump')
|
||||
const log = require('loglevel')
|
||||
const extension = require('extensionizer')
|
||||
const LocalStorageStore = require('obs-store/lib/localStorage')
|
||||
const storeTransform = require('obs-store/lib/transform')
|
||||
const asStream = require('obs-store/lib/asStream')
|
||||
const ExtensionPlatform = require('./platforms/extension')
|
||||
const Migrator = require('./lib/migrator/')
|
||||
const migrations = require('./migrations/')
|
||||
const PortStream = require('./lib/port-stream.js')
|
||||
const NotificationManager = require('./lib/notification-manager.js')
|
||||
const MetamaskController = require('./metamask-controller')
|
||||
const extension = require('extensionizer')
|
||||
const firstTimeState = require('./first-time-state')
|
||||
const setupRaven = require('./setupRaven')
|
||||
const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics')
|
||||
|
||||
const STORAGE_KEY = 'metamask-config'
|
||||
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||
|
||||
const log = require('loglevel')
|
||||
window.log = log
|
||||
log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn')
|
||||
|
||||
@ -24,44 +26,46 @@ const platform = new ExtensionPlatform()
|
||||
const notificationManager = new NotificationManager()
|
||||
global.METAMASK_NOTIFIER = notificationManager
|
||||
|
||||
// setup sentry error reporting
|
||||
const release = platform.getVersion()
|
||||
const raven = setupRaven({ release })
|
||||
|
||||
let popupIsOpen = false
|
||||
let openMetamaskTabsIDs = {}
|
||||
|
||||
// state persistence
|
||||
const diskStore = new LocalStorageStore({ storageKey: STORAGE_KEY })
|
||||
|
||||
// initialization flow
|
||||
asyncQ.waterfall([
|
||||
() => loadStateFromPersistence(),
|
||||
(initState) => setupController(initState),
|
||||
])
|
||||
.then(() => console.log('MetaMask initialization complete.'))
|
||||
.catch((err) => { console.error(err) })
|
||||
initialize().catch(log.error)
|
||||
|
||||
// setup metamask mesh testing container
|
||||
setupMetamaskMeshMetrics()
|
||||
|
||||
async function initialize () {
|
||||
const initState = await loadStateFromPersistence()
|
||||
await setupController(initState)
|
||||
log.debug('MetaMask initialization complete.')
|
||||
}
|
||||
|
||||
//
|
||||
// State and Persistence
|
||||
//
|
||||
|
||||
function loadStateFromPersistence() {
|
||||
async function loadStateFromPersistence () {
|
||||
// migrations
|
||||
let migrator = new Migrator({ migrations })
|
||||
let initialState = migrator.generateInitialState(firstTimeState)
|
||||
return asyncQ.waterfall([
|
||||
// read from disk
|
||||
() => Promise.resolve(diskStore.getState() || initialState),
|
||||
// migrate data
|
||||
(versionedData) => migrator.migrateData(versionedData),
|
||||
// write to disk
|
||||
(versionedData) => {
|
||||
diskStore.putState(versionedData)
|
||||
return Promise.resolve(versionedData)
|
||||
},
|
||||
// resolve to just data
|
||||
(versionedData) => Promise.resolve(versionedData.data),
|
||||
])
|
||||
const migrator = new Migrator({ migrations })
|
||||
// read from disk
|
||||
let versionedData = diskStore.getState() || migrator.generateInitialState(firstTimeState)
|
||||
// migrate data
|
||||
versionedData = await migrator.migrateData(versionedData)
|
||||
// write to disk
|
||||
diskStore.putState(versionedData)
|
||||
// return just the data
|
||||
return versionedData.data
|
||||
}
|
||||
|
||||
function setupController (initState) {
|
||||
|
||||
//
|
||||
// MetaMask Controller
|
||||
//
|
||||
@ -78,15 +82,26 @@ function setupController (initState) {
|
||||
})
|
||||
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)
|
||||
const errorMessage = `Transaction Failed: ${txMeta.err.message}`
|
||||
raven.captureMessage(errorMessage, {
|
||||
// "extra" key is required by Sentry
|
||||
extra: txMeta,
|
||||
})
|
||||
})
|
||||
|
||||
// setup state persistence
|
||||
pipe(
|
||||
controller.store,
|
||||
pump(
|
||||
asStream(controller.store),
|
||||
storeTransform(versionifyData),
|
||||
diskStore
|
||||
asStream(diskStore)
|
||||
)
|
||||
|
||||
function versionifyData(state) {
|
||||
let versionedData = diskStore.getState()
|
||||
function versionifyData (state) {
|
||||
const versionedData = diskStore.getState()
|
||||
versionedData.data = state
|
||||
return versionedData
|
||||
}
|
||||
@ -97,21 +112,27 @@ function setupController (initState) {
|
||||
|
||||
extension.runtime.onConnect.addListener(connectRemote)
|
||||
function connectRemote (remotePort) {
|
||||
var isMetaMaskInternalProcess = remotePort.name === 'popup' || remotePort.name === 'notification'
|
||||
var portStream = new PortStream(remotePort)
|
||||
const isMetaMaskInternalProcess = remotePort.name === 'popup' || remotePort.name === 'notification'
|
||||
const portStream = new PortStream(remotePort)
|
||||
if (isMetaMaskInternalProcess) {
|
||||
// communication with popup
|
||||
popupIsOpen = popupIsOpen || (remotePort.name === 'popup')
|
||||
controller.setupTrustedCommunication(portStream, 'MetaMask', remotePort.name)
|
||||
controller.setupTrustedCommunication(portStream, 'MetaMask')
|
||||
// record popup as closed
|
||||
if (remotePort.sender.url.match(/home.html$/)) {
|
||||
openMetamaskTabsIDs[remotePort.sender.tab.id] = true
|
||||
}
|
||||
if (remotePort.name === 'popup') {
|
||||
endOfStream(portStream, () => {
|
||||
popupIsOpen = false
|
||||
if (remotePort.sender.url.match(/home.html$/)) {
|
||||
openMetamaskTabsIDs[remotePort.sender.tab.id] = false
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// communication with page
|
||||
var originDomain = urlUtil.parse(remotePort.sender.url).hostname
|
||||
const originDomain = urlUtil.parse(remotePort.sender.url).hostname
|
||||
controller.setupUntrustedCommunication(portStream, originDomain)
|
||||
}
|
||||
}
|
||||
@ -121,15 +142,18 @@ function setupController (initState) {
|
||||
//
|
||||
|
||||
updateBadge()
|
||||
controller.txManager.on('updateBadge', updateBadge)
|
||||
controller.txController.on('update:badge', updateBadge)
|
||||
controller.messageManager.on('updateBadge', updateBadge)
|
||||
controller.personalMessageManager.on('updateBadge', updateBadge)
|
||||
|
||||
// plugin badge text
|
||||
function updateBadge () {
|
||||
var label = ''
|
||||
var unapprovedTxCount = controller.txManager.unapprovedTxCount
|
||||
var unapprovedTxCount = controller.txController.getUnapprovedTxCount()
|
||||
var unapprovedMsgCount = controller.messageManager.unapprovedMsgCount
|
||||
var count = unapprovedTxCount + unapprovedMsgCount
|
||||
var unapprovedPersonalMsgs = controller.personalMessageManager.unapprovedPersonalMsgCount
|
||||
var unapprovedTypedMsgs = controller.typedMessageManager.unapprovedTypedMessagesCount
|
||||
var count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs + unapprovedTypedMsgs
|
||||
if (count) {
|
||||
label = String(count)
|
||||
}
|
||||
@ -138,7 +162,6 @@ function setupController (initState) {
|
||||
}
|
||||
|
||||
return Promise.resolve()
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
@ -147,7 +170,10 @@ function setupController (initState) {
|
||||
|
||||
// popup trigger
|
||||
function triggerUi () {
|
||||
if (!popupIsOpen) notificationManager.showPopup()
|
||||
extension.tabs.query({ active: true }, (tabs) => {
|
||||
const currentlyActiveMetamaskTab = tabs.find(tab => openMetamaskTabsIDs[tab.id])
|
||||
if (!popupIsOpen && !currentlyActiveMetamaskTab) notificationManager.showPopup()
|
||||
})
|
||||
}
|
||||
|
||||
// On first install, open a window to MetaMask website to how-it-works.
|
||||
|
@ -1,16 +1,44 @@
|
||||
const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask'
|
||||
const TESTNET_RPC_URL = 'https://ropsten.infura.io/metamask'
|
||||
const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask'
|
||||
const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask'
|
||||
const DEFAULT_RPC_URL = TESTNET_RPC_URL
|
||||
const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask'
|
||||
const LOCALHOST_RPC_URL = 'http://localhost:8545'
|
||||
|
||||
const MAINET_RPC_URL_BETA = 'https://mainnet.infura.io/metamask2'
|
||||
const ROPSTEN_RPC_URL_BETA = 'https://ropsten.infura.io/metamask2'
|
||||
const KOVAN_RPC_URL_BETA = 'https://kovan.infura.io/metamask2'
|
||||
const RINKEBY_RPC_URL_BETA = 'https://rinkeby.infura.io/metamask2'
|
||||
|
||||
const DEFAULT_RPC = 'rinkeby'
|
||||
const OLD_UI_NETWORK_TYPE = 'network'
|
||||
const BETA_UI_NETWORK_TYPE = 'networkBeta'
|
||||
|
||||
global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||
|
||||
module.exports = {
|
||||
network: {
|
||||
default: DEFAULT_RPC_URL,
|
||||
localhost: LOCALHOST_RPC_URL,
|
||||
mainnet: MAINET_RPC_URL,
|
||||
testnet: TESTNET_RPC_URL,
|
||||
morden: TESTNET_RPC_URL,
|
||||
ropsten: ROPSTEN_RPC_URL,
|
||||
kovan: KOVAN_RPC_URL,
|
||||
rinkeby: RINKEBY_RPC_URL,
|
||||
},
|
||||
// Used for beta UI
|
||||
networkBeta: {
|
||||
localhost: LOCALHOST_RPC_URL,
|
||||
mainnet: MAINET_RPC_URL_BETA,
|
||||
ropsten: ROPSTEN_RPC_URL_BETA,
|
||||
kovan: KOVAN_RPC_URL_BETA,
|
||||
rinkeby: RINKEBY_RPC_URL_BETA,
|
||||
},
|
||||
networkNames: {
|
||||
3: 'Ropsten',
|
||||
4: 'Rinkeby',
|
||||
42: 'Kovan',
|
||||
},
|
||||
enums: {
|
||||
DEFAULT_RPC,
|
||||
OLD_UI_NETWORK_TYPE,
|
||||
BETA_UI_NETWORK_TYPE,
|
||||
},
|
||||
}
|
||||
|
@ -1,12 +1,15 @@
|
||||
const LocalMessageDuplexStream = require('post-message-stream')
|
||||
const PongStream = require('ping-pong-stream/pong')
|
||||
const PortStream = require('./lib/port-stream.js')
|
||||
const ObjectMultiplex = require('./lib/obj-multiplex')
|
||||
const extension = require('extensionizer')
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const inpageText = fs.readFileSync(path.join(__dirname, 'inpage.js')).toString()
|
||||
const pump = require('pump')
|
||||
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('./lib/port-stream.js')
|
||||
|
||||
const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'scripts', 'inpage.js')).toString()
|
||||
const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('scripts/inpage.js') + '\n'
|
||||
const inpageBundle = inpageContent + inpageSuffix
|
||||
|
||||
// Eventually this streaming injection could be replaced with:
|
||||
// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction
|
||||
@ -24,8 +27,7 @@ function setupInjection () {
|
||||
try {
|
||||
// inject in-page script
|
||||
var scriptTag = document.createElement('script')
|
||||
scriptTag.src = extension.extension.getURL('scripts/inpage.js')
|
||||
scriptTag.textContent = inpageText
|
||||
scriptTag.textContent = inpageBundle
|
||||
scriptTag.onload = function () { this.parentNode.removeChild(this) }
|
||||
var container = document.head || document.documentElement
|
||||
// append as first child
|
||||
@ -37,35 +39,64 @@ function setupInjection () {
|
||||
|
||||
function setupStreams () {
|
||||
// setup communication to page and plugin
|
||||
var pageStream = new LocalMessageDuplexStream({
|
||||
const pageStream = new LocalMessageDuplexStream({
|
||||
name: 'contentscript',
|
||||
target: 'inpage',
|
||||
})
|
||||
pageStream.on('error', console.error)
|
||||
var pluginPort = extension.runtime.connect({name: 'contentscript'})
|
||||
var pluginStream = new PortStream(pluginPort)
|
||||
pluginStream.on('error', console.error)
|
||||
const pluginPort = extension.runtime.connect({ name: 'contentscript' })
|
||||
const pluginStream = new PortStream(pluginPort)
|
||||
|
||||
// forward communication plugin->inpage
|
||||
pageStream.pipe(pluginStream).pipe(pageStream)
|
||||
pump(
|
||||
pageStream,
|
||||
pluginStream,
|
||||
pageStream,
|
||||
(err) => logStreamDisconnectWarning('MetaMask Contentscript Forwarding', err)
|
||||
)
|
||||
|
||||
// setup local multistream channels
|
||||
var mx = ObjectMultiplex()
|
||||
mx.on('error', console.error)
|
||||
mx.pipe(pageStream).pipe(mx)
|
||||
const mux = new ObjectMultiplex()
|
||||
mux.setMaxListeners(25)
|
||||
|
||||
pump(
|
||||
mux,
|
||||
pageStream,
|
||||
mux,
|
||||
(err) => logStreamDisconnectWarning('MetaMask Inpage', err)
|
||||
)
|
||||
pump(
|
||||
mux,
|
||||
pluginStream,
|
||||
mux,
|
||||
(err) => logStreamDisconnectWarning('MetaMask Background', err)
|
||||
)
|
||||
|
||||
// connect ping stream
|
||||
var pongStream = new PongStream({ objectMode: true })
|
||||
pongStream.pipe(mx.createStream('pingpong')).pipe(pongStream)
|
||||
const pongStream = new PongStream({ objectMode: true })
|
||||
pump(
|
||||
mux,
|
||||
pongStream,
|
||||
mux,
|
||||
(err) => logStreamDisconnectWarning('MetaMask PingPongStream', err)
|
||||
)
|
||||
|
||||
// ignore unused channels (handled by background)
|
||||
mx.ignoreStream('provider')
|
||||
mx.ignoreStream('publicConfig')
|
||||
mx.ignoreStream('reload')
|
||||
// connect phishing warning stream
|
||||
const phishingStream = mux.createStream('phishing')
|
||||
phishingStream.once('data', redirectToPhishingWarning)
|
||||
|
||||
// ignore unused channels (handled by background, inpage)
|
||||
mux.ignoreStream('provider')
|
||||
mux.ignoreStream('publicConfig')
|
||||
}
|
||||
|
||||
function logStreamDisconnectWarning (remoteLabel, err) {
|
||||
let warningMsg = `MetamaskContentscript - lost connection to ${remoteLabel}`
|
||||
if (err) warningMsg += '\n' + err.stack
|
||||
console.warn(warningMsg)
|
||||
}
|
||||
|
||||
function shouldInjectWeb3 () {
|
||||
return doctypeCheck() || suffixCheck()
|
||||
return doctypeCheck() && suffixCheck() && documentElementCheck()
|
||||
}
|
||||
|
||||
function doctypeCheck () {
|
||||
@ -73,19 +104,32 @@ function doctypeCheck () {
|
||||
if (doctype) {
|
||||
return doctype.name === 'html'
|
||||
} else {
|
||||
return false
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function suffixCheck() {
|
||||
function suffixCheck () {
|
||||
var prohibitedTypes = ['xml', 'pdf']
|
||||
var currentUrl = window.location.href
|
||||
var currentRegex
|
||||
for (let i = 0; i < prohibitedTypes.length; i++) {
|
||||
currentRegex = new RegExp(`\.${prohibitedTypes[i]}$`)
|
||||
currentRegex = new RegExp(`\\.${prohibitedTypes[i]}$`)
|
||||
if (currentRegex.test(currentUrl)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function documentElementCheck () {
|
||||
var documentElement = document.documentElement.nodeName
|
||||
if (documentElement) {
|
||||
return documentElement.toLowerCase() === 'html'
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function redirectToPhishingWarning () {
|
||||
console.log('MetaMask - redirecting to phishing warning')
|
||||
window.location.href = 'https://metamask.io/phishing.html'
|
||||
}
|
||||
|
@ -39,11 +39,11 @@ class AddressBookController {
|
||||
// pushed object is an object of two fields. Current behavior does not set an
|
||||
// upper limit to the number of addresses.
|
||||
_addToAddressBook (address, name) {
|
||||
let addressBook = this._getAddressBook()
|
||||
let identities = this._getIdentities()
|
||||
const addressBook = this._getAddressBook()
|
||||
const identities = this._getIdentities()
|
||||
|
||||
let addressBookIndex = addressBook.findIndex((element) => { return element.address.toLowerCase() === address.toLowerCase() || element.name === name })
|
||||
let identitiesIndex = Object.keys(identities).findIndex((element) => { return element.toLowerCase() === address.toLowerCase() })
|
||||
const addressBookIndex = addressBook.findIndex((element) => { return element.address.toLowerCase() === address.toLowerCase() || element.name === name })
|
||||
const identitiesIndex = Object.keys(identities).findIndex((element) => { return element.toLowerCase() === address.toLowerCase() })
|
||||
// trigger this condition if we own this address--no need to overwrite.
|
||||
if (identitiesIndex !== -1) {
|
||||
return Promise.resolve(addressBook)
|
||||
|
80
app/scripts/controllers/balance.js
Normal file
@ -0,0 +1,80 @@
|
||||
const ObservableStore = require('obs-store')
|
||||
const PendingBalanceCalculator = require('../lib/pending-balance-calculator')
|
||||
const BN = require('ethereumjs-util').BN
|
||||
|
||||
class BalanceController {
|
||||
|
||||
constructor (opts = {}) {
|
||||
this._validateParams(opts)
|
||||
const { address, accountTracker, txController, blockTracker } = opts
|
||||
|
||||
this.address = address
|
||||
this.accountTracker = accountTracker
|
||||
this.txController = txController
|
||||
this.blockTracker = blockTracker
|
||||
|
||||
const initState = {
|
||||
ethBalance: undefined,
|
||||
}
|
||||
this.store = new ObservableStore(initState)
|
||||
|
||||
this.balanceCalc = new PendingBalanceCalculator({
|
||||
getBalance: () => this._getBalance(),
|
||||
getPendingTransactions: this._getPendingTransactions.bind(this),
|
||||
})
|
||||
|
||||
this._registerUpdates()
|
||||
}
|
||||
|
||||
async updateBalance () {
|
||||
const balance = await this.balanceCalc.getBalance()
|
||||
this.store.updateState({
|
||||
ethBalance: balance,
|
||||
})
|
||||
}
|
||||
|
||||
_registerUpdates () {
|
||||
const update = this.updateBalance.bind(this)
|
||||
|
||||
this.txController.on('tx:status-update', (txId, status) => {
|
||||
switch (status) {
|
||||
case 'submitted':
|
||||
case 'confirmed':
|
||||
case 'failed':
|
||||
update()
|
||||
return
|
||||
default:
|
||||
return
|
||||
}
|
||||
})
|
||||
this.accountTracker.store.subscribe(update)
|
||||
this.blockTracker.on('block', update)
|
||||
}
|
||||
|
||||
async _getBalance () {
|
||||
const { accounts } = this.accountTracker.store.getState()
|
||||
const entry = accounts[this.address]
|
||||
const balance = entry.balance
|
||||
return balance ? new BN(balance.substring(2), 16) : undefined
|
||||
}
|
||||
|
||||
async _getPendingTransactions () {
|
||||
const pending = this.txController.getFilteredTxList({
|
||||
from: this.address,
|
||||
status: 'submitted',
|
||||
err: undefined,
|
||||
})
|
||||
return pending
|
||||
}
|
||||
|
||||
_validateParams (opts) {
|
||||
const { address, accountTracker, txController, blockTracker } = opts
|
||||
if (!address || !accountTracker || !txController || !blockTracker) {
|
||||
const error = 'Cannot construct a balance checker without address, accountTracker, txController, and blockTracker.'
|
||||
throw new Error(error)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = BalanceController
|
60
app/scripts/controllers/blacklist.js
Normal file
@ -0,0 +1,60 @@
|
||||
const ObservableStore = require('obs-store')
|
||||
const extend = require('xtend')
|
||||
const PhishingDetector = require('eth-phishing-detect/src/detector')
|
||||
|
||||
// compute phishing lists
|
||||
const PHISHING_DETECTION_CONFIG = require('eth-phishing-detect/src/config.json')
|
||||
// every four minutes
|
||||
const POLLING_INTERVAL = 4 * 60 * 1000
|
||||
|
||||
class BlacklistController {
|
||||
|
||||
constructor (opts = {}) {
|
||||
const initState = extend({
|
||||
phishing: PHISHING_DETECTION_CONFIG,
|
||||
}, opts.initState)
|
||||
this.store = new ObservableStore(initState)
|
||||
// phishing detector
|
||||
this._phishingDetector = null
|
||||
this._setupPhishingDetector(initState.phishing)
|
||||
// polling references
|
||||
this._phishingUpdateIntervalRef = null
|
||||
}
|
||||
|
||||
//
|
||||
// PUBLIC METHODS
|
||||
//
|
||||
|
||||
checkForPhishing (hostname) {
|
||||
if (!hostname) return false
|
||||
const { result } = this._phishingDetector.check(hostname)
|
||||
return result
|
||||
}
|
||||
|
||||
async updatePhishingList () {
|
||||
const response = await fetch('https://api.infura.io/v2/blacklist')
|
||||
const phishing = await response.json()
|
||||
this.store.updateState({ phishing })
|
||||
this._setupPhishingDetector(phishing)
|
||||
return phishing
|
||||
}
|
||||
|
||||
scheduleUpdates () {
|
||||
if (this._phishingUpdateIntervalRef) return
|
||||
this.updatePhishingList()
|
||||
this._phishingUpdateIntervalRef = setInterval(() => {
|
||||
this.updatePhishingList()
|
||||
}, POLLING_INTERVAL)
|
||||
}
|
||||
|
||||
//
|
||||
// PRIVATE METHODS
|
||||
//
|
||||
|
||||
_setupPhishingDetector (config) {
|
||||
this._phishingDetector = new PhishingDetector(config)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BlacklistController
|
||||
|
77
app/scripts/controllers/computed-balances.js
Normal file
@ -0,0 +1,77 @@
|
||||
const ObservableStore = require('obs-store')
|
||||
const extend = require('xtend')
|
||||
const BalanceController = require('./balance')
|
||||
|
||||
class ComputedbalancesController {
|
||||
|
||||
constructor (opts = {}) {
|
||||
const { accountTracker, txController, blockTracker } = opts
|
||||
this.accountTracker = accountTracker
|
||||
this.txController = txController
|
||||
this.blockTracker = blockTracker
|
||||
|
||||
const initState = extend({
|
||||
computedBalances: {},
|
||||
}, opts.initState)
|
||||
this.store = new ObservableStore(initState)
|
||||
this.balances = {}
|
||||
|
||||
this._initBalanceUpdating()
|
||||
}
|
||||
|
||||
updateAllBalances () {
|
||||
Object.keys(this.balances).forEach((balance) => {
|
||||
const address = balance.address
|
||||
this.balances[address].updateBalance()
|
||||
})
|
||||
}
|
||||
|
||||
_initBalanceUpdating () {
|
||||
const store = this.accountTracker.store.getState()
|
||||
this.syncAllAccountsFromStore(store)
|
||||
this.accountTracker.store.subscribe(this.syncAllAccountsFromStore.bind(this))
|
||||
}
|
||||
|
||||
syncAllAccountsFromStore (store) {
|
||||
const upstream = Object.keys(store.accounts)
|
||||
const balances = Object.keys(this.balances)
|
||||
.map(address => this.balances[address])
|
||||
|
||||
// Follow new addresses
|
||||
for (const address in balances) {
|
||||
this.trackAddressIfNotAlready(address)
|
||||
}
|
||||
|
||||
// Unfollow old ones
|
||||
balances.forEach(({ address }) => {
|
||||
if (!upstream.includes(address)) {
|
||||
delete this.balances[address]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
trackAddressIfNotAlready (address) {
|
||||
const state = this.store.getState()
|
||||
if (!(address in state.computedBalances)) {
|
||||
this.trackAddress(address)
|
||||
}
|
||||
}
|
||||
|
||||
trackAddress (address) {
|
||||
const updater = new BalanceController({
|
||||
address,
|
||||
accountTracker: this.accountTracker,
|
||||
txController: this.txController,
|
||||
blockTracker: this.blockTracker,
|
||||
})
|
||||
updater.store.subscribe((accountBalance) => {
|
||||
const newState = this.store.getState()
|
||||
newState.computedBalances[address] = accountBalance
|
||||
this.store.updateState(newState)
|
||||
})
|
||||
this.balances[address] = updater
|
||||
updater.updateBalance()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ComputedbalancesController
|
@ -8,7 +8,7 @@ class CurrencyController {
|
||||
|
||||
constructor (opts = {}) {
|
||||
const initState = extend({
|
||||
currentCurrency: 'USD',
|
||||
currentCurrency: 'usd',
|
||||
conversionRate: 0,
|
||||
conversionDate: 'N/A',
|
||||
}, opts.initState)
|
||||
@ -45,15 +45,17 @@ class CurrencyController {
|
||||
|
||||
updateConversionRate () {
|
||||
const currentCurrency = this.getCurrentCurrency()
|
||||
return fetch(`https://www.cryptonator.com/api/ticker/eth-${currentCurrency}`)
|
||||
return fetch(`https://api.infura.io/v1/ticker/eth${currentCurrency.toLowerCase()}`)
|
||||
.then(response => response.json())
|
||||
.then((parsedResponse) => {
|
||||
this.setConversionRate(Number(parsedResponse.ticker.price))
|
||||
this.setConversionRate(Number(parsedResponse.bid))
|
||||
this.setConversionDate(Number(parsedResponse.timestamp))
|
||||
}).catch((err) => {
|
||||
console.warn('MetaMask - Failed to query currency conversion.')
|
||||
this.setConversionRate(0)
|
||||
this.setConversionDate('N/A')
|
||||
if (err) {
|
||||
console.warn('MetaMask - Failed to query currency conversion.')
|
||||
this.setConversionRate(0)
|
||||
this.setConversionDate('N/A')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
43
app/scripts/controllers/infura.js
Normal file
@ -0,0 +1,43 @@
|
||||
const ObservableStore = require('obs-store')
|
||||
const extend = require('xtend')
|
||||
|
||||
// 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.
|
||||
checkInfuraNetworkStatus () {
|
||||
return fetch('https://api.infura.io/v1/status/metamask')
|
||||
.then(response => response.json())
|
||||
.then((parsedResponse) => {
|
||||
this.store.updateState({
|
||||
infuraNetworkStatus: parsedResponse,
|
||||
})
|
||||
return parsedResponse
|
||||
})
|
||||
}
|
||||
|
||||
scheduleInfuraNetworkCheck () {
|
||||
if (this.conversionInterval) {
|
||||
clearInterval(this.conversionInterval)
|
||||
}
|
||||
this.conversionInterval = setInterval(() => {
|
||||
this.checkInfuraNetworkStatus()
|
||||
}, POLLING_INTERVAL)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = InfuraController
|
214
app/scripts/controllers/network.js
Normal file
@ -0,0 +1,214 @@
|
||||
const assert = require('assert')
|
||||
const EventEmitter = require('events')
|
||||
const createMetamaskProvider = require('web3-provider-engine/zero.js')
|
||||
const SubproviderFromProvider = require('web3-provider-engine/subproviders/web3.js')
|
||||
const createInfuraProvider = require('eth-json-rpc-infura/src/createProvider')
|
||||
const ObservableStore = require('obs-store')
|
||||
const ComposedStore = require('obs-store/lib/composed')
|
||||
const extend = require('xtend')
|
||||
const EthQuery = require('eth-query')
|
||||
const createEventEmitterProxy = require('../lib/events-proxy.js')
|
||||
const networkConfig = require('../config.js')
|
||||
const { OLD_UI_NETWORK_TYPE, DEFAULT_RPC } = networkConfig.enums
|
||||
const INFURA_PROVIDER_TYPES = ['ropsten', 'rinkeby', 'kovan', 'mainnet']
|
||||
|
||||
module.exports = class NetworkController extends EventEmitter {
|
||||
|
||||
constructor (config) {
|
||||
super()
|
||||
|
||||
this._networkEndpointVersion = OLD_UI_NETWORK_TYPE
|
||||
this._networkEndpoints = this.getNetworkEndpoints(OLD_UI_NETWORK_TYPE)
|
||||
this._defaultRpc = this._networkEndpoints[DEFAULT_RPC]
|
||||
|
||||
config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider)
|
||||
this.networkStore = new ObservableStore('loading')
|
||||
this.providerStore = new ObservableStore(config.provider)
|
||||
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore })
|
||||
this._proxy = createEventEmitterProxy()
|
||||
|
||||
this.on('networkDidChange', this.lookupNetwork)
|
||||
}
|
||||
|
||||
async setNetworkEndpoints (version) {
|
||||
if (version === this._networkEndpointVersion) {
|
||||
return
|
||||
}
|
||||
|
||||
this._networkEndpointVersion = version
|
||||
this._networkEndpoints = this.getNetworkEndpoints(version)
|
||||
this._defaultRpc = this._networkEndpoints[DEFAULT_RPC]
|
||||
const { type } = this.getProviderConfig()
|
||||
|
||||
return this.setProviderType(type, true)
|
||||
}
|
||||
|
||||
getNetworkEndpoints (version = OLD_UI_NETWORK_TYPE) {
|
||||
return networkConfig[version]
|
||||
}
|
||||
|
||||
initializeProvider (_providerParams) {
|
||||
this._baseProviderParams = _providerParams
|
||||
const { type, rpcTarget } = this.providerStore.getState()
|
||||
// map rpcTarget to rpcUrl
|
||||
const opts = {
|
||||
type,
|
||||
rpcUrl: rpcTarget,
|
||||
}
|
||||
this._configureProvider(opts)
|
||||
this._proxy.on('block', this._logBlock.bind(this))
|
||||
this._proxy.on('error', this.verifyNetwork.bind(this))
|
||||
this.ethQuery = new EthQuery(this._proxy)
|
||||
this.lookupNetwork()
|
||||
return this._proxy
|
||||
}
|
||||
|
||||
verifyNetwork () {
|
||||
// Check network when restoring connectivity:
|
||||
if (this.isNetworkLoading()) this.lookupNetwork()
|
||||
}
|
||||
|
||||
getNetworkState () {
|
||||
return this.networkStore.getState()
|
||||
}
|
||||
|
||||
setNetworkState (network) {
|
||||
return this.networkStore.putState(network)
|
||||
}
|
||||
|
||||
isNetworkLoading () {
|
||||
return this.getNetworkState() === 'loading'
|
||||
}
|
||||
|
||||
lookupNetwork () {
|
||||
// Prevent firing when provider is not defined.
|
||||
if (!this.ethQuery || !this.ethQuery.sendAsync) {
|
||||
return log.warn('NetworkController - lookupNetwork aborted due to missing ethQuery')
|
||||
}
|
||||
this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => {
|
||||
if (err) return this.setNetworkState('loading')
|
||||
log.info('web3.getNetwork returned ' + network)
|
||||
this.setNetworkState(network)
|
||||
})
|
||||
}
|
||||
|
||||
setRpcTarget (rpcUrl) {
|
||||
this.providerStore.updateState({
|
||||
type: 'rpc',
|
||||
rpcTarget: rpcUrl,
|
||||
})
|
||||
this._switchNetwork({ rpcUrl })
|
||||
}
|
||||
|
||||
getCurrentRpcAddress () {
|
||||
const provider = this.getProviderConfig()
|
||||
if (!provider) return null
|
||||
return this.getRpcAddressForType(provider.type)
|
||||
}
|
||||
|
||||
async setProviderType (type, forceUpdate = false) {
|
||||
assert(type !== 'rpc', `NetworkController.setProviderType - cannot connect by type "rpc"`)
|
||||
// skip if type already matches
|
||||
if (type === this.getProviderConfig().type && !forceUpdate) {
|
||||
return
|
||||
}
|
||||
|
||||
const rpcTarget = this.getRpcAddressForType(type)
|
||||
assert(rpcTarget, `NetworkController - unknown rpc address for type "${type}"`)
|
||||
this.providerStore.updateState({ type, rpcTarget })
|
||||
this._switchNetwork({ type })
|
||||
}
|
||||
|
||||
getProviderConfig () {
|
||||
return this.providerStore.getState()
|
||||
}
|
||||
|
||||
getRpcAddressForType (type, provider = this.getProviderConfig()) {
|
||||
if (this._networkEndpoints[type]) {
|
||||
return this._networkEndpoints[type]
|
||||
}
|
||||
|
||||
return provider && provider.rpcTarget ? provider.rpcTarget : this._defaultRpc
|
||||
}
|
||||
|
||||
//
|
||||
// Private
|
||||
//
|
||||
|
||||
_switchNetwork (opts) {
|
||||
this.setNetworkState('loading')
|
||||
this._configureProvider(opts)
|
||||
this.emit('networkDidChange')
|
||||
}
|
||||
|
||||
_configureProvider (opts) {
|
||||
// type-based rpc endpoints
|
||||
const { type } = opts
|
||||
if (type) {
|
||||
// type-based infura rpc endpoints
|
||||
const isInfura = INFURA_PROVIDER_TYPES.includes(type)
|
||||
opts.rpcUrl = this.getRpcAddressForType(type)
|
||||
if (isInfura) {
|
||||
this._configureInfuraProvider(opts)
|
||||
// other type-based rpc endpoints
|
||||
} else {
|
||||
this._configureStandardProvider(opts)
|
||||
}
|
||||
// url-based rpc endpoints
|
||||
} else {
|
||||
this._configureStandardProvider(opts)
|
||||
}
|
||||
}
|
||||
|
||||
_configureInfuraProvider (opts) {
|
||||
log.info('_configureInfuraProvider', opts)
|
||||
const infuraProvider = createInfuraProvider({
|
||||
network: opts.type,
|
||||
})
|
||||
const infuraSubprovider = new SubproviderFromProvider(infuraProvider)
|
||||
const providerParams = extend(this._baseProviderParams, {
|
||||
rpcUrl: opts.rpcUrl,
|
||||
engineParams: {
|
||||
pollingInterval: 8000,
|
||||
blockTrackerProvider: infuraProvider,
|
||||
},
|
||||
dataSubprovider: infuraSubprovider,
|
||||
})
|
||||
const provider = createMetamaskProvider(providerParams)
|
||||
this._setProvider(provider)
|
||||
}
|
||||
|
||||
_configureStandardProvider ({ rpcUrl }) {
|
||||
const providerParams = extend(this._baseProviderParams, {
|
||||
rpcUrl,
|
||||
engineParams: {
|
||||
pollingInterval: 8000,
|
||||
},
|
||||
})
|
||||
const provider = createMetamaskProvider(providerParams)
|
||||
this._setProvider(provider)
|
||||
}
|
||||
|
||||
_setProvider (provider) {
|
||||
// collect old block tracker events
|
||||
const oldProvider = this._provider
|
||||
let blockTrackerHandlers
|
||||
if (oldProvider) {
|
||||
// capture old block handlers
|
||||
blockTrackerHandlers = oldProvider._blockTracker.proxyEventHandlers
|
||||
// tear down
|
||||
oldProvider.removeAllListeners()
|
||||
oldProvider.stop()
|
||||
}
|
||||
// override block tracler
|
||||
provider._blockTracker = createEventEmitterProxy(provider._blockTracker, blockTrackerHandlers)
|
||||
// set as new provider
|
||||
this._provider = provider
|
||||
this._proxy.setTarget(provider)
|
||||
}
|
||||
|
||||
_logBlock (block) {
|
||||
log.info(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`)
|
||||
this.verifyNetwork()
|
||||
}
|
||||
}
|
@ -7,13 +7,22 @@ class PreferencesController {
|
||||
constructor (opts = {}) {
|
||||
const initState = extend({
|
||||
frequentRpcList: [],
|
||||
currentAccountTab: 'history',
|
||||
tokens: [],
|
||||
useBlockie: false,
|
||||
featureFlags: {},
|
||||
}, opts.initState)
|
||||
this.store = new ObservableStore(initState)
|
||||
}
|
||||
// PUBLIC METHODS
|
||||
|
||||
//
|
||||
// PUBLIC METHODS
|
||||
//
|
||||
setUseBlockie (val) {
|
||||
this.store.updateState({ useBlockie: val })
|
||||
}
|
||||
|
||||
getUseBlockie () {
|
||||
return this.store.getState().useBlockie
|
||||
}
|
||||
|
||||
setSelectedAddress (_address) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -23,10 +32,44 @@ class PreferencesController {
|
||||
})
|
||||
}
|
||||
|
||||
getSelectedAddress (_address) {
|
||||
getSelectedAddress () {
|
||||
return this.store.getState().selectedAddress
|
||||
}
|
||||
|
||||
async addToken (rawAddress, symbol, decimals) {
|
||||
const address = normalizeAddress(rawAddress)
|
||||
const newEntry = { address, symbol, decimals }
|
||||
|
||||
const tokens = this.store.getState().tokens
|
||||
const previousEntry = tokens.find((token, index) => {
|
||||
return token.address === address
|
||||
})
|
||||
const previousIndex = tokens.indexOf(previousEntry)
|
||||
|
||||
if (previousEntry) {
|
||||
tokens[previousIndex] = newEntry
|
||||
} else {
|
||||
tokens.push(newEntry)
|
||||
}
|
||||
|
||||
this.store.updateState({ tokens })
|
||||
|
||||
return Promise.resolve(tokens)
|
||||
}
|
||||
|
||||
removeToken (rawAddress) {
|
||||
const tokens = this.store.getState().tokens
|
||||
|
||||
const updatedTokens = tokens.filter(token => token.address !== rawAddress)
|
||||
|
||||
this.store.updateState({ tokens: updatedTokens })
|
||||
return Promise.resolve(updatedTokens)
|
||||
}
|
||||
|
||||
getTokens () {
|
||||
return this.store.getState().tokens
|
||||
}
|
||||
|
||||
updateFrequentRpcList (_url) {
|
||||
return this.addToFrequentRpcList(_url)
|
||||
.then((rpcList) => {
|
||||
@ -35,9 +78,16 @@ class PreferencesController {
|
||||
})
|
||||
}
|
||||
|
||||
setCurrentAccountTab (currentAccountTab) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.store.updateState({ currentAccountTab })
|
||||
resolve()
|
||||
})
|
||||
}
|
||||
|
||||
addToFrequentRpcList (_url) {
|
||||
let rpcList = this.getFrequentRpcList()
|
||||
let index = rpcList.findIndex((element) => { return element === _url })
|
||||
const rpcList = this.getFrequentRpcList()
|
||||
const index = rpcList.findIndex((element) => { return element === _url })
|
||||
if (index !== -1) {
|
||||
rpcList.splice(index, 1)
|
||||
}
|
||||
@ -54,12 +104,24 @@ class PreferencesController {
|
||||
return this.store.getState().frequentRpcList
|
||||
}
|
||||
|
||||
setFeatureFlag (feature, activated) {
|
||||
const currentFeatureFlags = this.store.getState().featureFlags
|
||||
const updatedFeatureFlags = {
|
||||
...currentFeatureFlags,
|
||||
[feature]: activated,
|
||||
}
|
||||
|
||||
this.store.updateState({ featureFlags: updatedFeatureFlags })
|
||||
|
||||
return Promise.resolve(updatedFeatureFlags)
|
||||
}
|
||||
|
||||
getFeatureFlags () {
|
||||
return this.store.getState().featureFlags
|
||||
}
|
||||
//
|
||||
// PRIVATE METHODS
|
||||
//
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
module.exports = PreferencesController
|
||||
|
110
app/scripts/controllers/recent-blocks.js
Normal file
@ -0,0 +1,110 @@
|
||||
const ObservableStore = require('obs-store')
|
||||
const extend = require('xtend')
|
||||
const BN = require('ethereumjs-util').BN
|
||||
const EthQuery = require('eth-query')
|
||||
|
||||
class RecentBlocksController {
|
||||
|
||||
constructor (opts = {}) {
|
||||
const { blockTracker, provider } = opts
|
||||
this.blockTracker = blockTracker
|
||||
this.ethQuery = new EthQuery(provider)
|
||||
this.historyLength = opts.historyLength || 40
|
||||
|
||||
const initState = extend({
|
||||
recentBlocks: [],
|
||||
}, opts.initState)
|
||||
this.store = new ObservableStore(initState)
|
||||
|
||||
this.blockTracker.on('block', this.processBlock.bind(this))
|
||||
this.backfill()
|
||||
}
|
||||
|
||||
resetState () {
|
||||
this.store.updateState({
|
||||
recentBlocks: [],
|
||||
})
|
||||
}
|
||||
|
||||
processBlock (newBlock) {
|
||||
const block = this.mapTransactionsToPrices(newBlock)
|
||||
|
||||
const state = this.store.getState()
|
||||
state.recentBlocks.push(block)
|
||||
|
||||
while (state.recentBlocks.length > this.historyLength) {
|
||||
state.recentBlocks.shift()
|
||||
}
|
||||
|
||||
this.store.updateState(state)
|
||||
}
|
||||
|
||||
backfillBlock (newBlock) {
|
||||
const block = this.mapTransactionsToPrices(newBlock)
|
||||
|
||||
const state = this.store.getState()
|
||||
|
||||
if (state.recentBlocks.length < this.historyLength) {
|
||||
state.recentBlocks.unshift(block)
|
||||
}
|
||||
|
||||
this.store.updateState(state)
|
||||
}
|
||||
|
||||
mapTransactionsToPrices (newBlock) {
|
||||
const block = extend(newBlock, {
|
||||
gasPrices: newBlock.transactions.map((tx) => {
|
||||
return tx.gasPrice
|
||||
}),
|
||||
})
|
||||
delete block.transactions
|
||||
return block
|
||||
}
|
||||
|
||||
async backfill() {
|
||||
this.blockTracker.once('block', async (block) => {
|
||||
let blockNum = block.number
|
||||
let recentBlocks
|
||||
let state = this.store.getState()
|
||||
recentBlocks = state.recentBlocks
|
||||
|
||||
while (recentBlocks.length < this.historyLength) {
|
||||
try {
|
||||
let blockNumBn = new BN(blockNum.substr(2), 16)
|
||||
const newNum = blockNumBn.subn(1).toString(10)
|
||||
const newBlock = await this.getBlockByNumber(newNum)
|
||||
|
||||
if (newBlock) {
|
||||
this.backfillBlock(newBlock)
|
||||
blockNum = newBlock.number
|
||||
}
|
||||
|
||||
state = this.store.getState()
|
||||
recentBlocks = state.recentBlocks
|
||||
} catch (e) {
|
||||
log.error(e)
|
||||
}
|
||||
await this.wait()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async wait () {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, 100)
|
||||
})
|
||||
}
|
||||
|
||||
async getBlockByNumber (number) {
|
||||
const bn = new BN(number)
|
||||
return new Promise((resolve, reject) => {
|
||||
this.ethQuery.getBlockByNumber('0x' + bn.toString(16), true, (err, block) => {
|
||||
if (err) reject(err)
|
||||
resolve(block)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = RecentBlocksController
|
325
app/scripts/controllers/transactions.js
Normal file
@ -0,0 +1,325 @@
|
||||
const EventEmitter = require('events')
|
||||
const ObservableStore = require('obs-store')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const Transaction = require('ethereumjs-tx')
|
||||
const EthQuery = require('ethjs-query')
|
||||
const TransactionStateManger = require('../lib/tx-state-manager')
|
||||
const TxGasUtil = require('../lib/tx-gas-utils')
|
||||
const PendingTransactionTracker = require('../lib/pending-tx-tracker')
|
||||
const createId = require('../lib/random-id')
|
||||
const NonceTracker = require('../lib/nonce-tracker')
|
||||
|
||||
/*
|
||||
Transaction Controller is an aggregate of sub-controllers and trackers
|
||||
composing them in a way to be exposed to the metamask controller
|
||||
- txStateManager
|
||||
responsible for the state of a transaction and
|
||||
storing the transaction
|
||||
- pendingTxTracker
|
||||
watching blocks for transactions to be include
|
||||
and emitting confirmed events
|
||||
- txGasUtil
|
||||
gas calculations and safety buffering
|
||||
- nonceTracker
|
||||
calculating nonces
|
||||
*/
|
||||
|
||||
module.exports = class TransactionController extends EventEmitter {
|
||||
constructor (opts) {
|
||||
super()
|
||||
this.networkStore = opts.networkStore || new ObservableStore({})
|
||||
this.preferencesStore = opts.preferencesStore || new ObservableStore({})
|
||||
this.provider = opts.provider
|
||||
this.blockTracker = opts.blockTracker
|
||||
this.signEthTx = opts.signTransaction
|
||||
this.getGasPrice = opts.getGasPrice
|
||||
|
||||
this.memStore = new ObservableStore({})
|
||||
this.query = new EthQuery(this.provider)
|
||||
this.txGasUtil = new TxGasUtil(this.provider)
|
||||
|
||||
this.txStateManager = new TransactionStateManger({
|
||||
initState: opts.initState,
|
||||
txHistoryLimit: opts.txHistoryLimit,
|
||||
getNetwork: this.getNetwork.bind(this),
|
||||
})
|
||||
|
||||
this.txStateManager.getFilteredTxList({
|
||||
status: 'unapproved',
|
||||
loadingDefaults: true,
|
||||
}).forEach((tx) => {
|
||||
this.addTxDefaults(tx)
|
||||
.then((txMeta) => {
|
||||
txMeta.loadingDefaults = false
|
||||
this.txStateManager.updateTx(txMeta, 'transactions: gas estimation for tx on boot')
|
||||
}).catch((error) => {
|
||||
this.txStateManager.setTxStatusFailed(tx.id, error)
|
||||
})
|
||||
})
|
||||
|
||||
this.txStateManager.getFilteredTxList({
|
||||
status: 'approved',
|
||||
}).forEach((txMeta) => {
|
||||
const txSignError = new Error('Transaction found as "approved" during boot - possibly stuck during signing')
|
||||
this.txStateManager.setTxStatusFailed(txMeta.id, txSignError)
|
||||
})
|
||||
|
||||
|
||||
this.store = this.txStateManager.store
|
||||
this.txStateManager.on('tx:status-update', this.emit.bind(this, 'tx:status-update'))
|
||||
this.nonceTracker = new NonceTracker({
|
||||
provider: this.provider,
|
||||
getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
|
||||
getConfirmedTransactions: (address) => {
|
||||
return this.txStateManager.getFilteredTxList({
|
||||
from: address,
|
||||
status: 'confirmed',
|
||||
err: undefined,
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
this.pendingTxTracker = new PendingTransactionTracker({
|
||||
provider: this.provider,
|
||||
nonceTracker: this.nonceTracker,
|
||||
publishTransaction: (rawTx) => this.query.sendRawTransaction(rawTx),
|
||||
getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
|
||||
getCompletedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager),
|
||||
})
|
||||
|
||||
this.txStateManager.store.subscribe(() => this.emit('update:badge'))
|
||||
|
||||
this.pendingTxTracker.on('tx:warning', (txMeta) => {
|
||||
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('tx:confirmed', this.txStateManager.setTxStatusConfirmed.bind(this.txStateManager))
|
||||
this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => {
|
||||
if (!txMeta.firstRetryBlockNumber) {
|
||||
txMeta.firstRetryBlockNumber = latestBlockNumber
|
||||
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:block-update')
|
||||
}
|
||||
})
|
||||
this.pendingTxTracker.on('tx:retry', (txMeta) => {
|
||||
if (!('retryCount' in txMeta)) txMeta.retryCount = 0
|
||||
txMeta.retryCount++
|
||||
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:retry')
|
||||
})
|
||||
|
||||
this.blockTracker.on('block', this.pendingTxTracker.checkForTxInBlock.bind(this.pendingTxTracker))
|
||||
// this is a little messy but until ethstore has been either
|
||||
// removed or redone this is to guard against the race condition
|
||||
this.blockTracker.on('latest', this.pendingTxTracker.resubmitPendingTxs.bind(this.pendingTxTracker))
|
||||
this.blockTracker.on('sync', this.pendingTxTracker.queryPendingTxs.bind(this.pendingTxTracker))
|
||||
// memstore is computed from a few different stores
|
||||
this._updateMemstore()
|
||||
this.txStateManager.store.subscribe(() => this._updateMemstore())
|
||||
this.networkStore.subscribe(() => this._updateMemstore())
|
||||
this.preferencesStore.subscribe(() => this._updateMemstore())
|
||||
}
|
||||
|
||||
getState () {
|
||||
return this.memStore.getState()
|
||||
}
|
||||
|
||||
getNetwork () {
|
||||
return this.networkStore.getState()
|
||||
}
|
||||
|
||||
getSelectedAddress () {
|
||||
return this.preferencesStore.getState().selectedAddress
|
||||
}
|
||||
|
||||
getUnapprovedTxCount () {
|
||||
return Object.keys(this.txStateManager.getUnapprovedTxList()).length
|
||||
}
|
||||
|
||||
getPendingTxCount (account) {
|
||||
return this.txStateManager.getPendingTransactions(account).length
|
||||
}
|
||||
|
||||
getFilteredTxList (opts) {
|
||||
return this.txStateManager.getFilteredTxList(opts)
|
||||
}
|
||||
|
||||
getChainId () {
|
||||
const networkState = this.networkStore.getState()
|
||||
const getChainId = parseInt(networkState)
|
||||
if (Number.isNaN(getChainId)) {
|
||||
return 0
|
||||
} else {
|
||||
return getChainId
|
||||
}
|
||||
}
|
||||
|
||||
wipeTransactions (address) {
|
||||
this.txStateManager.wipeTransactions(address)
|
||||
}
|
||||
|
||||
// Adds a tx to the txlist
|
||||
addTx (txMeta) {
|
||||
this.txStateManager.addTx(txMeta)
|
||||
this.emit(`${txMeta.id}:unapproved`, txMeta)
|
||||
}
|
||||
|
||||
async newUnapprovedTransaction (txParams) {
|
||||
log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`)
|
||||
const initialTxMeta = await this.addUnapprovedTransaction(txParams)
|
||||
// listen for tx completion (success, fail)
|
||||
return new Promise((resolve, reject) => {
|
||||
this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => {
|
||||
switch (finishedTxMeta.status) {
|
||||
case 'submitted':
|
||||
return resolve(finishedTxMeta.hash)
|
||||
case 'rejected':
|
||||
return reject(new Error('MetaMask Tx Signature: User denied transaction signature.'))
|
||||
case 'failed':
|
||||
return reject(new Error(finishedTxMeta.err.message))
|
||||
default:
|
||||
return reject(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async addUnapprovedTransaction (txParams) {
|
||||
// validate
|
||||
await this.txGasUtil.validateTxParams(txParams)
|
||||
// construct txMeta
|
||||
const txMeta = {
|
||||
id: createId(),
|
||||
time: (new Date()).getTime(),
|
||||
status: 'unapproved',
|
||||
metamaskNetworkId: this.getNetwork(),
|
||||
txParams: txParams,
|
||||
loadingDefaults: true,
|
||||
}
|
||||
this.addTx(txMeta)
|
||||
this.emit('newUnapprovedTx', txMeta)
|
||||
// add default tx params
|
||||
try {
|
||||
await this.addTxDefaults(txMeta)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
this.txStateManager.setTxStatusFailed(txMeta.id, error)
|
||||
throw error
|
||||
}
|
||||
txMeta.loadingDefaults = false
|
||||
// save txMeta
|
||||
this.txStateManager.updateTx(txMeta)
|
||||
|
||||
return txMeta
|
||||
}
|
||||
|
||||
async addTxDefaults (txMeta) {
|
||||
const txParams = txMeta.txParams
|
||||
// ensure value
|
||||
txMeta.gasPriceSpecified = Boolean(txParams.gasPrice)
|
||||
txMeta.nonceSpecified = Boolean(txParams.nonce)
|
||||
let gasPrice = txParams.gasPrice
|
||||
if (!gasPrice) {
|
||||
gasPrice = this.getGasPrice ? this.getGasPrice() : await this.query.gasPrice()
|
||||
}
|
||||
txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16))
|
||||
txParams.value = txParams.value || '0x0'
|
||||
// set gasLimit
|
||||
return await this.txGasUtil.analyzeGasUsage(txMeta)
|
||||
}
|
||||
|
||||
async retryTransaction (txId) {
|
||||
this.txStateManager.setTxStatusUnapproved(txId)
|
||||
const txMeta = this.txStateManager.getTx(txId)
|
||||
txMeta.lastGasPrice = txMeta.txParams.gasPrice
|
||||
this.txStateManager.updateTx(txMeta, 'retryTransaction: manual retry')
|
||||
}
|
||||
|
||||
async updateTransaction (txMeta) {
|
||||
this.txStateManager.updateTx(txMeta, 'confTx: user updated transaction')
|
||||
}
|
||||
|
||||
async updateAndApproveTransaction (txMeta) {
|
||||
this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction')
|
||||
await this.approveTransaction(txMeta.id)
|
||||
}
|
||||
|
||||
async approveTransaction (txId) {
|
||||
let nonceLock
|
||||
try {
|
||||
// approve
|
||||
this.txStateManager.setTxStatusApproved(txId)
|
||||
// get next nonce
|
||||
const txMeta = this.txStateManager.getTx(txId)
|
||||
const fromAddress = txMeta.txParams.from
|
||||
// wait for a nonce
|
||||
nonceLock = await this.nonceTracker.getNonceLock(fromAddress)
|
||||
// add nonce to txParams
|
||||
const nonce = txMeta.nonceSpecified ? txMeta.txParams.nonce : nonceLock.nextNonce
|
||||
if (nonce > nonceLock.nextNonce) {
|
||||
const message = `Specified nonce may not be larger than account's next valid nonce.`
|
||||
throw new Error(message)
|
||||
}
|
||||
txMeta.txParams.nonce = ethUtil.addHexPrefix(nonce.toString(16))
|
||||
// add nonce debugging information to txMeta
|
||||
txMeta.nonceDetails = nonceLock.nonceDetails
|
||||
this.txStateManager.updateTx(txMeta, 'transactions#approveTransaction')
|
||||
// sign transaction
|
||||
const rawTx = await this.signTransaction(txId)
|
||||
await this.publishTransaction(txId, rawTx)
|
||||
// must set transaction to submitted/failed before releasing lock
|
||||
nonceLock.releaseLock()
|
||||
} catch (err) {
|
||||
this.txStateManager.setTxStatusFailed(txId, err)
|
||||
// must set transaction to submitted/failed before releasing lock
|
||||
if (nonceLock) nonceLock.releaseLock()
|
||||
// continue with error chain
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async signTransaction (txId) {
|
||||
const txMeta = this.txStateManager.getTx(txId)
|
||||
const txParams = txMeta.txParams
|
||||
const fromAddress = txParams.from
|
||||
// add network/chain id
|
||||
txParams.chainId = ethUtil.addHexPrefix(this.getChainId().toString(16))
|
||||
const ethTx = new Transaction(txParams)
|
||||
await this.signEthTx(ethTx, fromAddress)
|
||||
this.txStateManager.setTxStatusSigned(txMeta.id)
|
||||
const rawTx = ethUtil.bufferToHex(ethTx.serialize())
|
||||
return rawTx
|
||||
}
|
||||
|
||||
async publishTransaction (txId, rawTx) {
|
||||
const txMeta = this.txStateManager.getTx(txId)
|
||||
txMeta.rawTx = rawTx
|
||||
this.txStateManager.updateTx(txMeta, 'transactions#publishTransaction')
|
||||
const txHash = await this.query.sendRawTransaction(rawTx)
|
||||
this.setTxHash(txId, txHash)
|
||||
this.txStateManager.setTxStatusSubmitted(txId)
|
||||
}
|
||||
|
||||
async cancelTransaction (txId) {
|
||||
this.txStateManager.setTxStatusRejected(txId)
|
||||
}
|
||||
|
||||
// receives a txHash records the tx as signed
|
||||
setTxHash (txId, txHash) {
|
||||
// Add the tx hash to the persisted meta-tx object
|
||||
const txMeta = this.txStateManager.getTx(txId)
|
||||
txMeta.hash = txHash
|
||||
this.txStateManager.updateTx(txMeta, 'transactions#setTxHash')
|
||||
}
|
||||
|
||||
//
|
||||
// PRIVATE METHODS
|
||||
//
|
||||
|
||||
_updateMemstore () {
|
||||
const unapprovedTxs = this.txStateManager.getUnapprovedTxList()
|
||||
const selectedAddressTxList = this.txStateManager.getFilteredTxList({
|
||||
from: this.getSelectedAddress(),
|
||||
metamaskNetworkId: this.getNetwork(),
|
||||
})
|
||||
this.memStore.updateState({ unapprovedTxs, selectedAddressTxList })
|
||||
}
|
||||
}
|
@ -1,11 +1,15 @@
|
||||
// test and development environment variables
|
||||
const env = process.env.METAMASK_ENV
|
||||
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||
|
||||
//
|
||||
// The default state of MetaMask
|
||||
//
|
||||
|
||||
module.exports = {
|
||||
config: {
|
||||
config: {},
|
||||
NetworkController: {
|
||||
provider: {
|
||||
type: 'testnet',
|
||||
type: (METAMASK_DEBUG || env === 'test') ? 'rinkeby' : 'mainnet',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
/*global Web3*/
|
||||
cleanContextForImports()
|
||||
require('web3/dist/web3.min.js')
|
||||
const log = require('loglevel')
|
||||
const LocalMessageDuplexStream = require('post-message-stream')
|
||||
// const PingStream = require('ping-pong-stream/ping')
|
||||
// const endOfStream = require('end-of-stream')
|
||||
@ -8,6 +9,10 @@ const setupDappAutoReload = require('./lib/auto-reload.js')
|
||||
const MetamaskInpageProvider = require('./lib/inpage-provider.js')
|
||||
restoreContextAfterImports()
|
||||
|
||||
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||
window.log = log
|
||||
log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn')
|
||||
|
||||
|
||||
//
|
||||
// setup plugin communication
|
||||
@ -26,31 +31,23 @@ var inpageProvider = new MetamaskInpageProvider(metamaskStream)
|
||||
// setup web3
|
||||
//
|
||||
|
||||
if (typeof window.web3 !== 'undefined') {
|
||||
throw new Error(`MetaMask detected another web3.
|
||||
MetaMask will not work reliably with another web3 extension.
|
||||
This usually happens if you have two MetaMasks installed,
|
||||
or MetaMask and another web3 extension. Please remove one
|
||||
and try again.`)
|
||||
}
|
||||
var web3 = new Web3(inpageProvider)
|
||||
web3.setProvider = function () {
|
||||
console.log('MetaMask - overrode web3.setProvider')
|
||||
log.debug('MetaMask - overrode web3.setProvider')
|
||||
}
|
||||
console.log('MetaMask - injected web3')
|
||||
// export global web3, with usage-detection reload fn
|
||||
var triggerReload = setupDappAutoReload(web3)
|
||||
|
||||
// listen for reset requests from metamask
|
||||
var reloadStream = inpageProvider.multiStream.createStream('reload')
|
||||
reloadStream.once('data', triggerReload)
|
||||
|
||||
// setup ping timeout autoreload
|
||||
// LocalMessageDuplexStream does not self-close, so reload if pingStream fails
|
||||
// var pingChannel = inpageProvider.multiStream.createStream('pingpong')
|
||||
// var pingStream = new PingStream({ objectMode: true })
|
||||
// wait for first successful reponse
|
||||
|
||||
// disable pingStream until https://github.com/MetaMask/metamask-plugin/issues/746 is resolved more gracefully
|
||||
// metamaskStream.once('data', function(){
|
||||
// pingStream.pipe(pingChannel).pipe(pingStream)
|
||||
// })
|
||||
// endOfStream(pingStream, triggerReload)
|
||||
log.debug('MetaMask - injected web3')
|
||||
// export global web3, with usage-detection
|
||||
setupDappAutoReload(web3, inpageProvider.publicConfigStore)
|
||||
|
||||
// set web3 defaultAccount
|
||||
|
||||
inpageProvider.publicConfigStore.subscribe(function (state) {
|
||||
web3.eth.defaultAccount = state.selectedAddress
|
||||
})
|
||||
|
@ -1,594 +0,0 @@
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const BN = ethUtil.BN
|
||||
const bip39 = require('bip39')
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const ObservableStore = require('obs-store')
|
||||
const filter = require('promise-filter')
|
||||
const encryptor = require('browser-passworder')
|
||||
const sigUtil = require('eth-sig-util')
|
||||
const normalizeAddress = sigUtil.normalize
|
||||
// Keyrings:
|
||||
const SimpleKeyring = require('eth-simple-keyring')
|
||||
const HdKeyring = require('eth-hd-keyring')
|
||||
const keyringTypes = [
|
||||
SimpleKeyring,
|
||||
HdKeyring,
|
||||
]
|
||||
|
||||
class KeyringController extends EventEmitter {
|
||||
|
||||
// PUBLIC METHODS
|
||||
//
|
||||
// THE FIRST SECTION OF METHODS ARE PUBLIC-FACING,
|
||||
// MEANING THEY ARE USED BY CONSUMERS OF THIS CLASS.
|
||||
//
|
||||
// THEIR SURFACE AREA SHOULD BE CHANGED WITH GREAT CARE.
|
||||
|
||||
constructor (opts) {
|
||||
super()
|
||||
const initState = opts.initState || {}
|
||||
this.keyringTypes = keyringTypes
|
||||
this.store = new ObservableStore(initState)
|
||||
this.memStore = new ObservableStore({
|
||||
isUnlocked: false,
|
||||
keyringTypes: this.keyringTypes.map(krt => krt.type),
|
||||
keyrings: [],
|
||||
identities: {},
|
||||
})
|
||||
this.ethStore = opts.ethStore
|
||||
this.encryptor = encryptor
|
||||
this.keyrings = []
|
||||
this.getNetwork = opts.getNetwork
|
||||
}
|
||||
|
||||
// Full Update
|
||||
// returns Promise( @object state )
|
||||
//
|
||||
// Emits the `update` event and
|
||||
// returns a Promise that resolves to the current state.
|
||||
//
|
||||
// Frequently used to end asynchronous chains in this class,
|
||||
// indicating consumers can often either listen for updates,
|
||||
// or accept a state-resolving promise to consume their results.
|
||||
//
|
||||
// Not all methods end with this, that might be a nice refactor.
|
||||
fullUpdate () {
|
||||
this.emit('update')
|
||||
return Promise.resolve(this.memStore.getState())
|
||||
}
|
||||
|
||||
// Create New Vault And Keychain
|
||||
// @string password - The password to encrypt the vault with
|
||||
//
|
||||
// returns Promise( @object state )
|
||||
//
|
||||
// Destroys any old encrypted storage,
|
||||
// creates a new encrypted store with the given password,
|
||||
// randomly creates a new HD wallet with 1 account,
|
||||
// faucets that account on the testnet.
|
||||
createNewVaultAndKeychain (password) {
|
||||
return this.persistAllKeyrings(password)
|
||||
.then(this.createFirstKeyTree.bind(this))
|
||||
.then(this.fullUpdate.bind(this))
|
||||
}
|
||||
|
||||
// CreateNewVaultAndRestore
|
||||
// @string password - The password to encrypt the vault with
|
||||
// @string seed - The BIP44-compliant seed phrase.
|
||||
//
|
||||
// returns Promise( @object state )
|
||||
//
|
||||
// Destroys any old encrypted storage,
|
||||
// creates a new encrypted store with the given password,
|
||||
// creates a new HD wallet from the given seed with 1 account.
|
||||
createNewVaultAndRestore (password, seed) {
|
||||
if (typeof password !== 'string') {
|
||||
return Promise.reject('Password must be text.')
|
||||
}
|
||||
|
||||
if (!bip39.validateMnemonic(seed)) {
|
||||
return Promise.reject('Seed phrase is invalid.')
|
||||
}
|
||||
|
||||
this.clearKeyrings()
|
||||
|
||||
return this.persistAllKeyrings(password)
|
||||
.then(() => {
|
||||
return this.addNewKeyring('HD Key Tree', {
|
||||
mnemonic: seed,
|
||||
numberOfAccounts: 1,
|
||||
})
|
||||
})
|
||||
.then((firstKeyring) => {
|
||||
return firstKeyring.getAccounts()
|
||||
})
|
||||
.then((accounts) => {
|
||||
const firstAccount = accounts[0]
|
||||
if (!firstAccount) throw new Error('KeyringController - First Account not found.')
|
||||
const hexAccount = normalizeAddress(firstAccount)
|
||||
this.emit('newAccount', hexAccount)
|
||||
return this.setupAccounts(accounts)
|
||||
})
|
||||
.then(this.persistAllKeyrings.bind(this, password))
|
||||
.then(this.fullUpdate.bind(this))
|
||||
}
|
||||
|
||||
// Set Locked
|
||||
// returns Promise( @object state )
|
||||
//
|
||||
// This method deallocates all secrets, and effectively locks metamask.
|
||||
setLocked () {
|
||||
// set locked
|
||||
this.password = null
|
||||
this.memStore.updateState({ isUnlocked: false })
|
||||
// remove keyrings
|
||||
this.keyrings = []
|
||||
this._updateMemStoreKeyrings()
|
||||
return this.fullUpdate()
|
||||
}
|
||||
|
||||
// Submit Password
|
||||
// @string password
|
||||
//
|
||||
// returns Promise( @object state )
|
||||
//
|
||||
// Attempts to decrypt the current vault and load its keyrings
|
||||
// into memory.
|
||||
//
|
||||
// Temporarily also migrates any old-style vaults first, as well.
|
||||
// (Pre MetaMask 3.0.0)
|
||||
submitPassword (password) {
|
||||
return this.unlockKeyrings(password)
|
||||
.then((keyrings) => {
|
||||
this.keyrings = keyrings
|
||||
return this.fullUpdate()
|
||||
})
|
||||
}
|
||||
|
||||
// Add New Keyring
|
||||
// @string type
|
||||
// @object opts
|
||||
//
|
||||
// returns Promise( @Keyring keyring )
|
||||
//
|
||||
// Adds a new Keyring of the given `type` to the vault
|
||||
// and the current decrypted Keyrings array.
|
||||
//
|
||||
// All Keyring classes implement a unique `type` string,
|
||||
// and this is used to retrieve them from the keyringTypes array.
|
||||
addNewKeyring (type, opts) {
|
||||
const Keyring = this.getKeyringClassForType(type)
|
||||
const keyring = new Keyring(opts)
|
||||
return keyring.deserialize(opts)
|
||||
.then(() => {
|
||||
return keyring.getAccounts()
|
||||
})
|
||||
.then((accounts) => {
|
||||
return this.checkForDuplicate(type, accounts)
|
||||
})
|
||||
.then((checkedAccounts) => {
|
||||
this.keyrings.push(keyring)
|
||||
return this.setupAccounts(checkedAccounts)
|
||||
})
|
||||
.then(() => this.persistAllKeyrings())
|
||||
.then(() => this.fullUpdate())
|
||||
.then(() => {
|
||||
this._updateMemStoreKeyrings()
|
||||
return keyring
|
||||
})
|
||||
}
|
||||
|
||||
// For now just checks for simple key pairs
|
||||
// but in the future
|
||||
// should possibly add HD and other types
|
||||
//
|
||||
checkForDuplicate (type, newAccount) {
|
||||
return this.getAccounts()
|
||||
.then((accounts) => {
|
||||
switch (type) {
|
||||
case 'Simple Key Pair':
|
||||
let isNotIncluded = !accounts.find((key) => key === newAccount[0] || key === ethUtil.stripHexPrefix(newAccount[0]))
|
||||
return (isNotIncluded) ? Promise.resolve(newAccount) : Promise.reject(new Error('The account you\'re are trying to import is a duplicate'))
|
||||
default:
|
||||
return Promise.resolve(newAccount)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Add New Account
|
||||
// @number keyRingNum
|
||||
//
|
||||
// returns Promise( @object state )
|
||||
//
|
||||
// Calls the `addAccounts` method on the Keyring
|
||||
// in the kryings array at index `keyringNum`,
|
||||
// and then saves those changes.
|
||||
addNewAccount (selectedKeyring) {
|
||||
return selectedKeyring.addAccounts(1)
|
||||
.then(this.setupAccounts.bind(this))
|
||||
.then(this.persistAllKeyrings.bind(this))
|
||||
.then(this.fullUpdate.bind(this))
|
||||
}
|
||||
|
||||
// Save Account Label
|
||||
// @string account
|
||||
// @string label
|
||||
//
|
||||
// returns Promise( @string label )
|
||||
//
|
||||
// Persists a nickname equal to `label` for the specified account.
|
||||
saveAccountLabel (account, label) {
|
||||
try {
|
||||
const hexAddress = normalizeAddress(account)
|
||||
// update state on diskStore
|
||||
const state = this.store.getState()
|
||||
const walletNicknames = state.walletNicknames || {}
|
||||
walletNicknames[hexAddress] = label
|
||||
this.store.updateState({ walletNicknames })
|
||||
// update state on memStore
|
||||
const identities = this.memStore.getState().identities
|
||||
identities[hexAddress].name = label
|
||||
this.memStore.updateState({ identities })
|
||||
return Promise.resolve(label)
|
||||
} catch (err) {
|
||||
return Promise.reject(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Export Account
|
||||
// @string address
|
||||
//
|
||||
// returns Promise( @string privateKey )
|
||||
//
|
||||
// Requests the private key from the keyring controlling
|
||||
// the specified address.
|
||||
//
|
||||
// Returns a Promise that may resolve with the private key string.
|
||||
exportAccount (address) {
|
||||
try {
|
||||
return this.getKeyringForAccount(address)
|
||||
.then((keyring) => {
|
||||
return keyring.exportAccount(normalizeAddress(address))
|
||||
})
|
||||
} catch (e) {
|
||||
return Promise.reject(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// SIGNING METHODS
|
||||
//
|
||||
// This method signs tx and returns a promise for
|
||||
// TX Manager to update the state after signing
|
||||
|
||||
signTransaction (ethTx, _fromAddress) {
|
||||
const fromAddress = normalizeAddress(_fromAddress)
|
||||
return this.getKeyringForAccount(fromAddress)
|
||||
.then((keyring) => {
|
||||
return keyring.signTransaction(fromAddress, ethTx)
|
||||
})
|
||||
}
|
||||
|
||||
// Sign Message
|
||||
// @object msgParams
|
||||
//
|
||||
// returns Promise(@buffer rawSig)
|
||||
//
|
||||
// Attempts to sign the provided @object msgParams.
|
||||
signMessage (msgParams) {
|
||||
const address = normalizeAddress(msgParams.from)
|
||||
return this.getKeyringForAccount(address)
|
||||
.then((keyring) => {
|
||||
return keyring.signMessage(address, msgParams.data)
|
||||
})
|
||||
}
|
||||
|
||||
// Sign Personal Message
|
||||
// @object msgParams
|
||||
//
|
||||
// returns Promise(@buffer rawSig)
|
||||
//
|
||||
// Attempts to sign the provided @object msgParams.
|
||||
// Prefixes the hash before signing as per the new geth behavior.
|
||||
signPersonalMessage (msgParams) {
|
||||
const address = normalizeAddress(msgParams.from)
|
||||
return this.getKeyringForAccount(address)
|
||||
.then((keyring) => {
|
||||
return keyring.signPersonalMessage(address, msgParams.data)
|
||||
})
|
||||
}
|
||||
|
||||
// PRIVATE METHODS
|
||||
//
|
||||
// THESE METHODS ARE ONLY USED INTERNALLY TO THE KEYRING-CONTROLLER
|
||||
// AND SO MAY BE CHANGED MORE LIBERALLY THAN THE ABOVE METHODS.
|
||||
|
||||
// Create First Key Tree
|
||||
// returns @Promise
|
||||
//
|
||||
// Clears the vault,
|
||||
// creates a new one,
|
||||
// creates a random new HD Keyring with 1 account,
|
||||
// makes that account the selected account,
|
||||
// faucets that account on testnet,
|
||||
// puts the current seed words into the state tree.
|
||||
createFirstKeyTree () {
|
||||
this.clearKeyrings()
|
||||
return this.addNewKeyring('HD Key Tree', { numberOfAccounts: 1 })
|
||||
.then((keyring) => {
|
||||
return keyring.getAccounts()
|
||||
})
|
||||
.then((accounts) => {
|
||||
const firstAccount = accounts[0]
|
||||
if (!firstAccount) throw new Error('KeyringController - No account found on keychain.')
|
||||
const hexAccount = normalizeAddress(firstAccount)
|
||||
this.emit('newAccount', hexAccount)
|
||||
this.emit('newVault', hexAccount)
|
||||
return this.setupAccounts(accounts)
|
||||
})
|
||||
.then(this.persistAllKeyrings.bind(this))
|
||||
}
|
||||
|
||||
// Setup Accounts
|
||||
// @array accounts
|
||||
//
|
||||
// returns @Promise(@object account)
|
||||
//
|
||||
// Initializes the provided account array
|
||||
// Gives them numerically incremented nicknames,
|
||||
// and adds them to the ethStore for regular balance checking.
|
||||
setupAccounts (accounts) {
|
||||
return this.getAccounts()
|
||||
.then((loadedAccounts) => {
|
||||
const arr = accounts || loadedAccounts
|
||||
return Promise.all(arr.map((account) => {
|
||||
return this.getBalanceAndNickname(account)
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
// Get Balance And Nickname
|
||||
// @string account
|
||||
//
|
||||
// returns Promise( @string label )
|
||||
//
|
||||
// Takes an account address and an iterator representing
|
||||
// the current number of named accounts.
|
||||
getBalanceAndNickname (account) {
|
||||
if (!account) {
|
||||
throw new Error('Problem loading account.')
|
||||
}
|
||||
const address = normalizeAddress(account)
|
||||
this.ethStore.addAccount(address)
|
||||
return this.createNickname(address)
|
||||
}
|
||||
|
||||
// Create Nickname
|
||||
// @string address
|
||||
//
|
||||
// returns Promise( @string label )
|
||||
//
|
||||
// Takes an address, and assigns it an incremented nickname, persisting it.
|
||||
createNickname (address) {
|
||||
const hexAddress = normalizeAddress(address)
|
||||
const identities = this.memStore.getState().identities
|
||||
const currentIdentityCount = Object.keys(identities).length + 1
|
||||
const nicknames = this.store.getState().walletNicknames || {}
|
||||
const existingNickname = nicknames[hexAddress]
|
||||
const name = existingNickname || `Account ${currentIdentityCount}`
|
||||
identities[hexAddress] = {
|
||||
address: hexAddress,
|
||||
name,
|
||||
}
|
||||
this.memStore.updateState({ identities })
|
||||
return this.saveAccountLabel(hexAddress, name)
|
||||
}
|
||||
|
||||
// Persist All Keyrings
|
||||
// @password string
|
||||
//
|
||||
// returns Promise
|
||||
//
|
||||
// Iterates the current `keyrings` array,
|
||||
// serializes each one into a serialized array,
|
||||
// encrypts that array with the provided `password`,
|
||||
// and persists that encrypted string to storage.
|
||||
persistAllKeyrings (password = this.password) {
|
||||
if (typeof password === 'string') {
|
||||
this.password = password
|
||||
this.memStore.updateState({ isUnlocked: true })
|
||||
}
|
||||
return Promise.all(this.keyrings.map((keyring) => {
|
||||
return Promise.all([keyring.type, keyring.serialize()])
|
||||
.then((serializedKeyringArray) => {
|
||||
// Label the output values on each serialized Keyring:
|
||||
return {
|
||||
type: serializedKeyringArray[0],
|
||||
data: serializedKeyringArray[1],
|
||||
}
|
||||
})
|
||||
}))
|
||||
.then((serializedKeyrings) => {
|
||||
return this.encryptor.encrypt(this.password, serializedKeyrings)
|
||||
})
|
||||
.then((encryptedString) => {
|
||||
this.store.updateState({ vault: encryptedString })
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Unlock Keyrings
|
||||
// @string password
|
||||
//
|
||||
// returns Promise( @array keyrings )
|
||||
//
|
||||
// Attempts to unlock the persisted encrypted storage,
|
||||
// initializing the persisted keyrings to RAM.
|
||||
unlockKeyrings (password) {
|
||||
const encryptedVault = this.store.getState().vault
|
||||
if (!encryptedVault) {
|
||||
throw new Error('Cannot unlock without a previous vault.')
|
||||
}
|
||||
|
||||
return this.encryptor.decrypt(password, encryptedVault)
|
||||
.then((vault) => {
|
||||
this.password = password
|
||||
this.memStore.updateState({ isUnlocked: true })
|
||||
vault.forEach(this.restoreKeyring.bind(this))
|
||||
return this.keyrings
|
||||
})
|
||||
}
|
||||
|
||||
// Restore Keyring
|
||||
// @object serialized
|
||||
//
|
||||
// returns Promise( @Keyring deserialized )
|
||||
//
|
||||
// Attempts to initialize a new keyring from the provided
|
||||
// serialized payload.
|
||||
//
|
||||
// On success, returns the resulting @Keyring instance.
|
||||
restoreKeyring (serialized) {
|
||||
const { type, data } = serialized
|
||||
|
||||
const Keyring = this.getKeyringClassForType(type)
|
||||
const keyring = new Keyring()
|
||||
return keyring.deserialize(data)
|
||||
.then(() => {
|
||||
return keyring.getAccounts()
|
||||
})
|
||||
.then((accounts) => {
|
||||
return this.setupAccounts(accounts)
|
||||
})
|
||||
.then(() => {
|
||||
this.keyrings.push(keyring)
|
||||
this._updateMemStoreKeyrings()
|
||||
return keyring
|
||||
})
|
||||
}
|
||||
|
||||
// Get Keyring Class For Type
|
||||
// @string type
|
||||
//
|
||||
// Returns @class Keyring
|
||||
//
|
||||
// Searches the current `keyringTypes` array
|
||||
// for a Keyring class whose unique `type` property
|
||||
// matches the provided `type`,
|
||||
// returning it if it exists.
|
||||
getKeyringClassForType (type) {
|
||||
return this.keyringTypes.find(kr => kr.type === type)
|
||||
}
|
||||
|
||||
getKeyringsByType (type) {
|
||||
return this.keyrings.filter((keyring) => keyring.type === type)
|
||||
}
|
||||
|
||||
// Get Accounts
|
||||
// returns Promise( @Array[ @string accounts ] )
|
||||
//
|
||||
// Returns the public addresses of all current accounts
|
||||
// managed by all currently unlocked keyrings.
|
||||
getAccounts () {
|
||||
const keyrings = this.keyrings || []
|
||||
return Promise.all(keyrings.map(kr => kr.getAccounts()))
|
||||
.then((keyringArrays) => {
|
||||
return keyringArrays.reduce((res, arr) => {
|
||||
return res.concat(arr)
|
||||
}, [])
|
||||
})
|
||||
}
|
||||
|
||||
// Get Keyring For Account
|
||||
// @string address
|
||||
//
|
||||
// returns Promise(@Keyring keyring)
|
||||
//
|
||||
// Returns the currently initialized keyring that manages
|
||||
// the specified `address` if one exists.
|
||||
getKeyringForAccount (address) {
|
||||
const hexed = normalizeAddress(address)
|
||||
log.debug(`KeyringController - getKeyringForAccount: ${hexed}`)
|
||||
|
||||
return Promise.all(this.keyrings.map((keyring) => {
|
||||
return Promise.all([
|
||||
keyring,
|
||||
keyring.getAccounts(),
|
||||
])
|
||||
}))
|
||||
.then(filter((candidate) => {
|
||||
const accounts = candidate[1].map(normalizeAddress)
|
||||
return accounts.includes(hexed)
|
||||
}))
|
||||
.then((winners) => {
|
||||
if (winners && winners.length > 0) {
|
||||
return winners[0][0]
|
||||
} else {
|
||||
throw new Error('No keyring found for the requested account.')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Display For Keyring
|
||||
// @Keyring keyring
|
||||
//
|
||||
// returns Promise( @Object { type:String, accounts:Array } )
|
||||
//
|
||||
// Is used for adding the current keyrings to the state object.
|
||||
displayForKeyring (keyring) {
|
||||
return keyring.getAccounts()
|
||||
.then((accounts) => {
|
||||
return {
|
||||
type: keyring.type,
|
||||
accounts: accounts,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Add Gas Buffer
|
||||
// @string gas (as hexadecimal value)
|
||||
//
|
||||
// returns @string bufferedGas (as hexadecimal value)
|
||||
//
|
||||
// Adds a healthy buffer of gas to an initial gas estimate.
|
||||
addGasBuffer (gas) {
|
||||
const gasBuffer = new BN('100000', 10)
|
||||
const bnGas = new BN(ethUtil.stripHexPrefix(gas), 16)
|
||||
const correct = bnGas.add(gasBuffer)
|
||||
return ethUtil.addHexPrefix(correct.toString(16))
|
||||
}
|
||||
|
||||
// Clear Keyrings
|
||||
//
|
||||
// Deallocates all currently managed keyrings and accounts.
|
||||
// Used before initializing a new vault.
|
||||
clearKeyrings () {
|
||||
let accounts
|
||||
try {
|
||||
accounts = Object.keys(this.ethStore.getState())
|
||||
} catch (e) {
|
||||
accounts = []
|
||||
}
|
||||
accounts.forEach((address) => {
|
||||
this.ethStore.removeAccount(address)
|
||||
})
|
||||
|
||||
// clear keyrings from memory
|
||||
this.keyrings = []
|
||||
this.memStore.updateState({
|
||||
keyrings: [],
|
||||
identities: {},
|
||||
})
|
||||
}
|
||||
|
||||
_updateMemStoreKeyrings() {
|
||||
Promise.all(this.keyrings.map(this.displayForKeyring))
|
||||
.then((keyrings) => {
|
||||
this.memStore.updateState({ keyrings })
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = KeyringController
|
125
app/scripts/lib/account-tracker.js
Normal file
@ -0,0 +1,125 @@
|
||||
/* Account Tracker
|
||||
*
|
||||
* This module is responsible for tracking any number of accounts
|
||||
* and caching their current balances & transaction counts.
|
||||
*
|
||||
* It also tracks transaction hashes, and checks their inclusion status
|
||||
* on each new block.
|
||||
*/
|
||||
|
||||
const async = require('async')
|
||||
const EthQuery = require('eth-query')
|
||||
const ObservableStore = require('obs-store')
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
function noop () {}
|
||||
|
||||
|
||||
class AccountTracker extends EventEmitter {
|
||||
|
||||
constructor (opts = {}) {
|
||||
super()
|
||||
|
||||
const initState = {
|
||||
accounts: {},
|
||||
currentBlockGasLimit: '',
|
||||
}
|
||||
this.store = new ObservableStore(initState)
|
||||
|
||||
this._provider = opts.provider
|
||||
this._query = new EthQuery(this._provider)
|
||||
this._blockTracker = opts.blockTracker
|
||||
// subscribe to latest block
|
||||
this._blockTracker.on('block', this._updateForBlock.bind(this))
|
||||
// blockTracker.currentBlock may be null
|
||||
this._currentBlockNumber = this._blockTracker.currentBlock
|
||||
}
|
||||
|
||||
//
|
||||
// public
|
||||
//
|
||||
|
||||
syncWithAddresses (addresses) {
|
||||
const accounts = this.store.getState().accounts
|
||||
const locals = Object.keys(accounts)
|
||||
|
||||
const toAdd = []
|
||||
addresses.forEach((upstream) => {
|
||||
if (!locals.includes(upstream)) {
|
||||
toAdd.push(upstream)
|
||||
}
|
||||
})
|
||||
|
||||
const toRemove = []
|
||||
locals.forEach((local) => {
|
||||
if (!addresses.includes(local)) {
|
||||
toRemove.push(local)
|
||||
}
|
||||
})
|
||||
|
||||
toAdd.forEach(upstream => this.addAccount(upstream))
|
||||
toRemove.forEach(local => this.removeAccount(local))
|
||||
this._updateAccounts()
|
||||
}
|
||||
|
||||
addAccount (address) {
|
||||
const accounts = this.store.getState().accounts
|
||||
accounts[address] = {}
|
||||
this.store.updateState({ accounts })
|
||||
if (!this._currentBlockNumber) return
|
||||
this._updateAccount(address)
|
||||
}
|
||||
|
||||
removeAccount (address) {
|
||||
const accounts = this.store.getState().accounts
|
||||
delete accounts[address]
|
||||
this.store.updateState({ accounts })
|
||||
}
|
||||
|
||||
//
|
||||
// private
|
||||
//
|
||||
|
||||
_updateForBlock (block) {
|
||||
this._currentBlockNumber = block.number
|
||||
const currentBlockGasLimit = block.gasLimit
|
||||
|
||||
this.store.updateState({ currentBlockGasLimit })
|
||||
|
||||
async.parallel([
|
||||
this._updateAccounts.bind(this),
|
||||
], (err) => {
|
||||
if (err) return console.error(err)
|
||||
this.emit('block', this.store.getState())
|
||||
})
|
||||
}
|
||||
|
||||
_updateAccounts (cb = noop) {
|
||||
const accounts = this.store.getState().accounts
|
||||
const addresses = Object.keys(accounts)
|
||||
async.each(addresses, this._updateAccount.bind(this), cb)
|
||||
}
|
||||
|
||||
_updateAccount (address, cb = noop) {
|
||||
this._getAccount(address, (err, result) => {
|
||||
if (err) return cb(err)
|
||||
result.address = address
|
||||
const accounts = this.store.getState().accounts
|
||||
// only populate if the entry is still present
|
||||
if (accounts[address]) {
|
||||
accounts[address] = result
|
||||
this.store.updateState({ accounts })
|
||||
}
|
||||
cb(null, result)
|
||||
})
|
||||
}
|
||||
|
||||
_getAccount (address, cb = noop) {
|
||||
const query = this._query
|
||||
async.parallel({
|
||||
balance: query.getBalance.bind(query, address),
|
||||
}, cb)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = AccountTracker
|
@ -1,20 +0,0 @@
|
||||
const uri = 'https://faucet.metamask.io/'
|
||||
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||
const env = process.env.METAMASK_ENV
|
||||
|
||||
module.exports = function (address) {
|
||||
// Don't faucet in development or test
|
||||
if (METAMASK_DEBUG === true || env === 'test') return
|
||||
global.log.info('auto-fauceting:', address)
|
||||
const data = address
|
||||
const headers = new Headers()
|
||||
headers.append('Content-type', 'application/rawdata')
|
||||
fetch(uri, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: data,
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
@ -1,30 +1,58 @@
|
||||
const once = require('once')
|
||||
const ensnare = require('ensnare')
|
||||
|
||||
module.exports = setupDappAutoReload
|
||||
|
||||
function setupDappAutoReload (web3) {
|
||||
function setupDappAutoReload (web3, observable) {
|
||||
// export web3 as a global, checking for usage
|
||||
var pageIsUsingWeb3 = false
|
||||
var resetWasRequested = false
|
||||
global.web3 = ensnare(web3, once(function () {
|
||||
// if web3 usage happened after a reset request, trigger reset late
|
||||
if (resetWasRequested) return triggerReset()
|
||||
// mark web3 as used
|
||||
pageIsUsingWeb3 = true
|
||||
// reset web3 reference
|
||||
global.web3 = web3
|
||||
}))
|
||||
let hasBeenWarned = false
|
||||
let reloadInProgress = false
|
||||
let lastTimeUsed
|
||||
let lastSeenNetwork
|
||||
|
||||
return handleResetRequest
|
||||
global.web3 = new Proxy(web3, {
|
||||
get: (_web3, key) => {
|
||||
// show warning once on web3 access
|
||||
if (!hasBeenWarned && key !== 'currentProvider') {
|
||||
console.warn('MetaMask: web3 will be deprecated in the near future in favor of the ethereumProvider \nhttps://github.com/MetaMask/faq/blob/master/detecting_metamask.md#web3-deprecation')
|
||||
hasBeenWarned = true
|
||||
}
|
||||
// get the time of use
|
||||
lastTimeUsed = Date.now()
|
||||
// return value normally
|
||||
return _web3[key]
|
||||
},
|
||||
set: (_web3, key, value) => {
|
||||
// set value normally
|
||||
_web3[key] = value
|
||||
},
|
||||
})
|
||||
|
||||
function handleResetRequest () {
|
||||
resetWasRequested = true
|
||||
// ignore if web3 was not used
|
||||
if (!pageIsUsingWeb3) return
|
||||
// reload after short timeout
|
||||
setTimeout(triggerReset, 500)
|
||||
}
|
||||
observable.subscribe(function (state) {
|
||||
// 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
|
||||
|
@ -1,6 +1,6 @@
|
||||
module.exports = getBuyEthUrl
|
||||
|
||||
function getBuyEthUrl({ network, amount, address }){
|
||||
function getBuyEthUrl ({ network, amount, address }) {
|
||||
let url
|
||||
switch (network) {
|
||||
case '1':
|
||||
@ -11,9 +11,13 @@ function getBuyEthUrl({ network, amount, address }){
|
||||
url = 'https://faucet.metamask.io/'
|
||||
break
|
||||
|
||||
case '4':
|
||||
url = 'https://www.rinkeby.io/'
|
||||
break
|
||||
|
||||
case '42':
|
||||
url = 'https://github.com/kovan-testnet/faucet'
|
||||
break
|
||||
}
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
const MetamaskConfig = require('../config.js')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const normalize = require('eth-sig-util').normalize
|
||||
const MetamaskConfig = require('../config.js')
|
||||
|
||||
|
||||
const TESTNET_RPC = MetamaskConfig.network.testnet
|
||||
const MAINNET_RPC = MetamaskConfig.network.mainnet
|
||||
const MORDEN_RPC = MetamaskConfig.network.morden
|
||||
const ROPSTEN_RPC = MetamaskConfig.network.ropsten
|
||||
const KOVAN_RPC = MetamaskConfig.network.kovan
|
||||
const RINKEBY_RPC = MetamaskConfig.network.rinkeby
|
||||
|
||||
/* The config-manager is a convenience object
|
||||
* wrapping a pojo-migrator.
|
||||
@ -33,36 +34,6 @@ ConfigManager.prototype.getConfig = function () {
|
||||
return data.config
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setRpcTarget = function (rpcUrl) {
|
||||
var config = this.getConfig()
|
||||
config.provider = {
|
||||
type: 'rpc',
|
||||
rpcTarget: rpcUrl,
|
||||
}
|
||||
this.setConfig(config)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setProviderType = function (type) {
|
||||
var config = this.getConfig()
|
||||
config.provider = {
|
||||
type: type,
|
||||
}
|
||||
this.setConfig(config)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.useEtherscanProvider = function () {
|
||||
var config = this.getConfig()
|
||||
config.provider = {
|
||||
type: 'etherscan',
|
||||
}
|
||||
this.setConfig(config)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getProvider = function () {
|
||||
var config = this.getConfig()
|
||||
return config.provider
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setData = function (data) {
|
||||
this.store.putState(data)
|
||||
}
|
||||
@ -71,6 +42,17 @@ ConfigManager.prototype.getData = function () {
|
||||
return this.store.getState()
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setPasswordForgotten = function (passwordForgottenState) {
|
||||
const data = this.getData()
|
||||
data.forgottenPassword = passwordForgottenState
|
||||
this.setData(data)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getPasswordForgotten = function (passwordForgottenState) {
|
||||
const data = this.getData()
|
||||
return data.forgottenPassword
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setWallet = function (wallet) {
|
||||
var data = this.getData()
|
||||
data.wallet = wallet
|
||||
@ -136,6 +118,35 @@ ConfigManager.prototype.getSeedWords = function () {
|
||||
var data = this.getData()
|
||||
return data.seedWords
|
||||
}
|
||||
ConfigManager.prototype.setRpcTarget = function (rpcUrl) {
|
||||
var config = this.getConfig()
|
||||
config.provider = {
|
||||
type: 'rpc',
|
||||
rpcTarget: rpcUrl,
|
||||
}
|
||||
this.setConfig(config)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.setProviderType = function (type) {
|
||||
var config = this.getConfig()
|
||||
config.provider = {
|
||||
type: type,
|
||||
}
|
||||
this.setConfig(config)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.useEtherscanProvider = function () {
|
||||
var config = this.getConfig()
|
||||
config.provider = {
|
||||
type: 'etherscan',
|
||||
}
|
||||
this.setConfig(config)
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getProvider = function () {
|
||||
var config = this.getConfig()
|
||||
return config.provider
|
||||
}
|
||||
|
||||
ConfigManager.prototype.getCurrentRpcAddress = function () {
|
||||
var provider = this.getProvider()
|
||||
@ -145,17 +156,17 @@ ConfigManager.prototype.getCurrentRpcAddress = function () {
|
||||
case 'mainnet':
|
||||
return MAINNET_RPC
|
||||
|
||||
case 'testnet':
|
||||
return TESTNET_RPC
|
||||
|
||||
case 'morden':
|
||||
return MORDEN_RPC
|
||||
case 'ropsten':
|
||||
return ROPSTEN_RPC
|
||||
|
||||
case 'kovan':
|
||||
return KOVAN_RPC
|
||||
|
||||
case 'rinkeby':
|
||||
return RINKEBY_RPC
|
||||
|
||||
default:
|
||||
return provider && provider.rpcTarget ? provider.rpcTarget : TESTNET_RPC
|
||||
return provider && provider.rpcTarget ? provider.rpcTarget : RINKEBY_RPC
|
||||
}
|
||||
}
|
||||
|
||||
|
15
app/scripts/lib/createLoggerMiddleware.js
Normal file
@ -0,0 +1,15 @@
|
||||
// log rpc activity
|
||||
module.exports = createLoggerMiddleware
|
||||
|
||||
function createLoggerMiddleware ({ origin }) {
|
||||
return function loggerMiddleware (req, res, next, end) {
|
||||
next((cb) => {
|
||||
if (res.error) {
|
||||
log.error('Error in RPC response:\n', res)
|
||||
}
|
||||
if (req.isMetamaskInternal) return
|
||||
log.info(`RPC (${origin}):`, req, '->', res)
|
||||
cb()
|
||||
})
|
||||
}
|
||||
}
|
9
app/scripts/lib/createOriginMiddleware.js
Normal file
@ -0,0 +1,9 @@
|
||||
// append dapp origin domain to request
|
||||
module.exports = createOriginMiddleware
|
||||
|
||||
function createOriginMiddleware ({ origin }) {
|
||||
return function originMiddleware (req, res, next, end) {
|
||||
req.origin = origin
|
||||
next()
|
||||
}
|
||||
}
|
12
app/scripts/lib/createProviderMiddleware.js
Normal file
@ -0,0 +1,12 @@
|
||||
module.exports = createProviderMiddleware
|
||||
|
||||
// forward requests to provider
|
||||
function createProviderMiddleware ({ provider }) {
|
||||
return (req, res, next, end) => {
|
||||
provider.sendAsync(req, (err, _res) => {
|
||||
if (err) return end(err)
|
||||
res.result = _res.result
|
||||
end()
|
||||
})
|
||||
}
|
||||
}
|
10
app/scripts/lib/environment-type.js
Normal file
@ -0,0 +1,10 @@
|
||||
module.exports = function environmentType () {
|
||||
const url = window.location.href
|
||||
if (url.match(/popup.html$/)) {
|
||||
return 'popup'
|
||||
} else if (url.match(/home.html$/)) {
|
||||
return 'responsive'
|
||||
} else {
|
||||
return 'notification'
|
||||
}
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
/* Ethereum Store
|
||||
*
|
||||
* This module is responsible for tracking any number of accounts
|
||||
* and caching their current balances & transaction counts.
|
||||
*
|
||||
* It also tracks transaction hashes, and checks their inclusion status
|
||||
* on each new block.
|
||||
*/
|
||||
|
||||
const async = require('async')
|
||||
const EthQuery = require('eth-query')
|
||||
const ObservableStore = require('obs-store')
|
||||
function noop() {}
|
||||
|
||||
|
||||
class EthereumStore extends ObservableStore {
|
||||
|
||||
constructor (opts = {}) {
|
||||
super({
|
||||
accounts: {},
|
||||
transactions: {},
|
||||
currentBlockNumber: '0',
|
||||
currentBlockHash: '',
|
||||
})
|
||||
this._provider = opts.provider
|
||||
this._query = new EthQuery(this._provider)
|
||||
this._blockTracker = opts.blockTracker
|
||||
// subscribe to latest block
|
||||
this._blockTracker.on('block', this._updateForBlock.bind(this))
|
||||
// blockTracker.currentBlock may be null
|
||||
this._currentBlockNumber = this._blockTracker.currentBlock
|
||||
}
|
||||
|
||||
//
|
||||
// public
|
||||
//
|
||||
|
||||
addAccount (address) {
|
||||
const accounts = this.getState().accounts
|
||||
accounts[address] = {}
|
||||
this.updateState({ accounts })
|
||||
if (!this._currentBlockNumber) return
|
||||
this._updateAccount(address)
|
||||
}
|
||||
|
||||
removeAccount (address) {
|
||||
const accounts = this.getState().accounts
|
||||
delete accounts[address]
|
||||
this.updateState({ accounts })
|
||||
}
|
||||
|
||||
addTransaction (txHash) {
|
||||
const transactions = this.getState().transactions
|
||||
transactions[txHash] = {}
|
||||
this.updateState({ transactions })
|
||||
if (!this._currentBlockNumber) return
|
||||
this._updateTransaction(this._currentBlockNumber, txHash, noop)
|
||||
}
|
||||
|
||||
removeTransaction (txHash) {
|
||||
const transactions = this.getState().transactions
|
||||
delete transactions[txHash]
|
||||
this.updateState({ transactions })
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// private
|
||||
//
|
||||
|
||||
_updateForBlock (block) {
|
||||
const blockNumber = '0x' + block.number.toString('hex')
|
||||
this._currentBlockNumber = blockNumber
|
||||
this.updateState({ currentBlockNumber: parseInt(blockNumber) })
|
||||
this.updateState({ currentBlockHash: `0x${block.hash.toString('hex')}`})
|
||||
async.parallel([
|
||||
this._updateAccounts.bind(this),
|
||||
this._updateTransactions.bind(this, blockNumber),
|
||||
], (err) => {
|
||||
if (err) return console.error(err)
|
||||
this.emit('block', this.getState())
|
||||
})
|
||||
}
|
||||
|
||||
_updateAccounts (cb = noop) {
|
||||
const accounts = this.getState().accounts
|
||||
const addresses = Object.keys(accounts)
|
||||
async.each(addresses, this._updateAccount.bind(this), cb)
|
||||
}
|
||||
|
||||
_updateAccount (address, cb = noop) {
|
||||
const accounts = this.getState().accounts
|
||||
this._getAccount(address, (err, result) => {
|
||||
if (err) return cb(err)
|
||||
result.address = address
|
||||
// only populate if the entry is still present
|
||||
if (accounts[address]) {
|
||||
accounts[address] = result
|
||||
this.updateState({ accounts })
|
||||
}
|
||||
cb(null, result)
|
||||
})
|
||||
}
|
||||
|
||||
_updateTransactions (block, cb = noop) {
|
||||
const transactions = this.getState().transactions
|
||||
const txHashes = Object.keys(transactions)
|
||||
async.each(txHashes, this._updateTransaction.bind(this, block), cb)
|
||||
}
|
||||
|
||||
_updateTransaction (block, txHash, cb = noop) {
|
||||
// would use the block here to determine how many confirmations the tx has
|
||||
const transactions = this.getState().transactions
|
||||
this._query.getTransaction(txHash, (err, result) => {
|
||||
if (err) return cb(err)
|
||||
// only populate if the entry is still present
|
||||
if (transactions[txHash]) {
|
||||
transactions[txHash] = result
|
||||
this.updateState({ transactions })
|
||||
}
|
||||
cb(null, result)
|
||||
})
|
||||
}
|
||||
|
||||
_getAccount (address, cb = noop) {
|
||||
const query = this._query
|
||||
async.parallel({
|
||||
balance: query.getBalance.bind(query, address),
|
||||
nonce: query.getTransactionCount.bind(query, address),
|
||||
code: query.getCode.bind(query, address),
|
||||
}, cb)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = EthereumStore
|