├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── babel.config.js ├── lerna-debug.log ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── bitcoin │ ├── README.md │ ├── __tests__ │ │ └── bitcoin.test.js │ ├── lib │ │ └── bitcoin.js │ ├── package-lock.json │ ├── package.json │ ├── prepare.js │ ├── yarn-error.log │ └── yarn.lock ├── core │ ├── README.md │ ├── __tests__ │ │ └── core.test.js │ ├── lib │ │ ├── core.js │ │ ├── migrations │ │ │ ├── index.js │ │ │ └── migrator.js │ │ ├── models │ │ │ ├── Account.js │ │ │ ├── AccountAction.js │ │ │ ├── AuthorizedApp.js │ │ │ ├── Blockchains.js │ │ │ ├── Contact.js │ │ │ ├── CreditCard.js │ │ │ ├── Explorer.js │ │ │ ├── Identity.js │ │ │ ├── Keychain.js │ │ │ ├── Keypair.js │ │ │ ├── Locale.js │ │ │ ├── Meta.js │ │ │ ├── Network.js │ │ │ ├── Permission.js │ │ │ ├── Scatter.js │ │ │ ├── Settings.js │ │ │ ├── Token.js │ │ │ ├── api │ │ │ │ ├── ApiActions.js │ │ │ │ └── index.js │ │ │ ├── errors │ │ │ │ ├── Error.js │ │ │ │ ├── ErrorTypes.js │ │ │ │ └── index.js │ │ │ ├── hardware │ │ │ │ ├── ExternalWallet.js │ │ │ │ └── index.js │ │ │ ├── histories │ │ │ │ ├── HistoricAction.js │ │ │ │ ├── HistoricExchange.js │ │ │ │ ├── HistoricTransfer.js │ │ │ │ ├── History.js │ │ │ │ └── index.js │ │ │ └── index.js │ │ ├── plugins │ │ │ ├── Plugin.js │ │ │ ├── PluginRepository.js │ │ │ ├── PluginTypes.js │ │ │ ├── defaults │ │ │ │ ├── index.js │ │ │ │ └── interface.js │ │ │ └── index.js │ │ ├── services │ │ │ ├── apis │ │ │ │ ├── ApiService.js │ │ │ │ ├── BackendApiService.js │ │ │ │ ├── ExchangeService.js │ │ │ │ ├── PriceService.js │ │ │ │ └── index.js │ │ │ ├── apps │ │ │ │ ├── AppsService.js │ │ │ │ ├── PermissionService.js │ │ │ │ └── index.js │ │ │ ├── blockchain │ │ │ │ ├── AccountService.js │ │ │ │ ├── BalanceService.js │ │ │ │ ├── ExplorerService.js │ │ │ │ ├── NetworkService.js │ │ │ │ ├── ResourceService.js │ │ │ │ ├── TransferService.js │ │ │ │ └── index.js │ │ │ ├── index.js │ │ │ ├── secure │ │ │ │ ├── HardwareService.js │ │ │ │ ├── KeyPairService.js │ │ │ │ ├── PasswordService.js │ │ │ │ ├── QRService.js │ │ │ │ ├── Seeder.js │ │ │ │ ├── SigningService.js │ │ │ │ └── index.js │ │ │ └── utility │ │ │ │ ├── ContactService.js │ │ │ │ ├── EventService.js │ │ │ │ ├── Framework.js │ │ │ │ ├── IdentityService.js │ │ │ │ ├── SingletonService.js │ │ │ │ ├── SocketService.js │ │ │ │ ├── StoreService.js │ │ │ │ ├── TokenService.js │ │ │ │ └── index.js │ │ ├── store │ │ │ ├── constants.js │ │ │ └── index.js │ │ └── util │ │ │ ├── Crypto.js │ │ │ ├── DateHelpers.js │ │ │ ├── Hasher.js │ │ │ ├── Http.js │ │ │ ├── IdGenerator.js │ │ │ ├── Mnemonic.js │ │ │ ├── ObjectHelpers.js │ │ │ ├── TestingHelper.js │ │ │ └── index.js │ ├── package-lock.json │ ├── package.json │ ├── prepare.js │ └── yarn.lock ├── eosio │ ├── README.md │ ├── __tests__ │ │ ├── api.test.js │ │ └── eosio.test.js │ ├── lib │ │ ├── api.js │ │ └── eosio.js │ ├── package-lock.json │ ├── package.json │ ├── prepare.js │ ├── yarn-error.log │ └── yarn.lock ├── ethereum │ ├── README.md │ ├── __tests__ │ │ └── ethereum.test.js │ ├── lib │ │ ├── erc20.json │ │ └── ethereum.js │ ├── package-lock.json │ ├── package.json │ ├── prepare.js │ └── yarn.lock ├── fio │ ├── README.md │ ├── __tests__ │ │ └── fio.test.js │ ├── lib │ │ └── fio.js │ ├── package-lock.json │ ├── package.json │ ├── prepare.js │ ├── yarn-error.log │ └── yarn.lock └── tron │ ├── README.md │ ├── __tests__ │ └── tron.test.js │ ├── lib │ ├── trc20.json │ └── tron.js │ ├── package-lock.json │ ├── package.json │ ├── prepare.js │ └── yarn.lock ├── scripts ├── copy-files.js ├── prepare.js └── prepublish.js ├── webpack.config.js ├── yarn-error.log └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | .env 4 | .DS_Store 5 | *.iml 6 | dist -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | **/webpack.config.js 3 | node_modules 4 | src 5 | test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 GetScatter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WalletPack 2 | 3 | This is a wallet building SDK which takes care of all of the heavy lifting for creating blockchain wallets. 4 | 5 | Currently being used in Scatter Desktop, Scatter Mobile, and Scatter Bridge. 6 | 7 | 8 | 9 | ## Setup 10 | 11 | Install the core plus any blockchains you want to support 12 | ``` 13 | npm i -S @walletpack/core @walletpack/eosio @walletpack/ethereum @walletpack/bitcoin @walletpack/tron 14 | ``` 15 | 16 | ### Call initialize first. 17 | 18 | ```js 19 | 20 | import WalletPack from '@walletpack/core'; 21 | 22 | const eventListener = (type, data) => { 23 | console.log('event', type, data); 24 | switch(type){ 25 | case 'popout': break; 26 | case 'firewalled': break; 27 | case 'no_certs': break; 28 | } 29 | console.log('event', type, data); 30 | }; 31 | WalletPack.initialize( 32 | // -------------------------------------------- 33 | // blockchains & blockchain plugins 34 | { 35 | blockchains:{ 36 | EOSIO:'eos', 37 | ETH:'eth', 38 | // TRX:'trx', 39 | BTC:'btc', 40 | }, 41 | plugins:[ 42 | require('@walletpack/eosio').default, 43 | require('@walletpack/ethereum').default, 44 | // require('@walletpack/tron').default, 45 | require('@walletpack/bitcoin').default, 46 | ] 47 | }, 48 | // -------------------------------------------- 49 | // store 50 | { 51 | state:{}, 52 | commit:(key, val) => {}, 53 | dispatch:(key, val) => {} 54 | }, 55 | // -------------------------------------------- 56 | // security 57 | { 58 | getSalt:() => '', 59 | get:() => () => '', 60 | set:(_seed) => () => '', 61 | clear:() => () => '', 62 | }, 63 | // -------------------------------------------- 64 | // framework 65 | { 66 | getVersion:WebHelpers.getVersion, 67 | pushNotification:WebHelpers.pushNotificationMethod(), 68 | }, 69 | // -------------------------------------------- 70 | // events 71 | eventListener, 72 | // -------------------------------------------- 73 | // optionals 74 | { 75 | // Enables websocket based 3rd party app support 76 | socketService:SocketService, 77 | 78 | // Allows you to override private key provision with 79 | // external services 80 | publicToPrivate:async publicKey => { 81 | return false; 82 | }, 83 | // Allows you to have custom signers instead of key provision, 84 | // which means you can sign on completely separate processes instead 85 | // of giving the private key to the renderer process 86 | signer:async (network, publicKey, payload, arbitrary = false, isHash = false) => { 87 | return sign(...); 88 | } 89 | } 90 | ); 91 | ``` 92 | 93 | 94 | 95 | 96 | ## Store/state requirements 97 | These properties and methods must be available on the injected store manager. 98 | 99 | 100 | ```js 101 | store:{ 102 | state:{ 103 | dappLogos:{}, 104 | dappData:{}, 105 | resources:{}, 106 | scatter:null, 107 | balances:{}, 108 | prices:{}, 109 | history:[], 110 | priceData:{}, 111 | }, 112 | commit:(key, val) => {}, 113 | dispatch:(key, value){}, 114 | } 115 | ``` 116 | 117 | 118 | #### dispatch 119 | This is an action handler that pre-processing commits to the state. 120 | [An example of these are here](https://github.com/GetScatter/ScatterDesktop/blob/core-extrapolation/src/store/actions.js) 121 | (_Some of these could possibly be put into the core library_) 122 | 123 | #### commit 124 | **must be synchronous** 125 | This is the actual commiter to the state which changes state values. 126 | [An example of these are here](https://github.com/GetScatter/ScatterDesktop/blob/core-extrapolation/src/store/mutations.js) 127 | 128 | 129 | 130 | 131 | ---------------------------- 132 | 133 | ## Reaching blockchain plugins 134 | 135 | `/src/plugins/defaults/interface.js` has common methods between each blockchain plugin. 136 | Instead of tapping directly into the plugin itself you can grab the singleton plugin based on the blockchain required and 137 | then process methods on it. 138 | 139 | ```js 140 | import PluginRepository from ... 141 | PluginRepository.plugin(Blockchains.EOSIO).method(...); 142 | ``` 143 | 144 | 145 | ---------------------------- 146 | 147 |
148 |
149 |
150 |
151 | 152 | Some constants for the docs below: 153 | - `$API = "https://api.get-scatter.com/v1/"` 154 | 155 | ## Services breakdown 156 | These are some of the important services in Scatter, and brief explanations of what they do and how to use them. 157 | 158 | **Note: All ScatterCore methods are static**. 159 | 160 | 161 | ---------------------------- 162 | 163 | ### ApiService 164 | 165 | This service handles all of the incoming messages from external applications. 166 | You should never actually have to handle this service manually in the application, as all of the methods will be called 167 | from the messages in the SocketService you provide. 168 | 169 | The flow is as follows. 170 | 171 | `app -> socket -> api handler -> openPopOut -> api handler -> socket -> app` 172 | 173 | [To see a live example of this happening see this](https://github.com/GetScatter/ScatterDesktop/blob/core-extrapolation/src/services/SocketService.js#L24) 174 | [And check out also the low level socket service](https://github.com/GetScatter/ScatterDesktop/blob/core-extrapolation/electron.js#L339) 175 | 176 | 177 | 178 | 179 | ---------------------------- 180 | 181 | ### PriceService 182 | This service (and the price data) keeps itself up to date using a recursive timeout. You should never have to 183 | fetch prices manually. 184 | 185 | #### `PriceService.getCurrencies()` 186 | This fetches the available fiat currency ticker symbols from `$API/currencies`. 187 | - Example result: `["USD","EUR","CNY","GBP","JPY","CAD","CHF","AUD"]` 188 | 189 | #### `PriceService.getCurrencyPrices()` 190 | This fetches the available fiat currency prices from `$API/currencies/prices`. These are prices in relation to USD. 191 | - Example result: `{"USD":1,"EUR":0.887901,"CNY":6.877801,"GBP":0.799055,"JPY":107.956006,"CAD":1.304397,"CHF":0.98455,"AUD":1.42273}` 192 | 193 | #### `PriceService.loadPriceTimelineData()` 194 | This fetches a timeline of price data from `$API/prices/timeline` for the past 24 hours. 195 | It will automatically insert the returned data into the `state` under `priceData` in the form of `{prices, yesterday, today}` 196 | 197 | #### `PriceService.getTotal(totals, displayCurrency, bypassDisplayToken, displayToken)` 198 | Returns formatted totals based on the entire balances inside of a user's accounts. 199 | 200 | ```js 201 | // Return format 202 | ---------------------------- 203 | return Token.fromJson({ 204 | symbol:this.fiatSymbol(displayCurrency), 205 | amount:total.toFixed(2), 206 | }) 207 | 208 | // Examples 209 | ----------------------------- 210 | // Returns the total fiat balance 211 | PriceService.getTotal(BalanceService.totalBalances(false).totals) 212 | 213 | // Returns the total token balance 214 | return PriceService.getTotal(BalanceService.totalBalances(false).totals, null, false, state.scatter.settings.displayToken); 215 | ``` 216 | 217 | #### `PriceService.fiatSymbol(currency = StoreService.get().state.scatter.settings.displayCurrency)` 218 | Returns an ascii currency sign ($/¥/€/£) instead of a ticker (USD/CNY/EUR/etc). 219 | 220 | 221 | 222 | ---------------------------- 223 | 224 | 225 | ### AppsService 226 | This service fills itself using the SingletonService which is instantiated once when opening a Scatter wallet. 227 | All app data is available on `state.dappData` 228 | 229 | #### `AppsService.getAppData(origin)` 230 | Returns formatted data based on the applink (origin/fqdn) of the apps trying to interact with Scatter. 231 | If the app doesn't exist on the `state.dappData` then it will return a formatted result regardless. 232 | 233 | ```js 234 | // Return structure 235 | { 236 | applink:origin, 237 | type:'', 238 | name:origin, 239 | description:'', 240 | logo:'', 241 | url:'', 242 | } 243 | ``` 244 | 245 | #### `AppsService.categories(selectedCategory = null)` 246 | Returns a list of categories available based on the `state.dappData`. 247 | This is a simple helper method that loops over the dapps and aggregates the `.type` param. 248 | 249 | #### `AppsService.appsByCategory(selectedCategory = null)` 250 | Returns all the apps available with a given category. 251 | 252 | #### `AppsService.appsByTerm(terms)` 253 | Returns all the apps available with a given search terms. 254 | 255 | #### `AppsService.linkedApps(terms = '', categoryFilter = null)` 256 | Returns all of the apps that are **linked** in the user's Scatter. 257 | These are apps that the user already has permissions for (My Apps). 258 | 259 | 260 | ---------------------------- 261 | 262 | 263 | ### PermissionService 264 | This service handles everything to do with application permissions, including whitelists. 265 | A lot of the handling is internal for the library but below are some methods that will need to be 266 | integrated into components. 267 | 268 | #### `PermissionService.removeAllPermissions()` 269 | Removes every single permission that the user has. This includes all application permissions and whitelists. 270 | 271 | #### `PermissionService.removeAllPermissionsFor(origin)` 272 | Removes every permission for a given origin/applink 273 | 274 | #### `PermissionService.removePermission(permission)` 275 | Removes a given permission 276 | 277 | 278 | 279 | 280 | 281 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const EXPORTS = { 2 | "presets": ["@babel/preset-env"], 3 | "plugins": [] 4 | }; 5 | 6 | if(process.env.WALLETPACK_TESTING){ 7 | EXPORTS.plugins.push(["@babel/transform-async-to-generator"]); 8 | // This is used for tests, since the import path structures 9 | // are different for the packages internally. 10 | EXPORTS.plugins.push(["module-resolver", { 11 | "alias": { 12 | "^@walletpack/core/(.+)": ([, name]) => { 13 | // Catching absolute lib imports 14 | if(name.indexOf('lib') > -1) name = name.replace('lib/', ''); 15 | // Prefixing includes 16 | return `./packages/core/lib/${name}` 17 | } 18 | } 19 | }]) 20 | } else { 21 | EXPORTS.plugins.push(["@babel/transform-runtime"]); 22 | } 23 | 24 | module.exports = EXPORTS; 25 | -------------------------------------------------------------------------------- /lerna-debug.log: -------------------------------------------------------------------------------- 1 | 51 error Error: Command failed: git push --follow-tags --no-verify origin master 2 | 51 error 3 | 51 error at makeError (C:\Work\Libraries\walletpack\node_modules\execa\index.js:174:9) 4 | 51 error at Promise.all.then.arr (C:\Work\Libraries\walletpack\node_modules\execa\index.js:278:16) 5 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "command": { 6 | "publish": { 7 | "ignoreChanges": [ "*.md" ] 8 | }, 9 | "bootstrap": { 10 | "npmClientArgs": ["--no-package-lock"] 11 | } 12 | }, 13 | "version": "independent" 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "walletpack", 3 | "private": true, 4 | "scripts": { 5 | "postinstall": "lerna bootstrap", 6 | "build": "lerna exec -- babel --config-file ../../babel.config.js --out-dir dist lib && node scripts/copy-files", 7 | "links": "lerna exec -- yarn link", 8 | "publish": "node ./scripts/prepublish.js && lerna publish", 9 | "testfile": "cross-env WALLETPACK_TESTING=true mocha --require @babel/register --require @babel/polyfill --exit --timeout 1000000", 10 | "test": "npm run testfile \"./packages/**/__tests__/**/*.test.js\"", 11 | "pack": "webpack-cli --mode=production --display-error-details" 12 | }, 13 | "dependencies": {}, 14 | "devDependencies": { 15 | "@babel/cli": "^7.4.4", 16 | "@babel/core": "^7.6.4", 17 | "@babel/plugin-transform-async-to-generator": "^7.5.0", 18 | "@babel/plugin-transform-runtime": "^7.6.2", 19 | "@babel/polyfill": "^7.6.0", 20 | "@babel/preset-env": "^7.4.5", 21 | "@babel/register": "^7.5.5", 22 | "@babel/runtime": "^7.0.0", 23 | "babel-loader": "^8.0.2", 24 | "babel-plugin-module-resolver": "^3.2.0", 25 | "babel-preset-minify": "^0.5.0-alpha.3cc09dcf", 26 | "chai": "^4.1.2", 27 | "concurrently": "^4.0.1", 28 | "cross-env": "^6.0.3", 29 | "isomorphic-fetch": "^2.2.1", 30 | "lerna": "^3.15.0", 31 | "mocha": "^6.2.2", 32 | "webpack": "^4.17.2", 33 | "webpack-cli": "^3.1.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/bitcoin/README.md: -------------------------------------------------------------------------------- 1 | # `bitcoin` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const bitcoin = require('bitcoin'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/bitcoin/__tests__/bitcoin.test.js: -------------------------------------------------------------------------------- 1 | // 'use strict'; 2 | // 3 | // const bitcoin = require('..'); 4 | // 5 | // describe('bitcoin', () => { 6 | // it('needs tests'); 7 | // }); 8 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/bitcoin.js: -------------------------------------------------------------------------------- 1 | import Plugin from '@walletpack/core/plugins/Plugin'; 2 | import * as PluginTypes from '@walletpack/core/plugins/PluginTypes'; 3 | import {Blockchains} from '@walletpack/core/models/Blockchains' 4 | import Network from "@walletpack/core/models/Network"; 5 | import Token from "@walletpack/core/models/Token"; 6 | import ObjectHelpers from "@walletpack/core/util/ObjectHelpers"; 7 | import KeyPairService from "@walletpack/core/services/secure/KeyPairService"; 8 | import StoreService from "@walletpack/core/services/utility/StoreService"; 9 | import * as Actions from "@walletpack/core/models/api/ApiActions"; 10 | import {GET, POST} from "@walletpack/core/services/apis/BackendApiService"; 11 | import EventService from "@walletpack/core/services/utility/EventService"; 12 | import SigningService from "@walletpack/core/services/secure/SigningService"; 13 | 14 | const bitcoin = require('bitcoinjs-lib'); 15 | 16 | 17 | const SELECTED_CHAIN = 0; 18 | const SELECTED_NETWORK = SELECTED_CHAIN === 0 ? bitcoin.networks.bitcoin : bitcoin.networks.testnet; 19 | 20 | 21 | const EXPLORER = { 22 | "name":"Blockcypher", 23 | "account":"https://live.blockcypher.com/btc/address/{x}", 24 | "transaction":"https://live.blockcypher.com/btc/tx/{x}", 25 | "block":"https://live.blockcypher.com/btc/block/{x}" 26 | }; 27 | 28 | export default class BTC extends Plugin { 29 | 30 | constructor(){ super(Blockchains.BTC, PluginTypes.BLOCKCHAIN_SUPPORT) } 31 | 32 | bip(){ return `44'/0'/0'/0/`} 33 | bustCache(){ } 34 | defaultExplorer(){ return EXPLORER; } 35 | accountFormatter(account){ return `${account.publicKey}` } 36 | returnableAccount(account){ return { address:account.publicKey, blockchain:Blockchains.BTC }} 37 | 38 | contractPlaceholder(){ return '...'; } 39 | 40 | checkNetwork(network){ 41 | return Promise.race([ 42 | new Promise(resolve => setTimeout(() => resolve(null), 2000)), 43 | //TODO: 44 | new Promise(resolve => setTimeout(() => resolve(true), 10)), 45 | ]) 46 | } 47 | 48 | getEndorsedNetwork(){ 49 | return new Network('Bitcoin Mainnet', 'https', 'btcnodes.get-scatter.com', 443, Blockchains.BTC, '1') 50 | } 51 | 52 | isEndorsedNetwork(network){ 53 | const endorsedNetwork = this.getEndorsedNetwork(); 54 | return network.blockchain === Blockchains.BTC && network.chainId === endorsedNetwork.chainId; 55 | } 56 | 57 | async getChainId(network){ 58 | return 1; 59 | } 60 | 61 | usesResources(){ return false; } 62 | hasAccountActions(){ return false; } 63 | 64 | accountsAreImported(){ return false; } 65 | isValidRecipient(address){ return this.validPublicKey(address); } 66 | privateToPublic(privateKey){ 67 | const pubkey = (() => { 68 | if(typeof privateKey === 'string') return bitcoin.ECPair.fromWIF(privateKey, SELECTED_NETWORK); 69 | else return bitcoin.ECPair.fromPrivateKey(privateKey, {network:SELECTED_NETWORK}); 70 | })().publicKey; 71 | 72 | const { address } = bitcoin.payments.p2pkh({ pubkey, network:SELECTED_NETWORK }) 73 | return address; 74 | } 75 | 76 | validPrivateKey(privateKey){ 77 | return (typeof privateKey === 'string' ? privateKey : this.bufferToHexPrivate(privateKey)).length === 52; 78 | } 79 | 80 | validPublicKey(publicKey){ 81 | return publicKey.length === 34; 82 | } 83 | 84 | bufferToHexPrivate(buffer){ 85 | return bitcoin.ECPair.fromPrivateKey(Buffer.from(buffer), {network:SELECTED_NETWORK}).toWIF() 86 | } 87 | 88 | hexPrivateToBuffer(privateKey){ 89 | return bitcoin.ECPair.fromWIF(privateKey, SELECTED_NETWORK).privateKey; 90 | } 91 | 92 | 93 | bufferToHexPublicKeyOrAddress(buffer){ 94 | return bitcoin.payments.p2pkh({ pubkey:buffer }).address; 95 | } 96 | 97 | hasUntouchableTokens(){ return false; } 98 | 99 | async balanceFor(account){ 100 | return GET(`btc/balance/${account.publicKey}`).then(res => { 101 | const token = this.defaultToken().clone(); 102 | token.amount = parseInt(res[account.publicKey].final_balance) / 100000000; 103 | return token; 104 | }); 105 | } 106 | 107 | async balancesFor(account){ 108 | const balance = await this.balanceFor(account); 109 | return balance ? [balance] : []; 110 | } 111 | 112 | defaultDecimals(){ return 8; } 113 | defaultToken(){ return new Token(Blockchains.BTC, 'btc', 'BTC', 'BTC', this.defaultDecimals(), '1') } 114 | 115 | actionParticipants(payload){ 116 | return ObjectHelpers.flatten( 117 | payload.messages 118 | .map(message => message.authorization) 119 | ); 120 | } 121 | 122 | async transfer({account, to, amount, promptForSignature = true}){ 123 | amount = amount * 100000000; 124 | 125 | try { 126 | return new Promise(async (resolve, reject) => { 127 | const txb = new bitcoin.TransactionBuilder(SELECTED_NETWORK); 128 | txb.setVersion(1); 129 | 130 | // The amount you are sending to the recipient. 131 | txb.addOutput(to, amount); 132 | 133 | // Calculating unspent inputs 134 | const utxos = await GET(`btc/unspent/${account.publicKey}`).then(x => x.unspent_outputs).catch(() => null); 135 | if(!utxos) return resolve({error:`There was a problem loading UTXOs for ${account.publicKey}`}); 136 | 137 | let inputs = 0; 138 | for (let utx of utxos) { 139 | txb.addInput(utx.tx_hash_big_endian, utx.tx_output_n); 140 | inputs += utx.value; 141 | if (inputs >= amount) break; 142 | } 143 | 144 | 145 | // Calculating the fee 146 | let bestFee = await GET(`fees`).then(x => x.btc).catch(() => null); 147 | if(!bestFee) return resolve({error:`Couldn't get fee`}); 148 | // Sats * bytes 149 | const fee = (txb.buildIncomplete().toHex().length * bestFee); 150 | 151 | // Returning unspent to sender. 152 | const change = inputs - (amount + fee); 153 | if(change < 0) return resolve({error:`Insufficient BTC: ${inputs}. (Most likely due to fees, you need to leave ${fee} worth)`}); 154 | if (change) txb.addOutput(account.publicKey, change); 155 | 156 | const payload = { transaction:{from:account.publicKey, to, amount:amount / 100000000, fee:fee / 100000000}, unsigned:txb.buildIncomplete().toHex(), 157 | blockchain:Blockchains.BTC, network:account.network(), requiredFields:{}, abi:null }; 158 | const signed = promptForSignature 159 | ? await this.signerWithPopup(payload, account, x => resolve(x), null) 160 | : await SigningService.sign(account.network(), payload.unsigned, account.publicKey, false, false); 161 | 162 | if(!signed) return; 163 | 164 | await POST(`btc/pushtx`, {signed}).then(res => { 165 | if(res.indexOf('Transaction Submitted') > -1){ 166 | resolve({txid:bitcoin.Transaction.fromHex(signed).getId()}); 167 | } else { 168 | resolve({error:res}); 169 | } 170 | }).catch(error => { 171 | console.error(error); 172 | resolve({error}) 173 | }); 174 | }) 175 | } catch(e){ 176 | console.error(e); 177 | resolve({error:e}); 178 | } 179 | 180 | 181 | } 182 | 183 | async signer(transaction, publicKey, arbitrary = false, isHash = false, privateKey = null){ 184 | try { 185 | // TODO: No hardware support yet. 186 | // if(account && KeyPairService.isHardware(publicKey)) 187 | // return await HardwareService.sign(account, transaction); 188 | 189 | if(!privateKey) privateKey = await KeyPairService.publicToPrivate(publicKey); 190 | if(!privateKey) return; 191 | 192 | if(typeof privateKey === 'string') privateKey = this.hexPrivateToBuffer(privateKey); 193 | const key = bitcoin.ECPair.fromPrivateKey(Buffer.from(privateKey), {network:SELECTED_NETWORK}); 194 | const txb = bitcoin.TransactionBuilder.fromTransaction(bitcoin.Transaction.fromHex(transaction), SELECTED_NETWORK) 195 | 196 | if(Object.keys(txb.__PREV_TX_SET).length > 1){ 197 | Object.keys(txb.__PREV_TX_SET).map((x,i) => { 198 | txb.sign(i, key); 199 | }) 200 | } else txb.sign(0, key); 201 | return txb.build().toHex(); 202 | } catch(e){ 203 | console.error(e); 204 | return null; 205 | } 206 | } 207 | 208 | async signerWithPopup(payload, account, rejector, token = null){ 209 | return new Promise(async resolve => { 210 | payload.messages = [{ 211 | data:payload.transaction, 212 | code:payload.transaction.to, 213 | type:'transfer', 214 | authorization:payload.transaction.from 215 | }]; 216 | payload.identityKey = StoreService.get().state.scatter.keychain.identities[0].publicKey; 217 | payload.participants = [account]; 218 | payload.network = account.network(); 219 | payload.origin = 'Scatter'; 220 | const request = { 221 | payload, 222 | origin:payload.origin, 223 | blockchain:Blockchains.BTC, 224 | requiredFields:{}, 225 | type:Actions.SIGN, 226 | id:1, 227 | }; 228 | 229 | EventService.emit('popout', request).then( async ({result}) => { 230 | if(!result || (!result.accepted || false)) return rejector({error:'Could not get signature'}); 231 | 232 | // TODO: No hardware 233 | // let signature = null; 234 | // if(KeyPairService.isHardware(account.publicKey)){ 235 | // signature = await HardwareService.sign(account, payload); 236 | // } else signature = await this.signer(payload.transaction, account.publicKey, true); 237 | 238 | const signature = await SigningService.sign(payload.network, payload.unsigned, account.publicKey, true); 239 | if(!signature) return rejector({error:'Could not get signature'}); 240 | resolve(signature); 241 | }) 242 | }) 243 | } 244 | 245 | async requestParser(transaction, network){ 246 | throw new Error("Bitcoin not yet supported externally") 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /packages/bitcoin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@walletpack/bitcoin", 3 | "version": "1.0.56", 4 | "description": "> TODO: description", 5 | "author": "GetScatter Ltd. 2019", 6 | "homepage": "", 7 | "license": "ISC", 8 | "main": "bitcoin.js", 9 | "files": [ 10 | "dist", 11 | "prepare.js" 12 | ], 13 | "scripts": { 14 | "install": "node prepare.js", 15 | "test": "echo \"Error: run tests from root\" && exit 1" 16 | }, 17 | "dependencies": { 18 | "@walletpack/core": "^1.0.51", 19 | "bitcoinjs-lib": "^5.1.2" 20 | }, 21 | "gitHead": "70bd19f93b503618a79eb519eef082bfc40b16d7", 22 | "publishConfig": { 23 | "access": "public" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/bitcoin/prepare.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const rimraf = require("rimraf"); 4 | 5 | const paths = __dirname.split(path.sep); 6 | const parent = paths[paths.length-2]; 7 | 8 | if(parent === 'packages') return; 9 | 10 | try { 11 | rimraf.sync("./__tests__"); 12 | const files = fs.readdirSync(`./dist`); 13 | files.map(file => { 14 | if(fs.existsSync(`./${file}`)) rimraf.sync(`./${file}`); 15 | fs.renameSync(`./dist/${file}`, `./${file}`); 16 | }) 17 | rimraf.sync("./dist"); 18 | rimraf.sync("./lib"); 19 | } catch(e){ 20 | console.error('Walletpack prepare.js error', e); 21 | } 22 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # `core` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const core = require('core'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/core/__tests__/core.test.js: -------------------------------------------------------------------------------- 1 | // 'use strict'; 2 | // 3 | // const core = require('../lib/core'); 4 | // 5 | // describe('core', () => { 6 | // it('needs tests'); 7 | // console.log('core', core); 8 | // }); 9 | 10 | -------------------------------------------------------------------------------- /packages/core/lib/core.js: -------------------------------------------------------------------------------- 1 | import migrations from './migrations' 2 | import models from './models' 3 | import plugins from './plugins' 4 | import services from './services' 5 | import store from './store' 6 | import util from './util' 7 | 8 | const ScatterCore = { 9 | 10 | initialize( 11 | { 12 | blockchains, 13 | plugins:_plugins, 14 | nameParser = null 15 | }, 16 | store, 17 | security, 18 | framework, 19 | eventListener, 20 | { 21 | socketService = null, 22 | hardwareService = null, 23 | publicToPrivate = null, 24 | signer = null, 25 | }, 26 | ){ 27 | models.Blockchains.setBlockchains(blockchains, nameParser); 28 | plugins.PluginRepository.loadPlugins(_plugins); 29 | 30 | services.utility.StoreService.init(store); 31 | services.secure.Seeder.init(security); 32 | services.utility.Framework.init(framework); 33 | services.utility.EventService.init(eventListener); 34 | 35 | // Some wallets don't require dapp integration. 36 | if(socketService) services.utility.SocketService.init(socketService); 37 | 38 | // Some wallets aren't targeting hardware wallets. 39 | if(hardwareService) services.secure.HardwareService.init(hardwareService); 40 | 41 | // Optional method for providing extra ways to create private keys 42 | // from public keys. If only used for certain keys, return `false` on normal keys. 43 | // If it returns `null` or `PRIV_KEY` it will resolve that instead of falling back to internals. 44 | if(publicToPrivate) services.secure.KeyPairService.init(publicToPrivate); 45 | if(signer) services.secure.SigningService.init(signer); 46 | 47 | return true; 48 | }, 49 | 50 | migrations, 51 | models, 52 | plugins, 53 | services, 54 | store, 55 | util, 56 | } 57 | 58 | export default ScatterCore; -------------------------------------------------------------------------------- /packages/core/lib/migrations/index.js: -------------------------------------------------------------------------------- 1 | import * as migrator from './migrator'; 2 | export default migrator; -------------------------------------------------------------------------------- /packages/core/lib/migrations/migrator.js: -------------------------------------------------------------------------------- 1 | export const mathematicalVersion = version => { 2 | if(!version || version === '0') return 0; 3 | const parts = version.replace(/[.]/g,'_').replace(/[m]/g, '').split('_'); 4 | if(parts.length !== 3) throw new Error("Migration error, invalid version"); 5 | 6 | let total = 0; 7 | parts.map((v, i) => { 8 | const multiplier = i === 0 ? 100 : i === 1 ? 10 : 1; 9 | total += parseFloat(v) * multiplier; 10 | }); 11 | 12 | return total; 13 | }; 14 | 15 | const fnToVersion = fnName => fnName.replace(/[m]/g, '').replace(/[_]/g,'.'); 16 | 17 | export default async (scatter, migrators) => { 18 | scatter.meta.regenerateVersion(); 19 | if(scatter.isEncrypted()) return false; 20 | if(!scatter.meta.needsUpdating()) return false; 21 | 22 | const lastVersion = mathematicalVersion(scatter.meta.lastVersion); 23 | const nextVersions = Object.keys(migrators).filter(v => mathematicalVersion(v) > lastVersion) 24 | .sort((a,b) => mathematicalVersion(a) - mathematicalVersion(b)); 25 | 26 | if(nextVersions.length) { 27 | for(let i = 0; i < nextVersions.length; i++){ 28 | await migrators[nextVersions[i]](scatter) 29 | } 30 | scatter.meta.lastVersion = fnToVersion(nextVersions[nextVersions.length-1]); 31 | } 32 | 33 | return nextVersions.length > 0; 34 | } -------------------------------------------------------------------------------- /packages/core/lib/models/Account.js: -------------------------------------------------------------------------------- 1 | import PluginRepository from '../plugins/PluginRepository'; 2 | import {Blockchains} from "./Blockchains"; 3 | import Token from "./Token"; 4 | import StoreService from "../services/utility/StoreService"; 5 | 6 | export default class Account { 7 | constructor(){ 8 | this.keypairUnique = ''; 9 | this.networkUnique = ''; 10 | this.publicKey = ''; 11 | this.name = ''; 12 | this.authority = ''; 13 | 14 | this.logins = 0; 15 | 16 | this.createdAt = +new Date(); 17 | this.fromOrigin = null; 18 | } 19 | 20 | sendable(){ 21 | return PluginRepository.plugin(this.blockchain()).accountsAreImported() ? this.name : this.publicKey; 22 | } 23 | 24 | formatted(){ 25 | return PluginRepository.plugin(this.blockchain()).accountFormatter(this); 26 | } 27 | 28 | network(){ 29 | return StoreService.get().state.scatter.settings.networks.find(x => x.unique() === this.networkUnique); 30 | } 31 | 32 | keypair(){ 33 | return StoreService.get().state.scatter.keychain.keypairs.find(x => x.unique() === this.keypairUnique); 34 | } 35 | 36 | blockchain(){ 37 | if(!this.keypair()) return console.error('account.blockchain() error'); 38 | return this.keypair().publicKeys.find(x => x.key === this.publicKey).blockchain; 39 | } 40 | 41 | authorities(thisKeyOnly = true){ 42 | if(!this.authority.length) return []; 43 | return StoreService.get().state.scatter.keychain.accounts 44 | .filter(x => x.identifiable() === this.identifiable() && (!thisKeyOnly || x.keypairUnique === this.keypairUnique)) 45 | .sort((a,b) => a.authority.localeCompare(b.authority)); 46 | } 47 | 48 | hasDangerousAuthority(){ 49 | return this.authorities().find(x => x.authority === 'owner'); 50 | } 51 | 52 | static placeholder(){ return new Account(); } 53 | static fromJson(json){ return Object.assign(this.placeholder(), json); } 54 | unique(){ return this.keypairUnique + this.networkUnique + this.name + this.authority + this.publicKey; } 55 | identifiable(){ return this.networkUnique + this.sendable(); } 56 | clone(){ return Account.fromJson(JSON.parse(JSON.stringify(this))) } 57 | 58 | asReturnable(){ 59 | const returnable = PluginRepository.plugin(this.blockchain()).returnableAccount(this); 60 | returnable.chainId = this.network().chainId; 61 | returnable.isHardware = !!this.keypair().external; 62 | return returnable; 63 | } 64 | 65 | tokenCount(systemToken = null){ 66 | if(!StoreService.get().state.balances) return 0; 67 | if(!StoreService.get().state.balances.hasOwnProperty(this.identifiable())) return 0; 68 | if(!StoreService.get().state.balances[this.identifiable()]) return 0; 69 | return StoreService.get().state.balances[this.identifiable()].filter(x => !systemToken ? true : x.identifiable() !== systemToken.identifiable()).length; 70 | } 71 | 72 | tokens(){ 73 | let base = [this.network().systemToken()]; 74 | if(!StoreService.get().state.balances) return base; 75 | if(!StoreService.get().state.balances.hasOwnProperty(this.identifiable())) return base; 76 | if(!StoreService.get().state.balances[this.identifiable()]) return base; 77 | return StoreService.get().state.balances[this.identifiable()]; 78 | } 79 | 80 | balanceFor(token){ 81 | return this.tokens().find(x => x.uniqueWithChain() === token.uniqueWithChain()); 82 | } 83 | 84 | systemBalance(withSymbol = false){ 85 | if(!StoreService.get().state.balances) return 0; 86 | if(!StoreService.get().state.balances.hasOwnProperty(this.identifiable())) return 0; 87 | if(!StoreService.get().state.balances[this.identifiable()]) return 0; 88 | const systemBalance = StoreService.get().state.balances[this.identifiable()].find(x => Token.fromJson(x).identifiable() === this.network().systemToken().identifiable()); 89 | if(!systemBalance) return 0; 90 | return `${systemBalance.amount} ${withSymbol ? systemBalance.symbol : ''}`; 91 | } 92 | 93 | totalFiatBalance(){ 94 | return this.tokens().reduce((acc, x) => { 95 | acc += x.fiatBalance(false) ? parseFloat(x.fiatBalance(false)) : 0; 96 | return acc; 97 | }, 0).toFixed(2) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /packages/core/lib/models/AccountAction.js: -------------------------------------------------------------------------------- 1 | export default class AccountAction { 2 | constructor(type, onclick = () => {}, danger = false){ 3 | this.type = type; 4 | this.onclick = onclick; 5 | this.isDangerous = danger; 6 | } 7 | 8 | static placeholder(){ return new AccountAction(); } 9 | static fromJson(json){ return Object.assign(this.placeholder(), json); } 10 | } 11 | -------------------------------------------------------------------------------- /packages/core/lib/models/AuthorizedApp.js: -------------------------------------------------------------------------------- 1 | import Hasher from '../util/Hasher'; 2 | 3 | export default class AuthorizedApp { 4 | 5 | constructor(_origin, _appkey){ 6 | this.origin = _origin; 7 | this.appkey = _appkey; 8 | this.nextNonce = ''; 9 | this.createdAt = +new Date(); 10 | } 11 | 12 | static placeholder(){ return new AuthorizedApp(); } 13 | static fromJson(json){ return Object.assign(this.placeholder(), json); } 14 | checkKey(hashed){ return hashed === this.hashed(); } 15 | hashed(){ return Hasher.unsaltedQuickHash(this.appkey); } 16 | checkNonce(nonce){ return this.nextNonce === Hasher.unsaltedQuickHash(nonce) } 17 | } -------------------------------------------------------------------------------- /packages/core/lib/models/Blockchains.js: -------------------------------------------------------------------------------- 1 | 2 | export let Blockchains = { 3 | EOSIO:'eos', 4 | ETH:'eth', 5 | TRX:'trx', 6 | BTC:'btc', 7 | FIO:'fio', 8 | }; 9 | 10 | export let BlockchainsArray = Object.keys(Blockchains).map(key => ({key, value:Blockchains[key]})); 11 | 12 | export let blockchainName = x => { 13 | switch(x){ 14 | case 'btc': return 'Bitcoin'; 15 | case Blockchains.EOSIO: return 'EOSIO'; 16 | case Blockchains.ETH: return 'Ethereum'; 17 | case Blockchains.TRX: return 'Tron'; 18 | case Blockchains.BTC: return 'Bitcoin'; 19 | case Blockchains.FIO: return 'FIO'; 20 | default: return x; 21 | } 22 | }; 23 | 24 | export const setBlockchains = (_Blockchains, _blockchainNameParser = null) => { 25 | Blockchains = _Blockchains; 26 | BlockchainsArray = Object.keys(Blockchains).map(key => ({key, value:Blockchains[key]})); 27 | if(_blockchainNameParser) blockchainName = _blockchainNameParser; 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/lib/models/Contact.js: -------------------------------------------------------------------------------- 1 | import IdGenerator from '../util/IdGenerator' 2 | import {Blockchains} from './Blockchains'; 3 | 4 | export default class Contact { 5 | 6 | constructor(_name = '', _recipient = '', _blockchain = null, _chainId = null){ 7 | this.id = IdGenerator.text(24); 8 | this.name = _name; 9 | this.recipient = _recipient; 10 | this.blockchain = _blockchain; 11 | this.chainId = _chainId; 12 | } 13 | 14 | static placeholder(){ return new Contact(); } 15 | static fromJson(json){ return Object.assign(this.placeholder(), json); } 16 | 17 | unique(){ return `${this.blockchain}::${this.recipient}::${this.name}${/* LEGACY SUPPORT */ this.chainId ? `::${this.chainId}` : ''}`.toLowerCase().trim(); } 18 | clone(){ return Contact.fromJson(JSON.parse(JSON.stringify(this))) } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /packages/core/lib/models/CreditCard.js: -------------------------------------------------------------------------------- 1 | import AES from 'aes-oop'; 2 | import IdGenerator from '../util/IdGenerator'; 3 | import Crypto from '../util/Crypto'; 4 | import Keychain from "./Keychain"; 5 | 6 | export class CreditCardPersonalInformation { 7 | constructor() { 8 | this.name = ''; 9 | this.email = ''; 10 | this.birthdate = ''; 11 | this.address = ''; 12 | this.city = ''; 13 | this.state = ''; 14 | this.country = ''; 15 | this.zipcode = ''; 16 | } 17 | 18 | static placeholder(){ return new CreditCardPersonalInformation(); } 19 | static fromJson(json){ return Object.assign(this.placeholder(), json); } 20 | clone(){ return CreditCardPersonalInformation.fromJson(JSON.parse(JSON.stringify(this))) } 21 | 22 | } 23 | 24 | export class CreditCardSecureProperties { 25 | 26 | constructor(){ 27 | this.number = ''; 28 | this.authTokens = {}; 29 | this.cardHash = ''; 30 | 31 | this.personalInformation = CreditCardPersonalInformation.placeholder(); 32 | } 33 | 34 | static placeholder(){ return new CreditCardSecureProperties(); } 35 | static fromJson(json){ 36 | let p = Object.assign(this.placeholder(), json); 37 | if(json.hasOwnProperty('personalInformation')) 38 | p.personalInformation = CreditCardPersonalInformation.fromJson(json.personalInformation); 39 | return p; 40 | } 41 | clone(){ return CreditCardSecureProperties.fromJson(JSON.parse(JSON.stringify(this))) } 42 | 43 | } 44 | 45 | export default class CreditCard { 46 | 47 | constructor(){ 48 | this.id = IdGenerator.text(24); 49 | this.name = ''; 50 | this.lastFour = ''; 51 | this.expiration = ''; 52 | this.secure = CreditCardSecureProperties.placeholder(); 53 | this.createdAt = +new Date(); 54 | } 55 | 56 | static placeholder(){ return new CreditCard(); } 57 | static fromJson(json){ 58 | let p = Object.assign(this.placeholder(), json); 59 | if(json.hasOwnProperty('secure')) 60 | p.secure = (typeof json.secure === 'string') 61 | ? json.secure : CreditCardSecureProperties.fromJson(json.secure); 62 | return p; 63 | } 64 | unique(){ return this.id; } 65 | clone(){ return CreditCard.fromJson(JSON.parse(JSON.stringify(this))) } 66 | hash(){ this.cardHash = Crypto.bufferToHash(this.secure.number); } 67 | 68 | isEncrypted(){ 69 | return typeof this.secure === 'string'; 70 | } 71 | 72 | encrypt(seed){ 73 | if(!this.isEncrypted()) this.secure = AES.encrypt(this.secure, seed); 74 | } 75 | 76 | decrypt(seed){ 77 | if(this.isEncrypted()) this.secure = AES.decrypt(this.secure, seed); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/core/lib/models/Explorer.js: -------------------------------------------------------------------------------- 1 | export default class Explorer { 2 | 3 | constructor(){ 4 | this.raw = null; 5 | this.name = null; 6 | this.account = null; 7 | this.transaction = null; 8 | this.block = null; 9 | } 10 | 11 | static placeholder(){ return new Explorer(); } 12 | static fromJson(json){ return Object.assign(this.placeholder(), json); } 13 | static fromRaw(rawExplorer){ 14 | if(!rawExplorer) return this.placeholder(); 15 | return this.fromJson({ 16 | raw:rawExplorer, 17 | name:rawExplorer.name, 18 | account:x => rawExplorer.account.replace('{x}', x), 19 | transaction:x => rawExplorer.transaction.replace('{x}', x), 20 | block:x => rawExplorer.block.replace('{x}', x), 21 | }); 22 | } 23 | 24 | parsed(){ 25 | return Explorer.fromRaw(this.raw); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/lib/models/Keychain.js: -------------------------------------------------------------------------------- 1 | import Identity, {LocationInformation} from './Identity'; 2 | import Permission from './Permission'; 3 | import Keypair from './Keypair'; 4 | import Account from './Account'; 5 | import AuthorizedApp from './AuthorizedApp'; 6 | import CreditCard from "./CreditCard"; 7 | import StoreService from "../services/utility/StoreService"; 8 | import * as Actions from "../store/constants"; 9 | 10 | export default class Keychain { 11 | 12 | constructor(){ 13 | this.keypairs = []; 14 | this.accounts = []; 15 | this.identities = []; 16 | this.locations = []; 17 | this.permissions = []; 18 | this.cards = []; 19 | this.apps = []; 20 | this.avatars = {}; 21 | 22 | this.lastUsedIdentity = null; 23 | } 24 | 25 | static placeholder(){ return new Keychain(); } 26 | static fromJson(json){ 27 | let p = Object.assign(this.placeholder(), json); 28 | if(json.hasOwnProperty('keypairs')) p.keypairs = json.keypairs.map(x => Keypair.fromJson(x)); 29 | if(json.hasOwnProperty('accounts')) p.accounts = json.accounts.map(x => Account.fromJson(x)); 30 | if(json.hasOwnProperty('identities')) p.identities = json.identities.map(x => Identity.fromJson(x)); 31 | if(json.hasOwnProperty('locations')) p.locations = json.locations.map(x => LocationInformation.fromJson(x)); 32 | if(json.hasOwnProperty('permissions')) p.permissions = json.permissions.map(x => Permission.fromJson(x)); 33 | if(json.hasOwnProperty('cards')) p.cards = json.cards.map(x => CreditCard.fromJson(x)); 34 | if(json.hasOwnProperty('apps')) p.apps = json.apps.map(x => AuthorizedApp.fromJson(x)); 35 | return p; 36 | } 37 | 38 | clone(){ return Keychain.fromJson(JSON.parse(JSON.stringify(this))) } 39 | 40 | findIdentity(id){ 41 | return this.identities.find(identity => identity.id === id); 42 | } 43 | 44 | updateOrPushApp(app){ 45 | this.apps.find(x => x.origin === app.origin) 46 | ? this.apps = this.apps.map(x => x.origin === app.origin ? app : x) 47 | : this.apps.unshift(app); 48 | } 49 | 50 | removeApp(app){ 51 | this.apps = this.apps.filter(x => x.origin !== app.origin); 52 | } 53 | 54 | findApp(origin){ 55 | return this.apps.find(x => x.origin === origin); 56 | } 57 | 58 | updateOrPushIdentity(identity){ 59 | this.identities.find(id => id.id === identity.id) 60 | ? this.identities = this.identities.map(id => id.id === identity.id ? identity : id) 61 | : this.identities.unshift(identity); 62 | } 63 | 64 | removeIdentity(identity){ 65 | this.identities = this.identities.filter(id => id.id !== identity.id); 66 | this.permissions = this.permissions.filter(perm => perm.identity !== identity.id); 67 | delete this.avatars[identity.id]; 68 | } 69 | 70 | updateOrPushLocation(location){ 71 | this.locations.find(id => id.id === location.id) 72 | ? this.locations = this.locations.map(id => id.id === location.id ? location : id) 73 | : this.locations.unshift(location); 74 | } 75 | 76 | removeLocation(location){ 77 | this.locations = this.locations.filter(x => x.id !== location.id); 78 | this.identities.map(identity => { 79 | if(identity.location === location.id){ 80 | identity.location = null; 81 | } 82 | }) 83 | } 84 | 85 | getKeyPairByName(name){ 86 | return this.keypairs.find(key => key.name.toLowerCase() === name.toLowerCase()) 87 | } 88 | 89 | getKeyPairByPublicKey(publicKey){ 90 | if(!publicKey) return; 91 | return this.keypairs.find(key => key.publicKeys.find(x => x.key.toLowerCase() === publicKey.toLowerCase())) 92 | } 93 | 94 | removeKeyPair(keypair){ 95 | const accountsToRemove = this.accounts.filter(x => x.keypairUnique === keypair.unique()).map(x => x.unique()); 96 | this.permissions = this.permissions.filter(x => !x.accounts.some(a => accountsToRemove.includes(a))); 97 | this.accounts = this.accounts.filter(x => x.keypairUnique !== keypair.unique()); 98 | this.keypairs = this.keypairs.filter(key => key.unique() !== keypair.unique()); 99 | this.correctHistories(); 100 | this.correctAppLinks(); 101 | } 102 | 103 | addAccount(account){ 104 | if(!this.accounts.find(a => a.unique() === account.unique())) 105 | this.accounts.push(account); 106 | } 107 | 108 | removeAccount(account){ 109 | const accountsToRemove = this.accounts.filter(x => x.unique() === account.unique()).map(x => x.unique()); 110 | this.permissions = this.permissions.filter(x => !x.accounts.some(a => accountsToRemove.includes(a))); 111 | this.accounts = this.accounts.filter(a => a.unique() !== account.unique()); 112 | this.correctHistories(); 113 | this.correctAppLinks(); 114 | } 115 | 116 | correctHistories(){ 117 | const keypairUniques = this.keypairs.map(x => x.unique()); 118 | const accountUniques = this.accounts.map(x => x.unique()); 119 | StoreService.get().state.history.map(x => { 120 | const acc = Account.fromJson(x.type === 'action' ? x.account : x.from); 121 | if(!keypairUniques.includes(acc.keypairUnique) || !accountUniques.includes(acc.unique())) { 122 | StoreService.get().dispatch(Actions.DELTA_HISTORY, x); 123 | } 124 | }); 125 | } 126 | 127 | correctAppLinks(){ 128 | const origins = this.permissions.map(x => x.origin); 129 | this.apps = this.apps.filter(x => origins.includes(x => x.origin)); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /packages/core/lib/models/Keypair.js: -------------------------------------------------------------------------------- 1 | import AES from 'aes-oop'; 2 | import {blockchainName, Blockchains} from './Blockchains'; 3 | import IdGenerator from '../util/IdGenerator'; 4 | import ExternalWallet from './hardware/ExternalWallet'; 5 | import StoreService from "../services/utility/StoreService"; 6 | 7 | export default class Keypair { 8 | 9 | constructor(blockchains = []){ 10 | this.id = IdGenerator.text(24); 11 | this.name = ''; 12 | this.privateKey = ''; 13 | 14 | this.external = null; 15 | this.fork = null; 16 | 17 | this.publicKeys = []; 18 | this.blockchains = blockchains; 19 | 20 | this.createdAt = +new Date(); 21 | } 22 | 23 | static placeholder(blockchains){ return new Keypair(blockchains); } 24 | static fromJson(json){ 25 | let p = Object.assign(this.placeholder(), json); 26 | if(json.hasOwnProperty('external') && !!json.external) p.external = ExternalWallet.fromJson(json.external); 27 | return p; 28 | } 29 | 30 | resetExternal(){ 31 | this.external.interface.close(); 32 | this.external.interface.open(); 33 | // this.external = ExternalWallet.fromJson(this.external); 34 | } 35 | 36 | accounts(unique = false){ 37 | const accounts = StoreService.get().state.scatter.keychain.accounts.filter(x => x.keypairUnique === this.unique()); 38 | if(!unique) return accounts; 39 | return accounts.reduce((acc, account) => { 40 | if(!acc.find(x => account.network().unique() === x.network().unique() 41 | && account.sendable() === x.sendable())) acc.push(account); 42 | return acc; 43 | }, []) 44 | 45 | } 46 | 47 | enabledKey(){ 48 | return this.publicKeys.find(x => x.blockchain === this.blockchains[0]); 49 | } 50 | 51 | isUnique(){ 52 | return !StoreService.get().state.scatter.keychain.keypairs.find(x => { 53 | return x.enabledKey().key === this.enabledKey().key 54 | }) 55 | } 56 | 57 | setName(){ 58 | this.name = `${blockchainName(this.enabledKey().blockchain)} Key - ${new Date().toDateString()} - ${IdGenerator.text(4)}` 59 | } 60 | 61 | unique(){ return this.id; } 62 | clone(){ return Keypair.fromJson(JSON.parse(JSON.stringify(this))) } 63 | 64 | /*** 65 | * Checks whether a private key is encrypted 66 | * @returns {boolean} 67 | */ 68 | isEncrypted(){ 69 | return typeof this.privateKey === 'string' && this.privateKey.length > 100; 70 | } 71 | 72 | /*** 73 | * Encrypts this Keypair's Private Key 74 | * @param seed - The seed to encrypt with 75 | */ 76 | encrypt(seed){ 77 | if(!this.isEncrypted()) 78 | this.privateKey = AES.encrypt(this.privateKey, seed); 79 | } 80 | 81 | /*** 82 | * Decrypts this Keypair's Private Key 83 | * @param seed - The seed to decrypt with 84 | */ 85 | decrypt(seed){ 86 | if(this.isEncrypted()) { 87 | this.privateKey = AES.decrypt(this.privateKey, seed); 88 | if(typeof this.privateKey === 'object' && this.privateKey.hasOwnProperty('data')) this.privateKey = this.privateKey.data; 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /packages/core/lib/models/Locale.js: -------------------------------------------------------------------------------- 1 | export default class Locale { 2 | 3 | constructor(){ 4 | this.raw = null; 5 | this.name = null; 6 | this.methods = {}; 7 | this.locales = {}; 8 | } 9 | static placeholder(){ return new Locale(); } 10 | static fromJson(json){ return Object.assign(this.placeholder(), json); } 11 | 12 | static fromRaw(raw){ 13 | if(!raw) return this.placeholder(); 14 | 15 | const p = this.placeholder(); 16 | p.raw = raw; 17 | p.name = raw.name; 18 | 19 | raw.methods.map(x => { 20 | p.methods[x.name] = new Function(x.args, x.body); 21 | }); 22 | 23 | Object.keys(raw.locales).map(key => { 24 | p.locales[key] = (x) => { 25 | const parseString = s => { 26 | s = s.replace('{x}', x); 27 | Object.keys(p.methods).map(method => s = s.replace(`{${method}}`, p.methods[method](x))); 28 | return s; 29 | }; 30 | 31 | if(typeof raw.locales[key] === 'string') return parseString(raw.locales[key]); 32 | else return raw.locales[key].map(x => parseString(x)); 33 | } 34 | }); 35 | 36 | return p; 37 | } 38 | 39 | parsed(){ 40 | return Locale.fromRaw(JSON.parse(this.raw)); 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /packages/core/lib/models/Meta.js: -------------------------------------------------------------------------------- 1 | import Framework from "../services/utility/Framework"; 2 | 3 | export default class Meta { 4 | 5 | constructor(){ 6 | this.version = Framework.getVersion(); 7 | this.lastVersion = Framework.getVersion(); 8 | this.acceptedTerms = false; 9 | this.lastSuggestedVersion = null; 10 | } 11 | 12 | getVersion(){ 13 | return Framework.getVersion() 14 | } 15 | 16 | regenerateVersion(){ 17 | this.version = Framework.getVersion(); 18 | } 19 | 20 | needsUpdating(){ 21 | return this.version !== this.lastVersion; 22 | } 23 | 24 | static placeholder(){ return new Meta(); } 25 | static fromJson(json){ return Object.assign(this.placeholder(), json); } 26 | } -------------------------------------------------------------------------------- /packages/core/lib/models/Network.js: -------------------------------------------------------------------------------- 1 | import {Blockchains, BlockchainsArray} from './Blockchains'; 2 | import IdGenerator from '../util/IdGenerator'; 3 | import Token from "./Token"; 4 | import PluginRepository from "../plugins/PluginRepository"; 5 | import StoreService from "../services/utility/StoreService"; 6 | 7 | export default class Network { 8 | constructor(_name = '', _protocol = 'https', _host = '', _port = 0, blockchain = null, chainId = '', _path = ''){ 9 | this.id = IdGenerator.numeric(12); 10 | this.name = _name; 11 | this.protocol = _protocol; 12 | this.host = _host; 13 | this.port = _port; 14 | this.path = _path; 15 | this.blockchain = blockchain; 16 | this.chainId = chainId.toString(); 17 | 18 | this.fromOrigin = null; 19 | this.createdAt = +new Date(); 20 | 21 | this.token = null; 22 | } 23 | 24 | static placeholder(){ return new Network(); } 25 | 26 | static fromJson(json){ 27 | const p = Object.assign(Network.placeholder(), json); 28 | p.chainId = p.chainId ? p.chainId.toString() : ''; 29 | p.token = json.hasOwnProperty('token') && json.token ? Token.fromJson(json.token) : null; 30 | return p; 31 | } 32 | 33 | static fromUnique(netString){ 34 | const blockchain = netString.split(':')[0]; 35 | if(netString.indexOf(':chain:') > -1) 36 | return new Network('', '', '','',blockchain, netString.replace(`${blockchain}:chain:`,'')); 37 | 38 | const splits = netString.replace(`${blockchain}:`, '').split(':'); 39 | return new Network('', '', splits[0], parseInt(splits[1] || 80), blockchain) 40 | } 41 | 42 | unique(){ return (`${this.blockchain}:` + (this.chainId.length ? `chain:${this.chainId}` : `${this.host}:${this.port}`)).toLowerCase(); } 43 | fullhost(){ return `${this.protocol}://${this.host}${this.port ? ':' : ''}${this.port}${this.path ? this.path : ''}` } 44 | clone(){ return Network.fromJson(JSON.parse(JSON.stringify(this))) } 45 | 46 | isValid(){ 47 | if(!BlockchainsArray.map(x => x.value).includes(this.blockchain)) return false; 48 | return this.host.length && this.port.toString().length && this.chainId.length 49 | } 50 | 51 | setPort(){ 52 | if(!this.port) this.port = 80; 53 | if(![80,443].includes(parseInt(this.port))) return; 54 | this.port = this.protocol === 'http' ? 80 : 443; 55 | } 56 | 57 | systemToken(){ 58 | if(this.token) return this.token; 59 | const token = PluginRepository.plugin(this.blockchain).defaultToken(); 60 | token.chainId = this.chainId; 61 | return token; 62 | } 63 | 64 | accounts(unique = false){ 65 | const accounts = StoreService.get().state.scatter.keychain.accounts.filter(x => x.networkUnique === this.unique()); 66 | if(!unique) return accounts; 67 | return accounts.reduce((acc, account) => { 68 | if(!acc.find(x => account.network().unique() === x.network().unique() 69 | && account.sendable() === x.sendable())) acc.push(account); 70 | return acc; 71 | }, []) 72 | 73 | } 74 | } -------------------------------------------------------------------------------- /packages/core/lib/models/Permission.js: -------------------------------------------------------------------------------- 1 | import Hasher from '../util/Hasher'; 2 | import IdGenerator from '../util/IdGenerator'; 3 | import StoreService from "../services/utility/StoreService"; 4 | import {IdentityRequiredFields} from "./Identity"; 5 | 6 | export default class Permission { 7 | 8 | constructor(){ 9 | this.id = IdGenerator.numeric(24); 10 | // Mandatory 11 | this.origin = ''; 12 | 13 | this.identity = ''; 14 | this.accounts = []; 15 | 16 | // Optional 17 | this.contract = null; 18 | this.contractHash = null; 19 | this.action = null; 20 | this.mutableActionFields = []; 21 | this.immutableActionFields = []; 22 | 23 | this.timestamp = 0; 24 | 25 | this.identityRequirements = []; 26 | 27 | this.isIdentity = false; 28 | this.isIdentityRequirements = false; 29 | this.isContractAction = false; 30 | } 31 | 32 | static placeholder(){ return new Permission(); } 33 | static fromJson(json){ return Object.assign(this.placeholder(), json); } 34 | clone(){ return Permission.fromJson(JSON.parse(JSON.stringify(this))) } 35 | 36 | static fromAction(origin, identity, accounts, added){ 37 | const base = Permission.fromJson({ 38 | origin, 39 | identity:identity.id, 40 | accounts:accounts.map(x => x.unique()) 41 | }); 42 | return Object.assign(base, added); 43 | } 44 | 45 | checksum(){ 46 | return Hasher.unsaltedQuickHash( 47 | this.origin+ 48 | this.identity+ 49 | (this.accounts||[]).join(',')+ 50 | this.contract+ 51 | this.contractHash+ 52 | this.action+ 53 | (this.identityRequirements||[]).join(',') 54 | ) 55 | } 56 | 57 | getIdentity(){ 58 | return StoreService.get().state.scatter.keychain.findIdentity(this.identity); 59 | } 60 | 61 | getAccounts(){ 62 | const accounts = StoreService.get().state.scatter.keychain.accounts; 63 | return this.accounts.map(unique => accounts.find(x => x.unique() === unique)); 64 | } 65 | 66 | isIdentityPermissionFor(origin){ 67 | return this.isIdentity && this.origin === origin; 68 | } 69 | 70 | static createImmutableFieldsHash(allFields, mutableFields){ 71 | return Hasher.unsaltedQuickHash(Object.keys(allFields).map(key => { 72 | if(!mutableFields.includes(key)) return allFields[key]; 73 | else return null; 74 | }).filter(x => x).sort().join(',')); 75 | } 76 | 77 | asIdentityRequirements(){ 78 | return IdentityRequiredFields.fromPermission(this.identityRequirements); 79 | } 80 | } -------------------------------------------------------------------------------- /packages/core/lib/models/Scatter.js: -------------------------------------------------------------------------------- 1 | import Meta from './Meta'; 2 | import Keychain from './Keychain'; 3 | import Settings from './Settings'; 4 | import AES from 'aes-oop'; 5 | import Hasher from '../util/Hasher' 6 | import IdGenerator from '../util/IdGenerator' 7 | import PluginRepository from "../plugins/PluginRepository"; 8 | import Identity, {LocationInformation} from "./Identity"; 9 | import Contact from "./Contact"; 10 | 11 | export default class Scatter { 12 | 13 | constructor(){ 14 | this.meta = Meta.placeholder(); 15 | this.keychain = Keychain.placeholder(); 16 | this.settings = Settings.placeholder(); 17 | this.contacts = []; 18 | this.hash = Hasher.unsaltedQuickHash(IdGenerator.text(2048)); 19 | 20 | this.onboarded = false; 21 | 22 | this.pin = null; 23 | this.pinForAll = false; 24 | } 25 | 26 | static async create(){ 27 | const scatter = new Scatter(); 28 | await Promise.all(PluginRepository.signatureProviders().map(async plugin => { 29 | const network = plugin.getEndorsedNetwork(); 30 | scatter.settings.networks.push(network); 31 | })); 32 | 33 | const firstIdentity = Identity.placeholder(); 34 | await firstIdentity.initialize(scatter.hash); 35 | 36 | firstIdentity.name = 'MyFirstIdentity'; 37 | scatter.keychain.locations = [LocationInformation.fromJson({name:'Home'})]; 38 | firstIdentity.location = scatter.keychain.locations[0]; 39 | 40 | scatter.keychain.updateOrPushIdentity(firstIdentity); 41 | 42 | return scatter; 43 | } 44 | static placeholder(){ return new Scatter(); } 45 | static fromJson(json){ 46 | let p = Object.assign(this.placeholder(), json); 47 | if(json.hasOwnProperty('meta')) p.meta = Meta.fromJson(json.meta); 48 | if(json.hasOwnProperty('settings')) p.settings = Settings.fromJson(json.settings); 49 | if(json.hasOwnProperty('keychain')) 50 | p.keychain = (typeof json.keychain === 'string') 51 | ? json.keychain : Keychain.fromJson(json.keychain); 52 | 53 | if(json.hasOwnProperty('contacts')) p.contacts = json.contacts.map(x => Contact.fromJson(x)); 54 | 55 | return p; 56 | } 57 | 58 | clone(){ return Scatter.fromJson(JSON.parse(JSON.stringify(this))) } 59 | 60 | isEncrypted(){ 61 | return typeof this.keychain !== 'object' 62 | } 63 | 64 | /*** 65 | * Encrypts the entire keychain 66 | * @param seed - The seed to encrypt with 67 | */ 68 | decrypt(seed){ 69 | if(this.isEncrypted()) this.keychain = Keychain.fromJson(AES.decrypt(this.keychain, seed)); 70 | } 71 | 72 | /*** 73 | * Decrypts the entire keychain 74 | * @param seed - The seed to decrypt with 75 | */ 76 | encrypt(seed){ 77 | if(!this.isEncrypted()) this.keychain = AES.encrypt(this.keychain, seed); 78 | } 79 | 80 | savable(seed){ 81 | // Encrypting in-place. 82 | this.keychain.cards.map(card => card.encrypt(seed)); 83 | this.keychain.keypairs.map(keypair => keypair.encrypt(seed)); 84 | this.keychain.identities.map(id => id.encrypt(seed)); 85 | 86 | // Encrypting clone 87 | const clone = this.clone(); 88 | clone.encrypt(seed); 89 | return clone; 90 | } 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | /*************************************/ 100 | /************ HELPERS *************/ 101 | /*************************************/ 102 | 103 | networkTokens(){ 104 | return this.settings.networks.map(x => { 105 | const token = x.systemToken(); 106 | token.chainId = x.chainId; 107 | return token; 108 | }).reduce((acc, token) => { 109 | const exists = acc.find(x => x.unique() === token.unique() && x.chainId === token.chainId); 110 | if(!exists) acc.push(token); 111 | return acc; 112 | }, []) 113 | } 114 | 115 | allTokens(){ 116 | return this.networkTokens().concat(this.settings.tokens); 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /packages/core/lib/models/Settings.js: -------------------------------------------------------------------------------- 1 | import Network from './Network'; 2 | import PluginRepository from '../plugins/PluginRepository'; 3 | import Token from "./Token"; 4 | import Explorer from "./Explorer"; 5 | 6 | export const BACKUP_STRATEGIES = { 7 | MANUAL:'manual', 8 | AUTOMATIC:'auto' 9 | } 10 | 11 | export const SETTINGS_OPTIONS = { 12 | GENERAL:{ locked:false, name:'General' }, 13 | TOKENS:{ locked:false, name:'Tokens' }, 14 | EXPLORER:{ locked:false, name:'Explorers' }, 15 | BACKUP:{ locked:false, name:'Backup' }, 16 | FIREWALL:{ locked:true, name:'Firewall' }, 17 | PASSWORD:{ locked:true, name:'Password' }, 18 | DESTROY:{ locked:true, name:'Destroy' }, 19 | }; 20 | 21 | export default class Settings { 22 | 23 | constructor(){ 24 | this.networks = []; 25 | this.language = 'English'; 26 | this.autoBackup = BACKUP_STRATEGIES.AUTOMATIC; 27 | this.backupLocation = ''; 28 | this.explorers = PluginRepository.defaultExplorers(); 29 | this.showNotifications = true; 30 | 31 | // Tokens 32 | this.showMainnetsOnly = true; 33 | this.displayToken = null; 34 | this.displayCurrency = 'USD'; 35 | this.tokens = []; 36 | this.blacklistTokens = []; 37 | 38 | // {contract:[actions]} 39 | this.blacklistActions = { 40 | 'eos::eosio':['updateauth', 'linkauth'], 41 | 'eos::eosio.msig':['approve'], 42 | }; 43 | 44 | this.balanceFilters = {}; 45 | this.hideMainBalance = false; 46 | 47 | this.simpleMode = false; 48 | } 49 | 50 | static placeholder(){ return new Settings(); } 51 | static fromJson(json){ 52 | let p = Object.assign(this.placeholder(), json); 53 | if(json.hasOwnProperty('networks')) p.networks = json.networks.map(x => Network.fromJson(x)); 54 | if(json.hasOwnProperty('tokens')) p.tokens = json.tokens.map(x => Token.fromJson(x)); 55 | if(json.hasOwnProperty('blacklistTokens')) p.blacklistTokens = json.blacklistTokens.map(x => Token.fromJson(x)); 56 | if(json.hasOwnProperty('explorers')) p.explorers = Object.keys(json.explorers).reduce((acc, blockchain) => { 57 | acc[blockchain] = Explorer.fromRaw(json.explorers[blockchain].raw); 58 | return acc; 59 | }, {}); 60 | return p; 61 | } 62 | 63 | updateOrPushNetwork(network){ 64 | this.networks.find(n => n.id === network.id) 65 | ? this.networks = this.networks.map(n => n.id === network.id ? network : n) 66 | : this.networks.unshift(network); 67 | } 68 | 69 | removeNetwork(network){ 70 | this.networks = this.networks.filter(n => n.id !== network.id); 71 | } 72 | 73 | blacklistAction(blockchain, contract, action){ 74 | if(!contract.length || !action.length) return; 75 | if(!this.blacklistActions.hasOwnProperty(`${blockchain}::${contract}`)){ 76 | this.blacklistActions[`${blockchain}::${contract}`] = [] 77 | } 78 | 79 | this.blacklistActions[`${blockchain}::${contract}`].push(action); 80 | } 81 | 82 | removeBlacklistedAction(blockchain, contract, action){ 83 | if(!this.blacklistActions.hasOwnProperty(`${blockchain}::${contract}`)) return; 84 | if(!this.blacklistActions[`${blockchain}::${contract}`].includes(action)) return; 85 | if(this.blacklistActions[`${blockchain}::${contract}`].length === 1) return delete this.blacklistActions[`${blockchain}::${contract}`]; 86 | this.blacklistActions[`${blockchain}::${contract}`] = this.blacklistActions[`${blockchain}::${contract}`].filter(x => x !== action); 87 | } 88 | 89 | isActionBlacklisted(actionTag){ 90 | const [blockchain, contract, action] = actionTag.split('::'); 91 | return this.blacklistActions.hasOwnProperty(`${blockchain}::${contract}`) 92 | && this.blacklistActions[`${blockchain}::${contract}`].includes(action); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /packages/core/lib/models/Token.js: -------------------------------------------------------------------------------- 1 | import IdGenerator from "../util/IdGenerator"; 2 | import PluginRepository from "../plugins/PluginRepository"; 3 | import {Blockchains, BlockchainsArray} from "./Blockchains"; 4 | import StoreService from "../services/utility/StoreService"; 5 | import BalanceService from "../services/blockchain/BalanceService"; 6 | 7 | export default class Token { 8 | 9 | constructor(blockchain = null, contract = '', symbol = '', name = null, decimals = null, chainId = ''){ 10 | this.id = IdGenerator.text(24); 11 | this.blockchain = blockchain; 12 | this.contract = contract; 13 | this.symbol = symbol; 14 | this.name = name ? name : symbol; 15 | this.decimals = decimals ? decimals : 2; 16 | 17 | this.amount = 0; 18 | 19 | this.chainId = chainId; 20 | this.unusable = null; 21 | 22 | this.fromOrigin = ''; 23 | this.createdAt = +new Date(); 24 | } 25 | 26 | isValid(){ 27 | if(Object.keys(this).length !== 11) return false; 28 | return BlockchainsArray.map(x => x.value).includes(this.blockchain) && 29 | this.contract.length && 30 | this.symbol.length && 31 | this.name.length && 32 | this.decimals.toString().length && 33 | this.chainId.length 34 | } 35 | 36 | static placeholder(){ return new Token(); } 37 | static fromJson(json){ 38 | const p = Object.assign(this.placeholder(), json); 39 | if(!json.hasOwnProperty('name') || !json.name.length) p.name = json.symbol; 40 | return p; 41 | } 42 | static fromUnique(unique){ 43 | const p = this.placeholder(); 44 | const [blockchain, contract, symbol, chainId] = unique.split(':'); 45 | p.blockchain = blockchain; 46 | p.contract = contract; 47 | p.symbol = symbol ? symbol.toUpperCase() : 'INVALID_TOKEN'; 48 | p.chainId = chainId; 49 | p.decimals = PluginRepository.plugin(blockchain).defaultDecimals(); 50 | return p; 51 | } 52 | 53 | clone(){ return Token.fromJson(JSON.parse(JSON.stringify(this))) } 54 | 55 | unique(){ return `${this.blockchain}:${this.contract.toLowerCase()}:${this.symbol.toLowerCase()}` } 56 | uniqueWithChain(includeUnusable = true){ return `${this.blockchain}:${this.contract.toLowerCase()}:${this.symbol.toLowerCase()}:${this.chainId}${includeUnusable && this.unusable ? `:${this.unusable}` : ''}` } 57 | identifiable(){ return `${this.blockchain}:${this.contract.toLowerCase()}` } 58 | 59 | add(quantity){ 60 | this.amount = (parseFloat(this.amount) + parseFloat(quantity)).toFixed(this.decimals); 61 | } 62 | 63 | network(){ 64 | const networks = StoreService.get().state.scatter.settings.networks; 65 | const endorsed = networks.find(x => x.unique() === PluginRepository.plugin(this.blockchain).getEndorsedNetwork().unique()); 66 | if(!this.chainId || !this.chainId.length) return endorsed; 67 | return networks.find(x => x.blockchain === this.blockchain && x.chainId === this.chainId) || endorsed; 68 | } 69 | 70 | formatted(){ 71 | return `${this.amount} ${this.symbol}`; 72 | } 73 | 74 | fiatBalance(withSymbol = true, price = null){ 75 | const unusableReplacement = this.uniqueWithChain().replace(`:${this.unusable}`, ''); 76 | if(StoreService.get().state.prices.hasOwnProperty(this.uniqueWithChain())){ 77 | price = price ? price : parseFloat(StoreService.get().state.prices[this.uniqueWithChain()][StoreService.get().state.scatter.settings.displayCurrency]); 78 | return `${parseFloat(price * parseFloat(this.amount)).toFixed(4)} ${withSymbol ? StoreService.get().state.scatter.settings.displayCurrency : ''}`; 79 | } 80 | else if(this.unusable && StoreService.get().state.prices.hasOwnProperty(unusableReplacement)){ 81 | price = price ? price : parseFloat(StoreService.get().state.prices[unusableReplacement][StoreService.get().state.scatter.settings.displayCurrency]); 82 | return `${parseFloat(price * parseFloat(this.amount)).toFixed(4)} ${withSymbol ? StoreService.get().state.scatter.settings.displayCurrency : ''}`; 83 | } 84 | 85 | else { 86 | return null; 87 | } 88 | } 89 | 90 | fiatPrice(withSymbol = true){ 91 | if(StoreService.get().state.prices.hasOwnProperty(this.uniqueWithChain())){ 92 | const price = parseFloat(StoreService.get().state.prices[this.uniqueWithChain()][StoreService.get().state.scatter.settings.displayCurrency]); 93 | return `${parseFloat(price).toFixed(4)} ${withSymbol ? StoreService.get().state.scatter.settings.displayCurrency : ''}` 94 | } else { 95 | return null; 96 | } 97 | } 98 | 99 | baseTokenPrice(withSymbol = true){ 100 | if(StoreService.get().state.prices.hasOwnProperty(this.uniqueWithChain())){ 101 | const systemToken = this.network().systemToken(); 102 | if(this.uniqueWithChain(false) === systemToken.uniqueWithChain(false)) return null; 103 | const baseTokenPrice = parseFloat(StoreService.get().state.prices[systemToken.uniqueWithChain()][StoreService.get().state.scatter.settings.displayCurrency]); 104 | const price = parseFloat(StoreService.get().state.prices[this.uniqueWithChain()][StoreService.get().state.scatter.settings.displayCurrency]); 105 | return `${parseFloat(price/baseTokenPrice).toFixed(10)} ${withSymbol ? systemToken.symbol : ''}` 106 | } else { 107 | return null; 108 | } 109 | } 110 | 111 | totalBalance(){ 112 | if(BalanceService.totalBalances().totals.hasOwnProperty(this.uniqueWithChain())){ 113 | return BalanceService.totalBalances().totals[this.uniqueWithChain()]; 114 | } else { 115 | return null; 116 | } 117 | } 118 | 119 | symbolClass(){ 120 | const iconSearch = `${this.blockchain}-${this.symbol}`.toLowerCase(); 121 | const icons = ['eth-tusd', 'btc-btc', 'eos-eos', 'eth-dai', 'trx-trx', 'eth-eth', 'fio-fio']; 122 | return icons.includes(iconSearch) ? `token-icon token-${iconSearch}` : null; 123 | } 124 | 125 | truncatedSymbol(){ 126 | return this.symbol.length > 4 ? this.symbol.substr(0,4) : this.symbol 127 | } 128 | 129 | accounts(){ 130 | return StoreService.get().state.scatter.keychain.accounts.filter(x => x.blockchain() === this.blockchain && x.network().chainId === this.chainId).reduce((acc,x) => { 131 | if(!acc.find(y => y.sendable() === x.sendable())) acc.push(x); 132 | return acc; 133 | }, []); 134 | 135 | // Problem with doing this is that if the balance checks fail then accounts never show up. 136 | // const state = StoreService.get().state; 137 | // if(!state.balances) return []; 138 | // return Object.keys(state.balances).reduce((acc,accountUnique) => { 139 | // if(state.balances[accountUnique].find(token => token.uniqueWithChain() === this.uniqueWithChain())){ 140 | // if(!acc.find(x => x.identifiable() === accountUnique)){ 141 | // acc.push(state.scatter.keychain.accounts.find(x => x.identifiable() === accountUnique)); 142 | // } 143 | // } 144 | // return acc; 145 | // }, []); 146 | } 147 | 148 | static sorter(a,b){ 149 | const untouchable = !!b.unusable ? 1 : !!a.unusable ? -1 : 0; 150 | const systemTokenUniques = StoreService.get().state.scatter.networkTokens().map(x => x.uniqueWithChain(false)); 151 | const isSelfSystem = systemTokenUniques.includes(b.uniqueWithChain(false)) ? 1 : systemTokenUniques.includes(a.uniqueWithChain(false)) ? -1 : 0; 152 | return isSelfSystem || untouchable || (b.fiatBalance(false) || 0) - (a.fiatBalance(false) || 0); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /packages/core/lib/models/api/ApiActions.js: -------------------------------------------------------------------------------- 1 | export const GET_VERSION = 'getVersion'; 2 | export const GET_PUBLIC_KEY = 'getPublicKey'; 3 | export const LINK_ACCOUNT = 'linkAccount'; 4 | export const HAS_ACCOUNT_FOR = 'hasAccountFor'; 5 | export const LOGIN = 'getOrRequestIdentity'; 6 | export const LOGIN_ALL = 'getAllAccountsFor'; 7 | export const GET_AVATAR = 'getAvatar'; 8 | export const IDENTITY_FROM_PERMISSIONS = 'identityFromPermissions'; 9 | export const LOGOUT = 'forgetIdentity'; 10 | export const TRANSFER = 'requestTransfer'; 11 | export const SIGN = 'requestSignature'; 12 | export const ADD_TOKEN = 'addToken'; 13 | export const SIGN_ARBITRARY = 'requestArbitrarySignature'; 14 | export const SUGGEST_NETWORK = 'requestAddNetwork'; 15 | export const AUTHENTICATE = 'authenticate'; 16 | export const UPDATE_IDENTITY = 'updateIdentity'; 17 | export const CREATE_ENCRYPTION_KEY = 'createEncryptionKey'; 18 | -------------------------------------------------------------------------------- /packages/core/lib/models/api/index.js: -------------------------------------------------------------------------------- 1 | import * as ApiActions from './ApiActions'; 2 | 3 | export default {ApiActions}; -------------------------------------------------------------------------------- /packages/core/lib/models/errors/Error.js: -------------------------------------------------------------------------------- 1 | import * as ErrorTypes from './ErrorTypes' 2 | 3 | export const ErrorCodes = { 4 | NO_SIGNATURE:402, 5 | FORBIDDEN:403, 6 | TIMED_OUT:408, 7 | LOCKED:423, 8 | UPGRADE_REQUIRED:426, 9 | TOO_MANY_REQUESTS:429 10 | }; 11 | 12 | export default class Error { 13 | 14 | constructor(_type, _message, _code = ErrorCodes.LOCKED){ 15 | this.type = _type; 16 | this.message = _message; 17 | this.code = _code; 18 | this.isError = true; 19 | } 20 | 21 | static locked(){ 22 | return new Error(ErrorTypes.LOCKED, "The user's Scatter is locked. They have been notified and should unlock before continuing.") 23 | } 24 | 25 | static signatureError(_type, _message){ 26 | return new Error(_type, _message, ErrorCodes.NO_SIGNATURE) 27 | } 28 | 29 | static malicious(_message){ 30 | return new Error(ErrorTypes.MALICIOUS, _message, ErrorCodes.FORBIDDEN) 31 | } 32 | 33 | static rejected(){ 34 | return new Error(ErrorTypes.REJECTED, 'The user rejected the request.', ErrorCodes.NO_SIGNATURE) 35 | } 36 | 37 | static identityMissing(){ 38 | return this.signatureError("identity_missing", "Identity no longer exists on the user's keychain or user is not logged in."); 39 | } 40 | 41 | static badNetwork(){ 42 | return this.signatureError("bad_network", "The network you provided is malformed."); 43 | } 44 | 45 | static noKeypair(){ 46 | return this.signatureError("no_keypair", "The public key you provided does not exist on the user's keychain."); 47 | } 48 | 49 | static signatureAccountMissing(){ 50 | return this.signatureError("account_missing", "You are trying to sign a request with an account that isn't currently linked or doesn't exist in the user's Scatter"); 51 | } 52 | 53 | static sharedSecretNotAvailable(){ 54 | return this.signatureError("no_shared_secret", "The blockchain you want to use to create a shared secret does not support creating them."); 55 | } 56 | 57 | static cantParseTransaction(){ 58 | return this.signatureError("parsing_error", "Something happened while trying to parse the transaction internally."); 59 | } 60 | 61 | static noNetwork(){ 62 | return this.signatureError("no_network", "This user does not have this network in their Scatter."); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /packages/core/lib/models/errors/ErrorTypes.js: -------------------------------------------------------------------------------- 1 | export const MALICIOUS = 'malicious'; 2 | export const LOCKED = 'locked'; 3 | export const PROMPT_CLOSED = 'prompt_closed'; 4 | export const UPGRADE_REQUIRED = 'upgrade_required'; 5 | export const REJECTED = 'rejected'; 6 | -------------------------------------------------------------------------------- /packages/core/lib/models/errors/index.js: -------------------------------------------------------------------------------- 1 | import Error from './Error'; 2 | import * as ErrorTypes from './ErrorTypes'; 3 | 4 | export default {Error, ErrorTypes}; -------------------------------------------------------------------------------- /packages/core/lib/models/hardware/ExternalWallet.js: -------------------------------------------------------------------------------- 1 | import IdGenerator from '../../util/IdGenerator'; 2 | 3 | export let EXT_WALLET_TYPES = {}; 4 | 5 | // Format [ {type,name,wallet}, {type,name,wallet} ] 6 | let WALLETS = []; 7 | 8 | export default class ExternalWallet { 9 | 10 | static loadWallets(_wallets){ 11 | EXT_WALLET_TYPES = _wallets.map(x => ({[x.type]:x.name})); 12 | WALLETS = _wallets; 13 | } 14 | 15 | constructor(_type = null, _blockchain = null){ 16 | this.id = IdGenerator.text(64); 17 | this.type = _type; 18 | this.blockchain = _blockchain; 19 | this.addressIndex = 0; 20 | } 21 | 22 | static placeholder(){ return new ExternalWallet(); } 23 | static fromJson(json){ 24 | return Object.assign(this.placeholder(), json); 25 | } 26 | 27 | setup(){ 28 | this.interface = getInterface(this.type, this.blockchain); 29 | } 30 | } 31 | 32 | const getInterface = (type, blockchain) => { 33 | if(EXT_WALLET_TYPES.hasOwnProperty(type)) return WALLETS[type].wallet.typeToInterface(blockchain); 34 | return console.error('Type not defined in hardware wallets'); 35 | } 36 | 37 | export class ExternalWalletInterface { 38 | 39 | constructor(handler){ 40 | this.handler = handler; 41 | } 42 | 43 | async open(){ 44 | return await this.handler.open(); 45 | } 46 | 47 | async close(){ 48 | return await this.handler.close(); 49 | } 50 | 51 | async canConnect(){ 52 | return await this.handler.canConnect(); 53 | } 54 | 55 | async sign(publicKey, transaction, abi, network){ 56 | return await this.handler.sign(publicKey, transaction, abi, network); 57 | } 58 | 59 | async getPublicKey(){ 60 | return await this.handler.getPublicKey(); 61 | } 62 | 63 | setAddressIndex(path){ 64 | return this.handler.setAddressIndex(path); 65 | } 66 | 67 | availableBlockchains(){ 68 | return this.handler.availableBlockchains(); 69 | } 70 | 71 | } 72 | 73 | -------------------------------------------------------------------------------- /packages/core/lib/models/hardware/index.js: -------------------------------------------------------------------------------- 1 | import ExternalWallet from './ExternalWallet'; 2 | 3 | export default {ExternalWallet}; -------------------------------------------------------------------------------- /packages/core/lib/models/histories/HistoricAction.js: -------------------------------------------------------------------------------- 1 | import History, {HISTORY_TYPES} from "./History"; 2 | import Account from "../Account"; 3 | 4 | export default class HistoricAction extends History { 5 | 6 | 7 | constructor(account, action, txid = ''){ 8 | super(HISTORY_TYPES.Action, txid); 9 | this.account = account instanceof Account ? account.unique() : account; 10 | this.action = action; 11 | } 12 | 13 | clone(){ return HistoricAction.fromJson(JSON.parse(JSON.stringify(this))) } 14 | static placeholder(){ return new HistoricAction(); } 15 | static fromJson(json){ return Object.assign(this.placeholder(), json); } 16 | 17 | } -------------------------------------------------------------------------------- /packages/core/lib/models/histories/HistoricExchange.js: -------------------------------------------------------------------------------- 1 | import History, {HISTORY_TYPES} from "./History"; 2 | import Account from "../Account"; 3 | import Token from "../Token"; 4 | 5 | export default class HistoricExchange extends History { 6 | 7 | 8 | constructor(from, to, fromToken, toToken, orderDetails, txid = ''){ 9 | super(HISTORY_TYPES.Exchange, txid); 10 | this.from = from; 11 | this.to = to; 12 | this.fromToken = fromToken; 13 | this.toToken = toToken; 14 | this.orderDetails = orderDetails; 15 | this.status = 'pending'; 16 | } 17 | 18 | static placeholder(){ return new HistoricExchange(); } 19 | static fromJson(json){ 20 | let p = Object.assign(this.placeholder(), json); 21 | p.from = Account.fromJson(json.from); 22 | p.fromToken = Token.fromJson(json.fromToken); 23 | p.toToken = Token.fromJson(json.toToken); 24 | return p; 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /packages/core/lib/models/histories/HistoricTransfer.js: -------------------------------------------------------------------------------- 1 | import History, {HISTORY_TYPES} from "./History"; 2 | import Account from "../Account"; 3 | import Token from "../Token"; 4 | 5 | export default class HistoricTransfer extends History { 6 | 7 | 8 | constructor(from, to, token, amount, memo = null, txid = ''){ 9 | super(HISTORY_TYPES.Transfer, txid); 10 | this.from = from; 11 | this.to = to; 12 | this.token = token; 13 | this.amount = amount; 14 | this.memo = memo; 15 | } 16 | 17 | static placeholder(){ return new HistoricTransfer(); } 18 | static fromJson(json){ 19 | let p = Object.assign(this.placeholder(), json); 20 | p.from = Account.fromJson(json.from); 21 | p.token = Token.fromJson(json.token); 22 | return p; 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /packages/core/lib/models/histories/History.js: -------------------------------------------------------------------------------- 1 | import IdGenerator from "../../util/IdGenerator"; 2 | 3 | export const HISTORY_TYPES = { 4 | Transfer:'transfer', 5 | Exchange:'exchange', 6 | Action:'action' 7 | }; 8 | 9 | export default class History { 10 | 11 | constructor(type, txid = ''){ 12 | this.id = IdGenerator.text(24); 13 | this.type = type; 14 | this.timestamp = +new Date(); 15 | this.txid = typeof txid === 'string' ? txid : ''; 16 | } 17 | } -------------------------------------------------------------------------------- /packages/core/lib/models/histories/index.js: -------------------------------------------------------------------------------- 1 | import HistoricAction from './HistoricAction'; 2 | import HistoricExchange from './HistoricExchange'; 3 | import HistoricTransfer from './HistoricTransfer'; 4 | import History from './History'; 5 | 6 | export default {HistoricAction, HistoricExchange, HistoricTransfer, History}; -------------------------------------------------------------------------------- /packages/core/lib/models/index.js: -------------------------------------------------------------------------------- 1 | import api from './api/index'; 2 | import hardware from './hardware/index'; 3 | import histories from './histories/index'; 4 | 5 | import Account from './Account'; 6 | import AccountAction from './AccountAction'; 7 | import AuthorizedApp from './AuthorizedApp'; 8 | import * as Blockchains from './Blockchains'; 9 | import Contact from './Contact'; 10 | import CreditCard from './CreditCard'; 11 | import Explorer from './Explorer'; 12 | import Identity from './Identity'; 13 | import Keychain from './Keychain'; 14 | import Keypair from './Keypair'; 15 | import Locale from './Locale'; 16 | import Meta from './Meta'; 17 | import Network from './Network'; 18 | import Permission from './Permission'; 19 | import Scatter from './Scatter'; 20 | import Settings from './Settings'; 21 | import Token from './Token'; 22 | 23 | export default { 24 | api, 25 | hardware, 26 | histories, 27 | Account, 28 | AccountAction, 29 | AuthorizedApp, 30 | Blockchains, 31 | Contact, 32 | CreditCard, 33 | Explorer, 34 | Identity, 35 | Keychain, 36 | Keypair, 37 | Locale, 38 | Meta, 39 | Network, 40 | Permission, 41 | Scatter, 42 | Settings, 43 | Token, 44 | } -------------------------------------------------------------------------------- /packages/core/lib/plugins/Plugin.js: -------------------------------------------------------------------------------- 1 | 2 | export default class Plugin { 3 | 4 | constructor(_name = '', _type = ''){ 5 | this.name = _name; 6 | this.type = _type; 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /packages/core/lib/plugins/PluginRepository.js: -------------------------------------------------------------------------------- 1 | import * as PluginTypes from './PluginTypes'; 2 | import {Blockchains, BlockchainsArray} from '../models/Blockchains'; 3 | import Explorer from "../models/Explorer"; 4 | 5 | class PluginRepositorySingleton { 6 | 7 | constructor(){ 8 | this.plugins = []; 9 | } 10 | 11 | loadPlugins(plugins){ 12 | plugins.map(plugin => this.plugins.push(new plugin)); 13 | } 14 | 15 | signatureProviders(){ 16 | return this.plugins.filter(plugin => plugin.type === PluginTypes.BLOCKCHAIN_SUPPORT); 17 | } 18 | 19 | plugin(name){ 20 | return this.plugins.find(plugin => plugin.name === name); 21 | } 22 | 23 | defaultExplorers(){ 24 | return BlockchainsArray.reduce((acc,x) => { 25 | if(this.plugin(x.value)) { 26 | acc[x.value] = Explorer.fromJson({ 27 | raw:this.plugin(x.value).defaultExplorer() 28 | }); 29 | } 30 | return acc; 31 | }, {}) 32 | } 33 | 34 | bustCaches(){ 35 | this.signatureProviders().map(sp => sp.bustCache()) 36 | } 37 | } 38 | 39 | const PluginRepository = new PluginRepositorySingleton(); 40 | export default PluginRepository; 41 | -------------------------------------------------------------------------------- /packages/core/lib/plugins/PluginTypes.js: -------------------------------------------------------------------------------- 1 | export const BLOCKCHAIN_SUPPORT = 'blockchain_support'; -------------------------------------------------------------------------------- /packages/core/lib/plugins/defaults/index.js: -------------------------------------------------------------------------------- 1 | import pluginInterface from './interface'; 2 | 3 | export default { 4 | pluginInterface, 5 | } -------------------------------------------------------------------------------- /packages/core/lib/plugins/defaults/interface.js: -------------------------------------------------------------------------------- 1 | import {Blockchains} from "../../models/Blockchains"; 2 | import * as PluginTypes from "../PluginTypes"; 3 | import Plugin from "../Plugin"; 4 | 5 | /*** 6 | * DO NOT INCLUDE 7 | * This is just an interface for quickly raising 8 | * new Scatter blockchain plugins 9 | */ 10 | export default class PluginInterface extends Plugin { 11 | constructor(){ super('blockchain_type', PluginTypes.BLOCKCHAIN_SUPPORT) } 12 | 13 | /*** 14 | * BIP is the path on a device such as a ledger (HD wallet). 15 | * EXAMPLE: return `44'/0'/0'/0/` 16 | */ 17 | bip(){} 18 | 19 | /*** 20 | * If there is an internal cache, this method being called should clear it. 21 | */ 22 | bustCache(){} 23 | 24 | /*** 25 | * Explorer is an object that points to a web-interface block explorer. 26 | * {x} is used as a placeholder for an account/txid/block_id. 27 | * EXAMPLE: 28 | { 29 | "name":"EXPLORER NAME", 30 | "account":"https://explorer.com/account/{x}", 31 | "transaction":"https://explorer.com/transaction/{x}", 32 | "block":"https://explorer.com/block/{x}" 33 | } 34 | */ 35 | defaultExplorer(){} 36 | 37 | /*** 38 | * Account formatter turns an `Account` model into a string which is used as the recipient of transactions. 39 | * For instance in EOSIO blockchains the formatter would return `account.name`, but in Ethereum blockchains it would return 40 | * `account.publicKey` which denotes the address instead of a name. 41 | */ 42 | accountFormatter(account){} 43 | 44 | /*** 45 | * Returnable account is a POJO that is returned to interacting applications. 46 | * For instance, in EOSIO blockchains a name is required, however in Ethereum blockchains only a publicKey/address is required. 47 | */ 48 | returnableAccount(account){} 49 | 50 | /*** 51 | * This is a UI helper which defines what a placeholder value for a contract might be. 52 | * For instance in Ethereum blockchains it might be `0x...`, while in EOSIO blockchains it might be `eosio.token` 53 | */ 54 | contractPlaceholder(){} 55 | 56 | /*** 57 | * Check network simply checks the availability/connectivity of a `Network`. 58 | * This should either resolve or timeout after 2-4 seconds. 59 | */ 60 | checkNetwork(network){} 61 | 62 | /*** 63 | * An endorsed network is simply a "default" network hardcoded into the plugin, providing an absolute fallback 64 | * for a node connection. 65 | * THIS MUST RETURN A NETWORK CLASS 66 | * EXAMPLE: 67 | return new Network('EOS Mainnet', 'https', 'nodes.get-scatter.com', 443, Blockchains.EOSIO, MAINNET_CHAIN_ID) 68 | */ 69 | getEndorsedNetwork(){} 70 | 71 | /*** 72 | * Checks if a given network is the endorsed network (or a network matching the chainID) 73 | * EXAMPLE: 74 | return network.blockchain === Blockchains.EOSIO && network.chainId === MAINNET_CHAIN_ID; 75 | */ 76 | isEndorsedNetwork(network){} 77 | 78 | /*** 79 | * Fetches the chainID from the network (live) if available. 80 | */ 81 | async getChainId(network){} 82 | 83 | /*** 84 | * If specialized actions are required by the blockchain (like key management) this 85 | * should return true, otherwise false. 86 | * In the case of `true`, the plugin must also include an `accountActions` method 87 | */ 88 | hasAccountActions(){} 89 | 90 | // OPTIONAL, this is only required if `hasAccountActions` is `true`. 91 | // accountActions(account, callback){ 92 | // return [ 93 | // new AccountAction("unlink_account", () => callback(account)), 94 | // new AccountAction("change_permissions", () => callback(account), true), 95 | // ]; 96 | // } 97 | 98 | /*** 99 | * This might need to be re-designed to be dynamic, however this designates a 100 | * blockchain as using resources like CPU/NET/RAM on EOSIO blockchains. 101 | * If this is true, then `getResourcesFor(account)`, `needsResources(account)` and `addResources(account)` must also be included. 102 | * For examples check the EOSIO plugin. 103 | */ 104 | usesResources(){ return false; } 105 | 106 | /*** 107 | * Accounts are sometimes required to be created or imported before being available (such is the case in EOSIO blockchains). 108 | * If this is set to false, a dummy account will always be created using the publicKey/address. If not, then an account creation 109 | * process will need to be created on the front-end which will require UI work. 110 | */ 111 | accountsAreImported(){ return false; } 112 | 113 | /*** 114 | * Should check whether a string account_name/address is a valid recipient. 115 | */ 116 | isValidRecipient(name){} 117 | 118 | /*** 119 | * Converts a private key to a public key 120 | */ 121 | privateToPublic(privateKey){} 122 | 123 | /*** 124 | * Checks whether a private key is valid 125 | */ 126 | validPrivateKey(privateKey){} 127 | 128 | /*** 129 | * Checks whether a public key is valid 130 | */ 131 | validPublicKey(publicKey){} 132 | 133 | /*** 134 | * Generates a random private key 135 | * NOTE: This isn't used in Scatter, as we use buffer keys generated elsewhere. 136 | */ 137 | randomPrivateKey(){} 138 | 139 | /*** 140 | * Converts a byte buffer into a hex private key. 141 | */ 142 | bufferToHexPrivate(buffer){} 143 | 144 | /*** 145 | * Converts a hex private key into a byte buffer. 146 | */ 147 | hexPrivateToBuffer(privateKey){} 148 | 149 | /*** 150 | * Takes a transaction payload and returns a flat array of participants. 151 | * EXAMPLES: 152 | * EOSIO: ['testaccount@active'] 153 | * ETHEREUM: ['0x....'] 154 | */ 155 | actionParticipants(payload){} 156 | 157 | /*** 158 | * Untouchable tokens are things like staked tokens on the system level. 159 | * If a blockchain has untouchable (un-usable/un-transferable) tokens, then a `async untouchableBalance(account)` method 160 | * must also be provided which returns an array of `Token` class. 161 | */ 162 | hasUntouchableTokens(){ return false; } 163 | 164 | /*** 165 | * Gets a single token's balance. 166 | * Returns a Token class where `token.amount` is the balance. 167 | */ 168 | async balanceFor(account, token){} 169 | 170 | /*** 171 | * Gets an array of token's values. 172 | * The `tokens` param might also be omitted which would mean to grab "all available tokens for an account". 173 | * Returns an array of Token class. 174 | */ 175 | async balancesFor(account, tokens = null){} 176 | 177 | /*** 178 | * The default decimal count for tokens on this blockchain. 179 | * Returns an integer. 180 | */ 181 | defaultDecimals(){} 182 | 183 | /*** 184 | * The default token for this blockchain. 185 | * Returns a Token class. 186 | */ 187 | defaultToken(){} 188 | 189 | /*** 190 | * This is usually used internally inside of your walletpack plugin. 191 | * Simply takes a payload and converts it into a request that signing popups understand. 192 | * Check one of the existing plugins for the structures. 193 | * EXAMPLE: 194 | payload.messages = [...]; 195 | payload.identityKey = StoreService.get().state.scatter.keychain.identities[0].publicKey; 196 | payload.participants = [account]; 197 | payload.network = account.network(); 198 | payload.origin = 'Scatter'; 199 | const request = { 200 | payload, 201 | origin:payload.origin, 202 | blockchain:'YOUR BLOCKCHAIN', 203 | requiredFields:{}, 204 | type:Actions.SIGN, 205 | id:1, 206 | }; 207 | */ 208 | async signerWithPopup(payload, account, rejector){} 209 | 210 | /*** 211 | * Creates a token transfer. Should use the signerWithPopup above to 212 | * create a popup which the users has to sign. 213 | * This might also be requested to be bypassed sometimes with the `prompForSignature = false` flag param. 214 | */ 215 | async transfer({account, to, amount, contract, symbol, memo, promptForSignature = true}){} 216 | 217 | /*** 218 | * Does the actual signing of a transaction. 219 | * The `payload` will vary based on blockchain as it comes directly from their own libraries. 220 | * The goal of this method is to turn a payload into a signed transaction though, so if that works in tests then 221 | * it will work perfectly elsewhere. 222 | * Notes: 223 | * `arbitrary` and `isHash`: Sometimes blockchains change signing types based on whether a signature is signing a string (arbitrary) or a hash. 224 | */ 225 | async signer(payload, publicKey, arbitrary = false, isHash = false, privateKey = null){ 226 | // IMPORTANT: This method should always start with these calls. 227 | // The `privateKey` is not ensured to be passed into this method. 228 | //if(!privateKey) privateKey = await KeyPairService.publicToPrivate(publicKey); 229 | //if (!privateKey) return; 230 | } 231 | 232 | /*** 233 | * The goal of this method is to parse a `payload` into something which UI's understand. 234 | * Payload will differ based on blockchain, as it depends on the blockchain's actual javascript library. 235 | * The `abi` parameter is needed for blockchains which don't have on-chain ABI stores (which is bad). 236 | * 237 | * The results of this method should be an array structured as follows: 238 | [ 239 | // Many actions can be in the result as some blockchain support multiple actions within a transaction (batch). 240 | { 241 | // This is a key-value pair of the details of the transaction. 242 | // These details will be displayed for the user in the signature prompt. 243 | data:{ 244 | hello:'world', 245 | value:1, 246 | object:{...} 247 | } 248 | // The contract name, or in the case of a system token transfer then the account_name/address being sent to 249 | code, 250 | // Either the method name for a smart contract, or `transfer` for a system token transfer. 251 | type, 252 | // The authorizor of this transaction (account_name/address STRING) 253 | authorization 254 | } 255 | ] 256 | */ 257 | async requestParser(payload, network, abi = null){} 258 | } 259 | -------------------------------------------------------------------------------- /packages/core/lib/plugins/index.js: -------------------------------------------------------------------------------- 1 | import Plugin from './Plugin'; 2 | import PluginRepository from './PluginRepository'; 3 | import * as PluginTypes from './PluginTypes'; 4 | 5 | export default { 6 | Plugin, 7 | PluginRepository, 8 | PluginTypes, 9 | } -------------------------------------------------------------------------------- /packages/core/lib/services/apis/BackendApiService.js: -------------------------------------------------------------------------------- 1 | import IdGenerator from "../../util/IdGenerator"; 2 | import ecc from 'eosjs-ecc'; 3 | 4 | // const baseUrl = `http://localhost:6547/v1/`; 5 | const baseUrl = `https://api.get-scatter.com/v1/`; 6 | const PROOF_KEY = 'EOS62b3WxfuRyP7JYaDbF3gr49joLWYpsF3kPmo2HPxPuGRDiRUwj'; 7 | 8 | const getHeaders = () => { 9 | const proof = IdGenerator.text(64); 10 | return [proof, { 11 | 'Accept': 'application/json', 12 | 'Content-Type': 'application/json', 13 | proof, 14 | }] 15 | }; 16 | 17 | // All API requests must come back signed with the known 18 | // public key associated with the Scatter API 19 | const validate = (proof, res) => { 20 | try { 21 | const signedProof = res.headers.get('proof'); 22 | if(!signedProof) throw 'Invalid API Request'; 23 | if(ecc.recover(signedProof, proof) !== PROOF_KEY) throw 'Invalid API Request'; 24 | return res.json(); 25 | } catch(e){ 26 | throw "Invalid API Request"; 27 | } 28 | }; 29 | 30 | export const GET = route => { 31 | const [proof, headers] = getHeaders(); 32 | return fetch(`${baseUrl}${route}`, { 33 | method:'GET', 34 | mode:'cors', 35 | headers 36 | }).then(res => validate(proof,res)) 37 | }; 38 | 39 | export const POST = (route, data) => { 40 | const [proof, headers] = getHeaders(); 41 | return fetch(`${baseUrl}${route}`, { 42 | method:'POST', 43 | mode:'cors', 44 | headers, 45 | body:JSON.stringify(data), 46 | }).then(res => validate(proof,res)) 47 | }; 48 | 49 | export default class BackendApiService { 50 | 51 | // Add an array of applinks to filter only those results. 52 | static async apps(apps = []){ 53 | return POST(`apps`, {apps}); 54 | } 55 | 56 | // ACCOUNT CREATION 57 | static async checkMachineId(id){ return GET(`machine/${id}`); } 58 | static async createAccount(payload){ return POST(`create_bridge`, payload); } 59 | 60 | } -------------------------------------------------------------------------------- /packages/core/lib/services/apis/ExchangeService.js: -------------------------------------------------------------------------------- 1 | import * as Actions from '../../store/constants'; 2 | import BalanceService from "../blockchain/BalanceService"; 3 | import {GET, POST} from './BackendApiService'; 4 | import Token from "../../models/Token"; 5 | import StoreService from "../utility/StoreService"; 6 | import Framework from "../utility/Framework"; 7 | 8 | 9 | const timeout = (rq, caughtValue = null) => Promise.race([ 10 | new Promise(resolve => setTimeout(() => resolve(caughtValue), 10000)), 11 | rq.catch(() => caughtValue) 12 | ]); 13 | 14 | let watchers = []; 15 | let watchTimeout; 16 | 17 | export default class ExchangeService { 18 | 19 | static async pairs(token){ 20 | return timeout(POST('exchange/pairs', {token})).then(pairs => { 21 | if(!pairs) return []; 22 | Object.keys(pairs).map(key => pairs[key].map(x => x.token = Token.fromJson(x.token))); 23 | return pairs; 24 | }); 25 | } 26 | 27 | static async rate(token, other, service){ 28 | return timeout(POST('exchange/rate', {token, other, service})); 29 | } 30 | 31 | static async order(service, token, other, amount, from, to, returnsErrors = true){ 32 | return timeout(POST('exchange/order', {service, token, other, amount, from, to, returnsErrors})); 33 | } 34 | 35 | static async accepted(id){ 36 | return timeout(GET(`exchange/accepted/${id}`)); 37 | } 38 | 39 | static async cancelled(id){ 40 | return timeout(GET(`exchange/cancelled/${id}`)); 41 | } 42 | 43 | static async orderStatus(id){ 44 | return timeout(GET(`exchange/order/${id}`).then(res => res.updated.status)); 45 | } 46 | 47 | static async stablePaths(){ 48 | return timeout(GET(`exchange/stabilize/paths`), []); 49 | } 50 | 51 | static async pairable(){ 52 | return timeout(GET(`exchange/pairable`), []); 53 | } 54 | 55 | static watch(history){ 56 | watchers.push(history); 57 | this.checkExchanges(); 58 | return true; 59 | } 60 | 61 | static async checkExchanges(){ 62 | clearTimeout(watchTimeout); 63 | if(!watchers.length) return; 64 | 65 | for(let i = 0; i < watchers.length; i++){ 66 | const history = watchers[i]; 67 | const status = await this.orderStatus(history.orderDetails.id); 68 | if(status !== history.status){ 69 | history.status = status; 70 | await StoreService.get().dispatch(Actions.UPDATE_HISTORY, history); 71 | 72 | if(status === 'complete'){ 73 | // TODO: need to solve this with an injected sound service 74 | //SoundService.ding(); 75 | 76 | Framework.pushNotification('Exchange Complete', `Your token exchange has just completed.`); 77 | 78 | watchers = watchers.filter(x => x.id !== history.id); 79 | 80 | const accounts = StoreService.get().state.scatter.keychain.accounts.filter(x => x.sendable() === history.to); 81 | if(accounts.length){ 82 | for(let n = 0; n < accounts.length; n++){ 83 | await BalanceService.loadBalancesFor(accounts[n]); 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | setTimeout(() => this.checkExchanges(), 1000*30); 91 | } 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | } 103 | -------------------------------------------------------------------------------- /packages/core/lib/services/apis/PriceService.js: -------------------------------------------------------------------------------- 1 | import * as Actions from '../../store/constants'; 2 | import Token from "../../models/Token"; 3 | import {GET} from "./BackendApiService"; 4 | import StoreService from "../utility/StoreService"; 5 | import {dateId} from "../../util/DateHelpers"; 6 | 7 | 8 | // Once every 30 minutes. 9 | const intervalTime = 60000 * 30; 10 | let priceInterval; 11 | 12 | 13 | export default class PriceService { 14 | 15 | static async watchPrices(enable = true){ 16 | clearInterval(priceInterval); 17 | if(!enable) return; 18 | return new Promise(async resolve => { 19 | 20 | const setPrices = async () => { 21 | await PriceService.setPrices(); 22 | resolve(true); 23 | } 24 | 25 | await setPrices(); 26 | priceInterval = setInterval(async () => { 27 | await setPrices(); 28 | }, intervalTime); 29 | }) 30 | } 31 | 32 | static async setPrices(){ 33 | const prices = await PriceService.getAll(); 34 | if(prices && Object.keys(prices).length) { 35 | await StoreService.get().dispatch(Actions.SET_PRICES, prices); 36 | } 37 | } 38 | 39 | static getAll(){ 40 | return Promise.race([ 41 | new Promise(resolve => setTimeout(() => resolve(false), 10000)), 42 | GET(`prices?v2=true`).catch(() => { 43 | return {error:"Problem connecting to Prices API"}; 44 | }) 45 | ]) 46 | } 47 | 48 | static async getCurrencies(){ 49 | return Promise.race([ 50 | new Promise(resolve => setTimeout(() => resolve(false), 10000)), 51 | GET('currencies').catch(() => ['USD']) 52 | ]) 53 | } 54 | 55 | static async getCurrencyPrices(){ 56 | return Promise.race([ 57 | new Promise(resolve => setTimeout(() => resolve(false), 10000)), 58 | GET('currencies/prices').catch(() => null) 59 | ]) 60 | } 61 | 62 | static async loadPriceTimelineData(){ 63 | const prices = await PriceService.getCurrencyPrices(); 64 | const yesterday = await PriceService.getTimeline(dateId(1)); 65 | const today = await PriceService.getTimeline(); 66 | await StoreService.get().dispatch(Actions.SET_PRICE_DATA, {prices, yesterday, today}); 67 | return {prices, yesterday, today}; 68 | } 69 | 70 | static async getTimeline(date = null){ 71 | const query = date ? `?date=${date}` : ''; 72 | return Promise.race([ 73 | new Promise(resolve => setTimeout(() => resolve(false), 10000)), 74 | GET('prices/timeline'+query).catch(() => {}) 75 | ]) 76 | } 77 | 78 | static getTotal(totals, displayCurrency, bypassDisplayToken, displayToken){ 79 | if(!displayCurrency) displayCurrency = StoreService.get().state.scatter.settings.displayCurrency; 80 | 81 | if(!bypassDisplayToken && displayToken){ 82 | if(totals.hasOwnProperty(displayToken)) return totals[displayToken] 83 | else { 84 | const token = (displayToken instanceof Token ? displayToken : Token.fromUnique(displayToken)).clone(); 85 | token.amount = parseFloat(0).toFixed(token.decimals); 86 | return token; 87 | } 88 | } else { 89 | let total = 0; 90 | Object.keys(StoreService.get().state.prices).map(tokenUnique => { 91 | const balance = totals[tokenUnique]; 92 | if(balance){ 93 | const price = StoreService.get().state.prices[tokenUnique][displayCurrency]; 94 | const value = parseFloat(parseFloat(balance.amount) * parseFloat(price)); 95 | if(isNaN(value)) return; 96 | total += value; 97 | } 98 | }); 99 | 100 | return Token.fromJson({ 101 | symbol:this.fiatSymbol(displayCurrency), 102 | amount:total.toFixed(2), 103 | decimals:2, 104 | }) 105 | } 106 | } 107 | 108 | static fiatSymbol(currency) { 109 | if(!currency) currency = StoreService.get().state.scatter.settings.displayCurrency; 110 | switch(currency){ 111 | case 'USD': 112 | case 'AUD': 113 | case 'CAD': 114 | return '$'; 115 | case 'CNY': 116 | case 'JPY': 117 | return '¥'; 118 | case 'EUR': return '€'; 119 | case 'GBP': return '£'; 120 | 121 | 122 | default: return currency; 123 | } 124 | } 125 | 126 | } -------------------------------------------------------------------------------- /packages/core/lib/services/apis/index.js: -------------------------------------------------------------------------------- 1 | import ApiService from './ApiService'; 2 | import BackendApiService from './BackendApiService'; 3 | import ExchangeService from './ExchangeService'; 4 | import PriceService from './PriceService'; 5 | 6 | export default { 7 | ApiService, 8 | BackendApiService, 9 | ExchangeService, 10 | PriceService, 11 | } -------------------------------------------------------------------------------- /packages/core/lib/services/apps/AppsService.js: -------------------------------------------------------------------------------- 1 | import {BlockchainsArray, blockchainName} from "../../models/Blockchains"; 2 | import * as Actions from '../../store/constants'; 3 | import BackendApiService, {GET} from "../apis/BackendApiService"; 4 | import StoreService from "../utility/StoreService"; 5 | import ObjectHelpers from "../../util/ObjectHelpers"; 6 | 7 | const storeApps = res => { 8 | 9 | } 10 | 11 | export default class AppsService { 12 | 13 | /*** 14 | * Gets apps and binds them to state, 15 | * falls back to github if API is failing. 16 | * @returns {Promise} 17 | */ 18 | static async getApps(include = [], store = true, imageBackend = 'https://rawgit.com/GetScatter/ScatterApps/master/logos', filetype = 'svg'){ 19 | const apps = await BackendApiService.apps(include); 20 | const formattedApps = apps.reduce((acc,x) => { 21 | if(x.hasOwnProperty('hasimage')) x.img = `${imageBackend}/${x.applink}.${filetype}`; 22 | 23 | acc[x.applink] = x; 24 | return acc; 25 | }, {}); 26 | 27 | if(store && StoreService.get()) { 28 | StoreService.get().dispatch(Actions.SET_DAPP_DATA, formattedApps); 29 | } 30 | 31 | return formattedApps; 32 | } 33 | 34 | static async getFeaturedApps(){ 35 | return await GET('apps/featured'); 36 | } 37 | 38 | static async getAppDataFromServer(origin){ 39 | return GET(`app/${origin}`); 40 | } 41 | 42 | static appIsInLocalData(origin){ 43 | const dappData = StoreService.get().state.dappData || {}; 44 | let found = dappData[origin]; 45 | 46 | if(!found){ 47 | (() => { 48 | // Checking subdomains 49 | if(origin.split('.').length < 2) return; 50 | const [subdomain, domain, suffix] = origin.split('.'); 51 | Object.keys(dappData).map(applink => { 52 | if(origin.indexOf(applink) === -1) return; 53 | const dapp = dappData[applink]; 54 | if(!dapp.hasOwnProperty('subdomains') || !dapp.subdomains.length) return; 55 | // Checking wildcards 56 | if(dapp.subdomains.find(x => x === '*')){ 57 | if(`*.${applink}` === `*.${domain}.${suffix}`) return found = dapp; 58 | } 59 | // Checking hardcoded domains 60 | else { 61 | dapp.subdomains.map(sub => { 62 | if(`${sub}.${applink}` === origin) return found = dapp; 63 | }) 64 | } 65 | }) 66 | })(); 67 | } 68 | 69 | return found; 70 | } 71 | 72 | static getAppData(origin){ 73 | const emptyResult = { 74 | applink:origin, 75 | type:'', 76 | name:origin, 77 | description:'', 78 | logo:'', 79 | url:'', 80 | }; 81 | 82 | const found = AppsService.appIsInLocalData(origin); 83 | if(!found) return emptyResult; 84 | 85 | const maxDescriptionLength = 70; 86 | if(found.description.length > maxDescriptionLength){ 87 | found.description = `${found.description.substr(0,70)}${found.description.length > 70 ? '...':''}` 88 | } 89 | 90 | return found; 91 | } 92 | 93 | 94 | static categories(selectedCategory = null){ 95 | return AppsService.appsByCategory(selectedCategory).map(x => x.type) 96 | } 97 | 98 | static appsByCategory(selectedCategory = null){ 99 | const dappData = StoreService.get().state.dappData; 100 | if(!dappData) return {}; 101 | return Object.keys(dappData).reduce((acc, key) => { 102 | const item = dappData[key]; 103 | if(!acc.find(x => x.type === item.type)) acc.push({type:item.type, apps:[]}); 104 | acc.find(x => x.type === item.type).apps.push(item); 105 | return acc; 106 | }, []).map(cat => { 107 | ObjectHelpers.shuffle(cat.apps); 108 | if(selectedCategory) return cat; 109 | cat.apps = cat.apps.filter(({applink}) => AppsService.getAppData(applink).hasOwnProperty('img')); 110 | return cat; 111 | }).sort((a,b) => { 112 | return b.apps.length - a.apps.length 113 | }); 114 | } 115 | 116 | static appsByTerm(searchTerm){ 117 | const dappData = StoreService.get().state.dappData; 118 | if(!dappData) return {}; 119 | return Object.keys(dappData).reduce((acc, key) => { 120 | const item = dappData[key]; 121 | const found = prop => prop.toLowerCase().trim().indexOf(searchTerm.toLowerCase().trim()) > -1; 122 | if(found(item.applink) || found(item.name) || found(item.description)) acc.push(item); 123 | return acc; 124 | }, []); 125 | } 126 | 127 | static linkedApps(terms = '', typeFilter = null){ 128 | return StoreService.get().state.scatter.keychain.permissions.filter(x => x.isIdentity).map(({origin:applink}) => { 129 | return AppsService.getAppData(applink); 130 | }).filter(app => { 131 | return app.type === typeFilter || !typeFilter 132 | }).filter(app => { 133 | return app.name.toLowerCase().indexOf(terms) > -1 134 | }); 135 | } 136 | 137 | } -------------------------------------------------------------------------------- /packages/core/lib/services/apps/PermissionService.js: -------------------------------------------------------------------------------- 1 | import * as Actions from '../../store/constants'; 2 | 3 | import Permission from '../../models/Permission' 4 | import {IdentityRequiredFields} from '../../models/Identity' 5 | import SocketService from "../utility/SocketService"; 6 | import StoreService from "../utility/StoreService"; 7 | 8 | export default class PermissionService { 9 | 10 | static identityFromPermissions(origin, formatForResult = true){ 11 | 12 | const permissions = StoreService.get().state.scatter.keychain.permissions; 13 | const possibleId = permissions.find(x => x.isIdentityPermissionFor(origin)); 14 | if(possibleId){ 15 | let identityRequirements = IdentityRequiredFields.fromPermission(possibleId.identityRequirements); 16 | let identity = formatForResult ? possibleId.getIdentity().asOnlyRequiredFields(identityRequirements) : possibleId.getIdentity(); 17 | if(!identity) return null; 18 | identity.accounts = possibleId.getAccounts().map(x => formatForResult ? x.asReturnable() : x); 19 | return identity; 20 | } 21 | return null; 22 | } 23 | 24 | static async addIdentityOriginPermission(identity, accounts, identityRequirements, origin){ 25 | identityRequirements = IdentityRequiredFields.fromJson(identityRequirements); 26 | identityRequirements = identityRequirements.forPermission(); 27 | 28 | await this.removeIdentityPermission(origin); 29 | const scatter = StoreService.get().state.scatter.clone(); 30 | 31 | 32 | const permission = Permission.fromAction(origin, identity, accounts, { 33 | identityRequirements, 34 | isIdentity:true 35 | }); 36 | 37 | scatter.keychain.permissions.push(permission); 38 | return StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 39 | } 40 | 41 | static async removeIdentityPermission(origin){ 42 | const scatter = StoreService.get().state.scatter.clone(); 43 | // const idPermissions = scatter.keychain.permissions.find(x => x.isIdentity && x.origin === origin); 44 | // if(!idPermissions) return true; 45 | scatter.keychain.permissions = scatter.keychain.permissions.filter(x => !x.isIdentity || (x.isIdentity && x.origin !== origin)); 46 | return StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 47 | } 48 | 49 | static async addIdentityRequirementsPermission(origin, identity, identityRequirements){ 50 | identityRequirements = IdentityRequiredFields.fromJson(identityRequirements); 51 | 52 | // No need for a permission. 53 | if(identityRequirements.isEmpty()) return; 54 | 55 | identityRequirements = identityRequirements.forPermission(); 56 | 57 | 58 | const scatter = StoreService.get().state.scatter.clone(); 59 | 60 | const permission = Permission.fromJson({ 61 | origin, identity:identity.id, identityRequirements, isIdentityRequirements:true 62 | }); 63 | 64 | // Don't duplicate requirements. 65 | if(scatter.keychain.permissions.find(x => x.checksum() === permission.checksum())) return; 66 | 67 | scatter.keychain.permissions.push(permission); 68 | return StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 69 | } 70 | 71 | static hasIdentityRequirementsPermission(origin, identity, identityRequirements){ 72 | identityRequirements = IdentityRequiredFields.fromJson(identityRequirements); 73 | identityRequirements = identityRequirements.forPermission(); 74 | 75 | const permission = Permission.fromJson({ 76 | origin, identity:identity.id, identityRequirements, isIdentityRequirements:true 77 | }); 78 | 79 | return StoreService.get().state.scatter.keychain.permissions.find(x => x.checksum() === permission.checksum()); 80 | } 81 | 82 | static createActionPermission(origin, identity, accounts, whitelistData){ 83 | 84 | const immutableActionFields = Permission.createImmutableFieldsHash(whitelistData.fields, whitelistData.props); 85 | 86 | const permission = Permission.fromAction(origin, identity, accounts, { 87 | contract:whitelistData.code, 88 | contractHash:whitelistData.hash || null, 89 | action:whitelistData.type, 90 | immutableActionFields, 91 | mutableActionFields:whitelistData.props, 92 | timestamp:+new Date(), 93 | isContractAction:true 94 | }); 95 | 96 | 97 | const scatter = StoreService.get().state.scatter.clone(); 98 | return permission; 99 | } 100 | 101 | static async addActionPermissions(origin, identity, accounts, whitelists){ 102 | if(!whitelists || !whitelists.length) return; 103 | 104 | const permissions = whitelists.map(whitelist => 105 | PermissionService.createActionPermission(origin, identity, accounts, whitelist) 106 | ).filter(x => x); 107 | 108 | if(permissions.length){ 109 | const scatter = StoreService.get().state.scatter.clone(); 110 | permissions.map(perm => { 111 | // Removing all similar permissions for this action 112 | const similar = scatter.keychain.permissions.filter(x => 113 | x.origin === origin 114 | && x.isContractAction 115 | && x.contract === perm.contract 116 | && x.action === perm.action 117 | ).map(x => x.id); 118 | 119 | scatter.keychain.permissions = scatter.keychain.permissions.filter(x => !similar.includes(x.id)); 120 | scatter.keychain.permissions.push(perm) 121 | }); 122 | await StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 123 | } 124 | } 125 | 126 | static hasActionPermission(origin, identity, accounts, message){ 127 | 128 | const contract = message.code; 129 | const action = message.type; 130 | const contractHash = null; 131 | 132 | const permission = Permission.fromAction(origin, identity, accounts, { 133 | contract, 134 | contractHash, 135 | action, 136 | isContractAction:true 137 | }); 138 | 139 | const matchingPermissions = StoreService.get().state.scatter.keychain.permissions.filter(x => x.checksum() === permission.checksum()); 140 | 141 | if(!matchingPermissions.length) return false; 142 | 143 | return matchingPermissions.some(perm => { 144 | const immutableActionFields = Permission.createImmutableFieldsHash(message.data, perm.mutableActionFields); 145 | return perm.immutableActionFields === immutableActionFields; 146 | }); 147 | } 148 | 149 | static isWhitelistedTransaction(origin, identity, accounts, messages, requiredFields){ 150 | requiredFields = IdentityRequiredFields.fromJson(requiredFields); 151 | 152 | // Checking for permissions 153 | const whitelistedActions = messages.every(message => 154 | PermissionService.hasActionPermission(origin, identity, accounts, message) 155 | ); 156 | 157 | 158 | // Not all actions are whitelisted 159 | if(!whitelistedActions) return false; 160 | 161 | // Dont need to check for required fields 162 | if(requiredFields.isEmpty()) return true; 163 | 164 | return PermissionService.hasIdentityRequirementsPermission(origin, identity, requiredFields); 165 | } 166 | 167 | static checkAppLinkPermissions(origin){ 168 | const permissions = StoreService.get().state.scatter.keychain.permissions.filter(x => x.origin === origin); 169 | if(!permissions.length) SocketService.sendEvent('logout', {}, origin); 170 | } 171 | 172 | static async removeAllPermissions(){ 173 | const scatter = StoreService.get().state.scatter.clone(); 174 | 175 | scatter.keychain.permissions = []; 176 | scatter.keychain.apps.map(app => { 177 | scatter.keychain.removeApp(app); 178 | SocketService.sendEvent('logout', {}, app.origin); 179 | }) 180 | 181 | return StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 182 | } 183 | 184 | static async removeAllPermissionsFor(origin){ 185 | const scatter = StoreService.get().state.scatter.clone(); 186 | 187 | const app = scatter.keychain.apps.find(x => x.origin === origin); 188 | if(app) scatter.keychain.removeApp(app); 189 | 190 | scatter.keychain.permissions = scatter.keychain.permissions.filter(x => x.origin !== origin); 191 | await StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 192 | this.checkAppLinkPermissions(origin); 193 | return true; 194 | } 195 | 196 | static async removePermission(permission){ 197 | const scatter = StoreService.get().state.scatter.clone(); 198 | scatter.keychain.permissions = scatter.keychain.permissions.filter(x => x.id !== permission.id); 199 | await StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 200 | this.checkAppLinkPermissions(permission.origin); 201 | return true; 202 | } 203 | 204 | static async removeDanglingPermissions(){ 205 | const scatter = StoreService.get().state.scatter.clone(); 206 | const origins = scatter.keychain.permissions.map(x => x.origin); 207 | scatter.keychain.apps = scatter.keychain.apps.filter(x => origins.includes(x.origin)); 208 | return StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 209 | } 210 | 211 | } -------------------------------------------------------------------------------- /packages/core/lib/services/apps/index.js: -------------------------------------------------------------------------------- 1 | import AppsService from './AppsService' 2 | import PermissionService from './PermissionService' 3 | 4 | export default {AppsService, PermissionService} -------------------------------------------------------------------------------- /packages/core/lib/services/blockchain/AccountService.js: -------------------------------------------------------------------------------- 1 | import Account from '../../models/Account' 2 | import PluginRepository from '../../plugins/PluginRepository' 3 | import * as Actions from '../../store/constants' 4 | import {BlockchainsArray} from '../../models/Blockchains' 5 | import StoreService from "../utility/StoreService"; 6 | import BalanceService from "./BalanceService"; 7 | 8 | let checkedOrphanedAccounts = false; 9 | 10 | export default class AccountService { 11 | 12 | static async addAccount(account){ 13 | const scatter = StoreService.get().state.scatter.clone(); 14 | scatter.keychain.addAccount(account); 15 | return StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 16 | } 17 | 18 | static async removeAccounts(accounts){ 19 | const scatter = StoreService.get().state.scatter.clone(); 20 | accounts.map(account => scatter.keychain.removeAccount(account)); 21 | await StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 22 | return BalanceService.removeStaleBalances(); 23 | } 24 | 25 | static async getAccountsFor(keypair, network){ 26 | const publicKey = keypair.publicKeys.find(x => x.blockchain === network.blockchain).key; 27 | if(!publicKey) return null; 28 | 29 | let accounts = []; 30 | 31 | const plugin = PluginRepository.plugin(network.blockchain); 32 | 33 | if(!plugin.accountsAreImported()) accounts.push(Account.fromJson({ 34 | keypairUnique:keypair.unique(), 35 | networkUnique:network.unique(), 36 | publicKey 37 | })); 38 | 39 | else { 40 | await AccountService.accountsFrom(plugin, [network], accounts, keypair); 41 | } 42 | 43 | return accounts; 44 | } 45 | 46 | static async importAllAccounts(keypair, isNewKeypair = false, blockchains = null, networks = null, addOnly = false){ 47 | return new Promise(async resolve => { 48 | let scatter = StoreService.get().state.scatter.clone(); 49 | let accounts = []; 50 | 51 | if(!networks) networks = scatter.settings.networks; 52 | if(!blockchains) blockchains = keypair.blockchains; 53 | 54 | await Promise.all(blockchains.map(async blockchain => { 55 | const plugin = PluginRepository.plugin(blockchain); 56 | const filteredNetworks = networks.filter(x => x.blockchain === blockchain); 57 | if(isNewKeypair && plugin.accountsAreImported()) return true; 58 | return AccountService.accountsFrom(plugin, filteredNetworks, accounts, keypair); 59 | })); 60 | 61 | const uniques = accounts.map(x => x.unique()); 62 | const accountsToRemove = scatter.keychain.accounts.filter(x => x.keypairUnique === keypair.unique() && !uniques.includes(x.unique()) && blockchains.includes(x.blockchain)); 63 | 64 | 65 | // This method takes a while, re-cloning to make sure we're 66 | // always up to date before committing the data to storage. 67 | scatter = StoreService.get().state.scatter.clone(); 68 | if(!addOnly) accountsToRemove.map(account => scatter.keychain.removeAccount(account)); 69 | accounts.map(account => scatter.keychain.addAccount(account)); 70 | 71 | await BalanceService.removeStaleBalances(); 72 | 73 | await StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 74 | setTimeout(() => { 75 | resolve(accounts); 76 | }, 100); 77 | }) 78 | } 79 | 80 | static async importAllAccountsForNetwork(network){ 81 | return new Promise(async resolve => { 82 | let scatter = StoreService.get().state.scatter.clone(); 83 | const blockchain = network.blockchain; 84 | const keypairs = scatter.keychain.keypairs.filter(x => x.blockchains.includes(blockchain)); 85 | let accounts = []; 86 | 87 | const plugin = PluginRepository.plugin(network.blockchain); 88 | 89 | await Promise.all(keypairs.map(async keypair => { 90 | return AccountService.accountsFrom(plugin, [network], accounts, keypair); 91 | })); 92 | 93 | // This method takes a while, re-cloning to make sure we're 94 | // always up to date before committing the data to storage. 95 | scatter = StoreService.get().state.scatter.clone(); 96 | accounts.map(account => scatter.keychain.addAccount(account)); 97 | await StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 98 | resolve(accounts); 99 | }) 100 | } 101 | 102 | /*** 103 | * Gets accounts from networks 104 | * @param plugin - Blockchain plugin 105 | * @param networks - Networks to fetch from 106 | * @param accounts - (OUT) accounts array to append to 107 | * @param keypair - Associated keypair 108 | * @returns {Promise<*>} 109 | */ 110 | static async accountsFrom(plugin, networks, accounts, keypair){ 111 | return new Promise(async resolve => { 112 | if(plugin.accountsAreImported()){ 113 | (await Promise.all(networks.map(async network => { 114 | return await plugin.getImportableAccounts(keypair, network); 115 | }))).reduce((acc, arr) => { 116 | arr.map(account => { 117 | accounts.push(account) 118 | }); 119 | return acc; 120 | }, []); 121 | resolve(true); 122 | } else { 123 | networks.map(network => { 124 | const key = keypair.publicKeys.find(x => x.blockchain === network.blockchain); 125 | if(key){ 126 | accounts.push(Account.fromJson({ 127 | keypairUnique:keypair.unique(), 128 | networkUnique:network.unique(), 129 | publicKey:key.key 130 | })); 131 | } 132 | }); 133 | resolve(true); 134 | } 135 | }) 136 | } 137 | 138 | static async incrementAccountLogins(accounts){ 139 | const ids = accounts.map(x => x.unique()); 140 | const scatter = StoreService.get().state.scatter.clone(); 141 | scatter.keychain.accounts.filter(x => ids.includes(x.unique())).map(x => x.logins++); 142 | return StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 143 | } 144 | 145 | static async fixOrphanedAccounts(){ 146 | if(checkedOrphanedAccounts) return true; 147 | checkedOrphanedAccounts = true; 148 | 149 | const scatter = StoreService.get().state.scatter.clone(); 150 | const keypairs = scatter.keychain.keypairs.map(x => x.unique()); 151 | const orphaned = scatter.keychain.accounts.filter(x => !keypairs.includes(x.keypairUnique)); 152 | if(!orphaned.length) return true; 153 | 154 | orphaned.map(x => scatter.keychain.removeAccount(x)); 155 | await BalanceService.removeStaleBalances(); 156 | return StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /packages/core/lib/services/blockchain/BalanceService.js: -------------------------------------------------------------------------------- 1 | import PluginRepository from "../../plugins/PluginRepository"; 2 | import * as Actions from "../../store/constants"; 3 | import StoreService from "../utility/StoreService"; 4 | 5 | let lastBalanceTime; 6 | 7 | export default class BalanceService { 8 | 9 | static async loadBalancesFor(account, returnOnly = false){ 10 | try { 11 | const blockchain = account.blockchain(); 12 | const plugin = PluginRepository.plugin(blockchain); 13 | const tokens = StoreService.get().state.scatter.allTokens().filter(x => x.blockchain === blockchain) 14 | .filter(x => x.chainId === account.network().chainId); 15 | const balances = await plugin.balancesFor(account, tokens); 16 | 17 | // We are now considering this "locked up balances", which should be fetched individually on-demand 18 | // (await this.loadUntouchables(account)).map(x => balances.push(x)); 19 | 20 | if(returnOnly) return {account:account.identifiable(), balances}; 21 | return StoreService.get().dispatch(Actions.SET_BALANCES, {account:account.identifiable(), balances}); 22 | } catch(e){ 23 | return null; 24 | } 25 | } 26 | 27 | static async loadAllBalances(force = false, returnOnly = false){ 28 | if(!force && lastBalanceTime < (+new Date()+1000*60*5)) return; 29 | lastBalanceTime = +new Date(); 30 | const accounts = StoreService.get().state.scatter.keychain.accounts.reduce((acc, account) => { 31 | // Filtering out permission based accounts 32 | if(!acc.find(x => x.identifiable() === account.identifiable())) acc.push(account); 33 | return acc; 34 | }, []).sort(async account => { 35 | // Sorting mainnets first. 36 | const isMainnet = PluginRepository.plugin(account.blockchain()).isEndorsedNetwork(account.network()); 37 | return isMainnet ? -1 : 1; 38 | }); 39 | 40 | let results = []; 41 | for(let i = 0; i < accounts.length; i++){ 42 | await Promise.race([ 43 | new Promise(resolve => setTimeout(() => resolve(), 20000)), 44 | this.loadBalancesFor(accounts[i]).then(x => results.push(x)) 45 | ]); 46 | } 47 | 48 | if(returnOnly) return results; 49 | 50 | return true; 51 | } 52 | 53 | static removeStaleBalances(){ 54 | const accountKeys = StoreService.get().state.scatter.keychain.accounts.map(x => x.identifiable()); 55 | const keysToRemove = Object.keys(StoreService.get().state.balances).filter(key => !accountKeys.includes(key)); 56 | return StoreService.get().dispatch(Actions.REMOVE_BALANCES, keysToRemove); 57 | } 58 | 59 | static async loadUntouchables(account){ 60 | const plugin = PluginRepository.plugin(account.blockchain()); 61 | return plugin.hasUntouchableTokens() ? plugin.untouchableBalance(account) : []; 62 | } 63 | 64 | static totalBalances(){ 65 | const tokens = {}; 66 | tokens['totals'] = {}; 67 | 68 | const balances = StoreService.get().state.balances; 69 | 70 | Object.keys(balances).map(async accountUnique => { 71 | const account = StoreService.get().state.scatter.keychain.accounts.find(x => x.identifiable() === accountUnique); 72 | if(!account) return; 73 | 74 | if(!tokens.hasOwnProperty(account.networkUnique)){ 75 | tokens[account.networkUnique] = {}; 76 | } 77 | 78 | if(!balances.hasOwnProperty(accountUnique) || !balances[accountUnique]) return; 79 | balances[accountUnique].map(token => { 80 | if(!tokens[account.networkUnique].hasOwnProperty(token.uniqueWithChain())) { 81 | tokens[account.networkUnique][token.uniqueWithChain()] = token.clone(); 82 | tokens['totals'][token.uniqueWithChain()] = token.clone(); 83 | } else { 84 | tokens[account.networkUnique][token.uniqueWithChain()].add(token.amount); 85 | tokens['totals'][token.uniqueWithChain()].add(token.amount); 86 | } 87 | }); 88 | }); 89 | 90 | return tokens; 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /packages/core/lib/services/blockchain/ExplorerService.js: -------------------------------------------------------------------------------- 1 | import {blockchainName, Blockchains, BlockchainsArray} from "../../models/Blockchains"; 2 | import PluginRepository from "../../plugins/PluginRepository"; 3 | import Explorer from "../../models/Explorer"; 4 | import {GET} from "../apis/BackendApiService"; 5 | 6 | export default class ExplorerService { 7 | 8 | static getExplorers(){ 9 | let explorers = {}; 10 | BlockchainsArray.map(({value:blockchain}) => explorers[blockchain] = []); 11 | 12 | const setDefaultExplorers = () => { 13 | explorers = PluginRepository.defaultExplorers(); 14 | }; 15 | 16 | return Promise.race([ 17 | new Promise((resolve) => setTimeout(() => { 18 | setDefaultExplorers(); 19 | resolve(explorers); 20 | }, 3000)), 21 | GET(`explorers`) 22 | .then(res => { 23 | BlockchainsArray.map(({value:blockchain}) => { 24 | res[blockchainName(blockchain)].map(rawExplorer => { 25 | explorers[blockchain].push(Explorer.fromRaw(rawExplorer)); 26 | }); 27 | }); 28 | 29 | return explorers; 30 | }).catch(err => { 31 | setDefaultExplorers(); 32 | return explorers; 33 | }) 34 | ]) 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /packages/core/lib/services/blockchain/NetworkService.js: -------------------------------------------------------------------------------- 1 | import * as Actions from '../../store/constants'; 2 | 3 | import AccountService from './AccountService'; 4 | import BalanceService from "./BalanceService"; 5 | import PluginRepository from "../../plugins/PluginRepository"; 6 | import StoreService from "../utility/StoreService"; 7 | 8 | 9 | export default class NetworkService { 10 | 11 | static async addNetwork(network){ 12 | // Can't modify existing networks. 13 | const scatter = StoreService.get().state.scatter.clone(); 14 | const networks = scatter.settings.networks; 15 | if(networks.find(x => x.id === network.id)) return; 16 | 17 | if(!network.name.length) return {error:"Missing Name"}; 18 | if(!network.host.length) return {error:"Missing Host"}; 19 | if(!network.port) return {error:"Missing Port"}; 20 | if(!network.chainId) return {error:"Missing Chain"}; 21 | 22 | network.setPort(); 23 | 24 | if(networks.find(x => x.blockchain === network.blockchain && x.chainId === network.chainId)) 25 | return {error:"Chain Exists"} 26 | 27 | if(networks.find(x => x.name.toLowerCase() === network.name.toLowerCase())) 28 | return {error:"Name Exists"}; 29 | 30 | scatter.settings.updateOrPushNetwork(network); 31 | await StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 32 | const accounts = await AccountService.importAllAccountsForNetwork(network); 33 | if(accounts.length){ 34 | for(let i = 0; i < accounts.length; i++){ 35 | await BalanceService.loadBalancesFor(accounts[i]); 36 | } 37 | } 38 | PluginRepository.bustCaches(); 39 | return true; 40 | } 41 | 42 | static async removeNetwork(network){ 43 | PluginRepository.bustCaches(); 44 | const scatter = StoreService.get().state.scatter.clone(); 45 | 46 | // Removing accounts and permissions for this network 47 | const accounts = scatter.keychain.accounts.filter(x => x.networkUnique === network.unique()); 48 | accounts.map(account => scatter.keychain.removeAccount(account)); 49 | scatter.settings.removeNetwork(network); 50 | StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 51 | BalanceService.removeStaleBalances(); 52 | return true; 53 | } 54 | 55 | static async updateNetwork(network){ 56 | const scatter = StoreService.get().state.scatter.clone(); 57 | scatter.settings.updateOrPushNetwork(network); 58 | await StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 59 | PluginRepository.bustCaches(); 60 | return true; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /packages/core/lib/services/blockchain/ResourceService.js: -------------------------------------------------------------------------------- 1 | import PluginRepository from '../../plugins/PluginRepository'; 2 | import {Blockchains} from '../../models/Blockchains'; 3 | import Account from '../../models/Account'; 4 | import * as Actions from '../../store/constants'; 5 | import StoreService from "../utility/StoreService"; 6 | 7 | export default class ResourceService { 8 | 9 | static usesResources(account){ 10 | account = Account.fromJson(account); 11 | const plugin = PluginRepository.plugin(account.blockchain()); 12 | return plugin.usesResources(); 13 | } 14 | 15 | static async needsResources(account){ 16 | account = Account.fromJson(account); 17 | const plugin = PluginRepository.plugin(account.blockchain()); 18 | if(!plugin.usesResources()) return false; 19 | return plugin.needsResources(account); 20 | } 21 | 22 | static async addResources(account){ 23 | account = Account.fromJson(account); 24 | const plugin = PluginRepository.plugin(account.blockchain()); 25 | if(!plugin.usesResources()) return false; 26 | return plugin.addResources(account); 27 | } 28 | 29 | static async getResourcesFor(account){ 30 | account = Account.fromJson(account); 31 | const plugin = PluginRepository.plugin(account.blockchain()); 32 | if(!plugin.usesResources()) return []; 33 | return plugin.getResourcesFor(account); 34 | } 35 | 36 | static async cacheResourceFor(account){ 37 | if(!account) return; 38 | const resources = await ResourceService.getResourcesFor(account); 39 | StoreService.get().dispatch(Actions.ADD_RESOURCES, {acc:account.identifiable(), res:resources}); 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /packages/core/lib/services/blockchain/TransferService.js: -------------------------------------------------------------------------------- 1 | import {Blockchains, BlockchainsArray} from '../../models/Blockchains' 2 | import PluginRepository from '../../plugins/PluginRepository'; 3 | import HistoricTransfer from "../../models/histories/HistoricTransfer"; 4 | import * as Actions from '../../store/constants' 5 | import StoreService from "../utility/StoreService"; 6 | 7 | export default class TransferService { 8 | 9 | static async [Blockchains.BTC](params){ 10 | return this.baseTransfer(params); 11 | } 12 | 13 | static async [Blockchains.ETH](params){ 14 | return this.baseTransfer(params); 15 | } 16 | 17 | static async [Blockchains.TRX](params){ 18 | return this.baseTransfer(params); 19 | } 20 | 21 | static async [Blockchains.FIO](params){ 22 | return this.baseTransfer(params); 23 | } 24 | 25 | static async [Blockchains.EOSIO](params){ 26 | params.recipient = params.recipient.toLowerCase(); 27 | return this.baseTransfer(params); 28 | } 29 | 30 | static async baseTransfer(params){ 31 | let {account, recipient, amount, memo, token } = params; 32 | const plugin = PluginRepository.plugin(account.blockchain()); 33 | 34 | const transfer = await PluginRepository.plugin(account.blockchain()) 35 | .transfer({ 36 | account, 37 | to:recipient, 38 | amount, 39 | token, 40 | memo, 41 | }).catch(x => x); 42 | 43 | if(transfer !== null) { 44 | if (transfer.hasOwnProperty('error')) return transfer; 45 | else { 46 | if(!params.bypassHistory){ 47 | const history = new HistoricTransfer(account, recipient, token, amount, memo, this.getTransferId(transfer, token.blockchain)); 48 | StoreService.get().dispatch(Actions.DELTA_HISTORY, history); 49 | } 50 | 51 | return transfer; 52 | } 53 | } 54 | return null; 55 | } 56 | 57 | static getTransferId(transfer, blockchain){ 58 | switch(blockchain){ 59 | case Blockchains.EOSIO: return transfer.transaction_id; 60 | case Blockchains.TRX: return transfer.txID; 61 | case Blockchains.ETH: return transfer.transactionHash; 62 | case Blockchains.BTC: return transfer.txid; 63 | case Blockchains.FIO: return transfer.transaction_id; 64 | } 65 | return null; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /packages/core/lib/services/blockchain/index.js: -------------------------------------------------------------------------------- 1 | import AccountService from './AccountService'; 2 | import BalanceService from './BalanceService'; 3 | import ExplorerService from './ExplorerService'; 4 | import NetworkService from './NetworkService'; 5 | import ResourceService from './ResourceService'; 6 | import TransferService from './TransferService'; 7 | 8 | export default { 9 | AccountService, 10 | BalanceService, 11 | ExplorerService, 12 | NetworkService, 13 | ResourceService, 14 | TransferService, 15 | } -------------------------------------------------------------------------------- /packages/core/lib/services/index.js: -------------------------------------------------------------------------------- 1 | import apis from './apis/index'; 2 | import apps from './apps/index'; 3 | import blockchain from './blockchain/index'; 4 | import secure from './secure/index'; 5 | import utility from './utility/index'; 6 | 7 | export default { 8 | apis, 9 | apps, 10 | blockchain, 11 | secure, 12 | utility, 13 | } -------------------------------------------------------------------------------- /packages/core/lib/services/secure/HardwareService.js: -------------------------------------------------------------------------------- 1 | let hardwareService; 2 | 3 | const NO_INIT = "You must initialize the hardware service first."; 4 | 5 | export default class HardwareService { 6 | 7 | static init(_service){ 8 | hardwareService = _service; 9 | } 10 | 11 | static async openConnections(onlyIfDisconnected = false){ 12 | if(!hardwareService) return console.error(NO_INIT); 13 | return this.openConnections(onlyIfDisconnected); 14 | } 15 | 16 | static async checkHardware(account){ 17 | if(!hardwareService) return console.error(NO_INIT); 18 | return hardwareService.checkHardware(account); 19 | } 20 | 21 | static async sign(network, publicKey, payload){ 22 | if(!hardwareService) return console.error(NO_INIT); 23 | return hardwareService.sign(network, publicKey, payload); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /packages/core/lib/services/secure/KeyPairService.js: -------------------------------------------------------------------------------- 1 | import {BlockchainsArray, Blockchains, blockchainName} from '../../models/Blockchains'; 2 | import PluginRepository from '../../plugins/PluginRepository' 3 | import * as Actions from '../../store/constants'; 4 | 5 | import Crypto from '../../util/Crypto'; 6 | import Keypair from '../../models/Keypair'; 7 | import HardwareService from "./HardwareService"; 8 | import StoreService from "../utility/StoreService"; 9 | import IdGenerator from "../../util/IdGenerator"; 10 | import Seeder from "./Seeder"; 11 | 12 | let publicToPrivate; 13 | export default class KeyPairService { 14 | 15 | static init(_publicToPrivate){ 16 | publicToPrivate = _publicToPrivate; 17 | } 18 | 19 | static getImportedKeyBlockchains(privateKey){ 20 | let blockchains = []; 21 | BlockchainsArray.map(blockchainKV => { 22 | try { 23 | const plugin = PluginRepository.plugin(blockchainKV.value); 24 | if(plugin.validPrivateKey(privateKey)) blockchains.push(blockchainKV.value); 25 | } catch(e){} 26 | }); 27 | return blockchains; 28 | } 29 | 30 | static isValidPrivateKey(keypair){ 31 | return !!this.getImportedKeyBlockchains(keypair.privateKey).length; 32 | } 33 | 34 | static convertHexPrivateToBuffer(keypair){ 35 | if(typeof keypair.privateKey !== 'string') return false; 36 | let buffered = false; 37 | BlockchainsArray.map(blockchainKV => { 38 | if(buffered) return; 39 | try { 40 | const plugin = PluginRepository.plugin(blockchainKV.value); 41 | if(plugin.validPrivateKey(keypair.privateKey)){ 42 | keypair.privateKey = plugin.hexPrivateToBuffer(keypair.privateKey); 43 | buffered = true; 44 | } 45 | } catch(e){} 46 | }); 47 | } 48 | 49 | /*** 50 | * Tries to make a keypair in place from a private key 51 | * @param keypair 52 | */ 53 | static async makePublicKeys(keypair){ 54 | keypair.publicKeys = []; 55 | 56 | return Promise.all(BlockchainsArray.map(blockchainKV => { 57 | return this.addPublicKey(keypair, blockchainKV.value); 58 | })); 59 | } 60 | 61 | static async addPublicKey(keypair, blockchain, allowDecryption = false){ 62 | if(keypair.publicKeys.find(x => x.blockchain === blockchain)) return true; 63 | 64 | if(keypair.isEncrypted() && !allowDecryption) return false; 65 | else if(keypair.isEncrypted() && allowDecryption){ 66 | const seed = await Seeder.getSeed(); 67 | keypair.decrypt(seed); 68 | } 69 | 70 | try { 71 | const plugin = PluginRepository.plugin(blockchain); 72 | let p = keypair.privateKey; 73 | if(typeof p !== 'string') p = plugin.bufferToHexPrivate(p); 74 | keypair.publicKeys.push({blockchain, key:plugin.privateToPublic(p, keypair.fork)}); 75 | } catch(e){ 76 | console.log('err', e); 77 | return false; 78 | } 79 | 80 | return true; 81 | } 82 | 83 | static async generateKeyPair(keypair){ 84 | keypair.privateKey = await Crypto.generatePrivateKey(); 85 | return true; 86 | } 87 | 88 | static convertKey(keypair, blockchain){ 89 | const clone = keypair.clone(); 90 | clone.id = IdGenerator.text(24); 91 | clone.name = `${blockchainName(blockchain)} copy of ${keypair.name}`; 92 | clone.blockchains = [blockchain]; 93 | clone.createdAt = +new Date(); 94 | return clone; 95 | } 96 | 97 | static async saveKeyPair(keypair){ 98 | if(!keypair.name.length) keypair.name = `Key-${IdGenerator.text(8)}`; 99 | if(!keypair.isUnique()) return {error:"Keypair already exists."}; 100 | const scatter = StoreService.get().state.scatter.clone(); 101 | scatter.keychain.keypairs.push(Keypair.fromJson(keypair)); 102 | return StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 103 | } 104 | 105 | static async updateKeyPair(keypair){ 106 | if(!keypair.name.length) return; 107 | const scatter = StoreService.get().state.scatter.clone(); 108 | scatter.keychain.keypairs.find(x => x.unique() === keypair.unique()).name = keypair.name; 109 | scatter.keychain.keypairs.find(x => x.unique() === keypair.unique()).blockchains = keypair.blockchains; 110 | return StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 111 | } 112 | 113 | static async removeKeyPair(keypair){ 114 | const scatter = StoreService.get().state.scatter.clone(); 115 | scatter.keychain.removeKeyPair(keypair); 116 | return StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 117 | } 118 | 119 | static getKeyPairFromPublicKey(publicKey){ 120 | const keypair = StoreService.get().state.scatter.keychain.keypairs.find(x => x.publicKeys.find(k => k.key === publicKey)); 121 | if(keypair) return keypair.clone(); 122 | 123 | 124 | const identity = StoreService.get().state.scatter.keychain.identities.find(x => x.publicKey === publicKey); 125 | if(identity) { 126 | return Keypair.fromJson({ 127 | name:identity.name, 128 | publicKeys:[{blockchain:'eos', key:publicKey}], 129 | privateKey:identity.privateKey 130 | }); 131 | } 132 | 133 | return null; 134 | } 135 | 136 | static async publicToPrivate(publicKey){ 137 | if(publicToPrivate){ 138 | const p = await publicToPrivate(publicKey); 139 | if(p !== false) return p; 140 | } 141 | 142 | const keypair = this.getKeyPairFromPublicKey(publicKey, true); 143 | keypair.decrypt(await Seeder.getSeed()); 144 | if(keypair) return keypair.privateKey; 145 | return null; 146 | } 147 | 148 | static async getHardwareKeyList(external, delta = 0, tries = 0){ 149 | if(typeof external.interface.getAddress !== 'function') return false; 150 | if(tries >= 5) return false; 151 | 152 | return external.interface.getAddress(delta).catch(async err => { 153 | if(err.toString().match('CLA_NOT_SUPPORTED') || err.toString().match('Cannot write to HID device')){ 154 | await HardwareService.openConnections(); 155 | return this.getHardwareKeyList(external, delta, tries++); 156 | } 157 | return false; 158 | }) 159 | } 160 | 161 | 162 | static async loadFromHardware(keypair, tries = 0){ 163 | if(typeof keypair.external.interface.getPublicKey !== 'function') return false; 164 | if(tries >= 5) return false; 165 | 166 | return keypair.external.interface.getPublicKey().then(key => { 167 | if(PluginRepository.plugin(keypair.external.blockchain).validPublicKey(key)){ 168 | keypair.external.publicKey = key; 169 | keypair.publicKeys.push({blockchain:keypair.external.blockchain, key}); 170 | return true; 171 | } else return false; 172 | }).catch(async err => { 173 | if(err.toString().match('Cannot write to HID device')){ 174 | await HardwareService.openConnections(); 175 | return this.loadFromHardware(keypair, tries++); 176 | } 177 | return false; 178 | }) 179 | } 180 | 181 | static isHardware(publicKey){ 182 | const keypair = this.getKeyPairFromPublicKey(publicKey); 183 | if(!keypair) throw new Error('Keypair doesnt exist on keychain'); 184 | return keypair.external !== null; 185 | } 186 | 187 | } 188 | -------------------------------------------------------------------------------- /packages/core/lib/services/secure/PasswordService.js: -------------------------------------------------------------------------------- 1 | export default class PasswordService { 2 | 3 | static isLongEnough(password, suggested = 8){ 4 | return password.length >= suggested; 5 | } 6 | 7 | static uppercaseCount(password){ 8 | return password.split('').filter(x => x === x.toUpperCase()).length; 9 | } 10 | 11 | static lowercaseCount(password){ 12 | return password.split('').filter(x => x !== x.toUpperCase()).length; 13 | } 14 | 15 | static specialCharCount(password){ 16 | return password.replace(/[0-9a-zA-Z]/gi, '').length; 17 | } 18 | 19 | static hasError(password){ 20 | if(!this.isLongEnough(password)) return 'Your password is not long enough (8 characters)'; 21 | if(this.uppercaseCount(password) < 2) return `Passwords must have at least two uppercase letters`; 22 | if(this.lowercaseCount(password) < 2) return `Passwords must have at least two lowercase letters`; 23 | if(this.specialCharCount(password) < 2) return `Passwords must have at least two special characters (like # or @)`; 24 | return false; 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /packages/core/lib/services/secure/QRService.js: -------------------------------------------------------------------------------- 1 | import QRCode from 'qrcode'; 2 | import AES from 'aes-oop'; 3 | import Mnemonic from '../../util/Mnemonic' 4 | import Seeder from "./Seeder"; 5 | 6 | export default class QRService { 7 | 8 | static createQR(data, pass = null){ 9 | return new Promise(async resolve => { 10 | if(!pass || !pass.length) { 11 | resolve(QRCode.toDataURL(JSON.stringify({data, salt: Seeder.getSalt()}), {errorCorrectionLevel: 'L'})); 12 | } else { 13 | const oldSeed = await Seeder.getSeed(); 14 | const newSeed = (await Mnemonic.generateMnemonic(pass, Seeder.getSalt()))[1]; 15 | const dData = AES.encrypt(AES.decrypt(data, oldSeed), newSeed); 16 | resolve(QRCode.toDataURL(JSON.stringify({data:dData, salt: Seeder.getSalt()}), {errorCorrectionLevel: 'L'})); 17 | } 18 | }) 19 | } 20 | 21 | static async createUnEncryptedQR(data){ 22 | return QRCode.toDataURL(JSON.stringify(data), {errorCorrectionLevel: 'L'}); 23 | } 24 | 25 | static async decryptQR(data, salt, password){ 26 | const [mnemonic, seed] = await Mnemonic.generateMnemonic(password, salt); 27 | try { 28 | return AES.decrypt(data, seed) 29 | } catch(e){ 30 | console.error('Error decrypting QR: ', e); 31 | return null; 32 | } 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /packages/core/lib/services/secure/Seeder.js: -------------------------------------------------------------------------------- 1 | let seeder; 2 | export default class Seeder { 3 | 4 | static init(_seeder){ 5 | seeder = _seeder; 6 | } 7 | 8 | static async getSalt(){ 9 | return seeder.getSalt(); 10 | } 11 | 12 | static async getSeed(){ 13 | return seeder.get(); 14 | } 15 | 16 | static async setSeed(seed){ 17 | return seeder.set(seed); 18 | } 19 | 20 | static async clear(){ 21 | return seeder.clear(); 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /packages/core/lib/services/secure/SigningService.js: -------------------------------------------------------------------------------- 1 | import PluginRepository from "../../plugins/PluginRepository"; 2 | import KeyPairService from "./KeyPairService"; 3 | import HardwareService from "./HardwareService"; 4 | 5 | let signer; 6 | export default class SigningService { 7 | 8 | static init(_signer){ 9 | signer = _signer; 10 | } 11 | 12 | static sign(network, payload, publicKey, arbitrary = false, isHash = false){ 13 | // payload, publicKey, arbitrary = false, isHash = false, account = null 14 | if(!signer){ 15 | if(KeyPairService.isHardware(publicKey)){ 16 | return HardwareService.sign(network, publicKey, payload); 17 | } else return PluginRepository.plugin(network.blockchain).signer(payload, publicKey, arbitrary, isHash); 18 | } else return signer(network, publicKey, payload, arbitrary, isHash); 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /packages/core/lib/services/secure/index.js: -------------------------------------------------------------------------------- 1 | import HardwareService from './HardwareService'; 2 | import KeyPairService from './KeyPairService'; 3 | import QRService from './QRService'; 4 | import Seeder from './Seeder'; 5 | import SigningService from './SigningService'; 6 | 7 | export default { 8 | HardwareService, 9 | KeyPairService, 10 | QRService, 11 | Seeder, 12 | SigningService, 13 | } -------------------------------------------------------------------------------- /packages/core/lib/services/utility/ContactService.js: -------------------------------------------------------------------------------- 1 | import * as Actions from '../../store/constants' 2 | import StoreService from "./StoreService"; 3 | import {BlockchainsArray} from "../../models/Blockchains"; 4 | import PluginRepository from "../../plugins/PluginRepository"; 5 | 6 | export default class ContactService { 7 | 8 | constructor(){} 9 | 10 | static async addOrUpdate(contact){ 11 | contact.recipient = contact.recipient.trim(); 12 | contact.name = contact.name.trim(); 13 | const scatter = StoreService.get().state.scatter.clone(); 14 | 15 | if(!contact.name.length) return {error:'Invalid contact name'}; 16 | if(!contact.recipient.length) return {error:'Invalid contact account / address'}; 17 | 18 | if(scatter.contacts.find(x => x.id !== contact.id && x.recipient.toLowerCase() === contact.recipient.toLowerCase())) 19 | return {error:"Contact Exists"}; 20 | 21 | if(scatter.contacts.find(x => x.id !== contact.id && x.name.toLowerCase() === contact.name.toLowerCase())) 22 | return {error:"Contact Name Exists"}; 23 | 24 | 25 | const c = scatter.contacts.find(x => x.id === contact.id); 26 | if(c){ 27 | c.recipient = contact.recipient; 28 | c.name = contact.name; 29 | c.blockchain = contact.blockchain; 30 | } else { 31 | scatter.contacts.push(contact); 32 | } 33 | 34 | return StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 35 | } 36 | 37 | static async remove(contact){ 38 | const scatter = StoreService.get().state.scatter.clone(); 39 | scatter.contacts = scatter.contacts.filter(x => x.id !== contact.id); 40 | return StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 41 | } 42 | 43 | static validate(blockchain, contact){ 44 | // You can add unsupported blockchains which we have no logic for, 45 | // so we will always default to true for those. 46 | if(!BlockchainsArray.map(x => x.value).includes(blockchain)) return true; 47 | 48 | return PluginRepository.plugin(blockchain).isValidRecipient(contact); 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /packages/core/lib/services/utility/EventService.js: -------------------------------------------------------------------------------- 1 | 2 | let eventListener; 3 | 4 | export default class EventService { 5 | 6 | static init(_service){ 7 | eventListener = _service; 8 | } 9 | 10 | static emit(type, data){ 11 | return eventListener(type, data); 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /packages/core/lib/services/utility/Framework.js: -------------------------------------------------------------------------------- 1 | let framework; 2 | export default class Framework { 3 | 4 | static init(_framework){ 5 | framework = _framework; 6 | } 7 | 8 | static getVersion(){ 9 | return framework.getVersion(); 10 | } 11 | 12 | static pushNotification(title, description){ 13 | return framework.pushNotification(title, description); 14 | } 15 | 16 | static triggerDeepLink(deepLink){ 17 | 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /packages/core/lib/services/utility/IdentityService.js: -------------------------------------------------------------------------------- 1 | import StoreService from "./StoreService"; 2 | import * as Actions from '../../store/constants'; 3 | 4 | export default class IdentityService { 5 | 6 | static async addIdentity(identity){ 7 | const clone = StoreService.get().state.scatter.clone(); 8 | clone.keychain.updateOrPushIdentity(identity); 9 | return StoreService.get().dispatch(Actions.SET_SCATTER, clone); 10 | } 11 | 12 | static async updateIdentity(identity){ 13 | return this.addIdentity(identity); 14 | } 15 | 16 | static async removeIdentity(identity){ 17 | const clone = StoreService.get().state.scatter.clone(); 18 | clone.keychain.removeIdentity(identity); 19 | return StoreService.get().dispatch(Actions.SET_SCATTER, clone); 20 | } 21 | 22 | static async addLocation(location){ 23 | const clone = StoreService.get().state.scatter.clone(); 24 | clone.keychain.updateOrPushLocation(location); 25 | return StoreService.get().dispatch(Actions.SET_SCATTER, clone); 26 | } 27 | 28 | static async updateLocation(location){ 29 | return this.addLocation(location); 30 | } 31 | 32 | static async removeLocation(location){ 33 | const clone = StoreService.get().state.scatter.clone(); 34 | clone.keychain.removeLocation(location); 35 | return StoreService.get().dispatch(Actions.SET_SCATTER, clone); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /packages/core/lib/services/utility/SingletonService.js: -------------------------------------------------------------------------------- 1 | import * as Actions from '../../store/constants'; 2 | import AccountService from "../blockchain/AccountService"; 3 | import PriceService from "../apis/PriceService"; 4 | import PermissionService from "../apps/PermissionService"; 5 | import StoreService from "./StoreService"; 6 | import SocketService from "./SocketService"; 7 | import AppsService from "../apps/AppsService"; 8 | import PluginRepository from "../../plugins/PluginRepository"; 9 | import {Blockchains} from "../../models/Blockchains"; 10 | 11 | let initialized = false; 12 | 13 | export default class SingletonService { 14 | 15 | static async init(){ 16 | if(initialized) return true; 17 | initialized = true; 18 | PluginRepository.plugin(Blockchains.TRX).init(); 19 | SocketService.initialize(); 20 | AppsService.getApps(); 21 | PriceService.watchPrices(); 22 | 23 | PermissionService.removeDanglingPermissions(); 24 | AccountService.fixOrphanedAccounts(); 25 | return true; 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /packages/core/lib/services/utility/SocketService.js: -------------------------------------------------------------------------------- 1 | import ApiService from '../apis/ApiService'; 2 | import AuthorizedApp from '../../models/AuthorizedApp'; 3 | import * as Actions from '../../store/constants'; 4 | import StoreService from "./StoreService"; 5 | import EventService from "./EventService"; 6 | 7 | 8 | 9 | let service; 10 | 11 | const emit = (origin, id, path, data) => service.emit(origin, id, path, data); 12 | const getNewKey = (origin, id) => service.getNewKey(origin, id); 13 | 14 | export const handleApiResponse = async (request, id) => { 15 | 16 | // 2 way authentication 17 | const existingApp = StoreService.get().state.scatter.keychain.findApp(request.data.payload.origin); 18 | 19 | const updateNonce = async () => { 20 | const clone = StoreService.get().state.scatter.clone(); 21 | existingApp.nextNonce = request.data.nextNonce; 22 | clone.keychain.updateOrPushApp(existingApp); 23 | return StoreService.get().dispatch(Actions.SET_SCATTER, clone); 24 | }; 25 | 26 | const removeAppPermissions = async () => { 27 | const clone = StoreService.get().state.scatter.clone(); 28 | clone.keychain.removeApp(existingApp); 29 | return StoreService.get().dispatch(Actions.SET_SCATTER, clone); 30 | }; 31 | 32 | if(!existingApp) return; 33 | if(!existingApp.checkKey(request.data.appkey)) return; 34 | if(existingApp.nextNonce.length && !existingApp.checkNonce(request.data.nonce)) await removeAppPermissions(); 35 | else await updateNonce(); 36 | 37 | ApiService.handler(Object.assign(request.data, {plugin:request.plugin})).then(result => { 38 | emit(existingApp.origin, id, 'api', result); 39 | }) 40 | }; 41 | 42 | export const handlePairedResponse = async (request, id) => { 43 | const scatter = StoreService.get().state.scatter; 44 | const existingApp = scatter.keychain.findApp(request.data.origin); 45 | const linkApp = { 46 | type:'linkApp', 47 | payload:request.data 48 | }; 49 | 50 | if(request.data.passthrough) 51 | return emit(request.data.origin, id, 'paired', existingApp && existingApp.checkKey(request.data.appkey)); 52 | 53 | const addAuthorizedApp = async (newKey = null) => { 54 | const authedApp = new AuthorizedApp(request.data.origin, newKey ? newKey : request.data.appkey); 55 | const clone = scatter.clone(); 56 | clone.keychain.updateOrPushApp(authedApp); 57 | await StoreService.get().dispatch(Actions.SET_SCATTER, clone); 58 | emit(request.data.origin, id, 'paired', true); 59 | }; 60 | 61 | const repair = async () => { 62 | const newKey = await getNewKey(request.data.origin, id); 63 | if(newKey.data.origin !== request.data.origin || newKey.data.appkey.indexOf('appkey:') === -1) return emit(request.data.origin, id, 'paired', false); 64 | return addAuthorizedApp(newKey.data.appkey) 65 | } 66 | 67 | if(existingApp){ 68 | if(existingApp.checkKey(request.data.appkey)) return emit(request.data.origin, id, 'paired', true); 69 | else EventService.emit('popout', linkApp).then( async ({result}) => { 70 | if(result) return repair(); 71 | else emit(request.data.origin, id, 'paired', false); 72 | }); 73 | } 74 | else return repair(); 75 | }; 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | /*** 85 | * Gets certs that allow for `wss` local connections. 86 | * @returns {Promise} 87 | */ 88 | export const getCerts = async () => { 89 | return fetch('https://certs.get-scatter.com?rand='+Math.round(Math.random()*100 + 1)) 90 | .then(res => res.json()) 91 | .then(res => { 92 | if(res.hasOwnProperty('key') && res.hasOwnProperty('cert')) return res; 93 | EventService.emit('no_certs'); 94 | return null; 95 | }) 96 | .catch(() => console.error('Could not fetch certs. Probably due to a proxy, vpn, or firewall.')); 97 | }; 98 | 99 | export default class SocketService { 100 | 101 | static init(_service){ 102 | service = _service; 103 | } 104 | 105 | static async initialize(){ 106 | return service.initialize(); 107 | } 108 | 109 | static async close(){ 110 | return service.close(); 111 | } 112 | 113 | static async sendEvent(event, payload, origin){ 114 | return service.sendEvent(event, payload, origin); 115 | } 116 | 117 | static async broadcastEvent(event, payload){ 118 | return service.broadcastEvent(event, payload); 119 | } 120 | 121 | } -------------------------------------------------------------------------------- /packages/core/lib/services/utility/StoreService.js: -------------------------------------------------------------------------------- 1 | import * as Actions from '../../store/constants' 2 | 3 | let store; 4 | 5 | /*** 6 | * This is a helper service which returns the store 7 | * but allows for testing suites to be run without vuex 8 | */ 9 | export default class StoreService { 10 | 11 | static init(_store){ 12 | store = _store; 13 | } 14 | 15 | static get(){ 16 | return store; 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /packages/core/lib/services/utility/TokenService.js: -------------------------------------------------------------------------------- 1 | import * as Actions from '../../store/constants'; 2 | import BigNumber from "bignumber.js"; 3 | import Token from "../../models/Token"; 4 | import StoreService from "./StoreService"; 5 | import BalanceService from "../blockchain/BalanceService"; 6 | 7 | const filterOutToken = (scatter, token) => { 8 | scatter.settings.tokens = scatter.settings.tokens.filter(x => x.unique() !== token.unique()); 9 | scatter.settings.blacklistTokens = scatter.settings.blacklistTokens.filter(x => x.unique() !== token.unique()); 10 | if(scatter.settings.displayToken === token.unique()) scatter.settings.displayToken = null; 11 | } 12 | 13 | export default class TokenService { 14 | 15 | static async addToken(token, blacklist = false){ 16 | const scatter = StoreService.get().state.scatter.clone(); 17 | 18 | // Never adding system tokens. 19 | if(StoreService.get().state.scatter.networkTokens().find(x => x.unique() === token.unique())) return true; 20 | 21 | if(!token.symbol.length) return {error:"Symbol Missing"}; 22 | if(!token.contract.length) return {error:"Contract missing"}; 23 | 24 | if(!blacklist && scatter.settings.tokens.find(x => x.unique() === token.unique())) 25 | return {error:"Token exists already (whitelist)"}; 26 | 27 | if(blacklist && scatter.settings.blacklistTokens.find(x => x.unique() === token.unique())) 28 | return {error:"Token exists already (blacklist)"}; 29 | 30 | if(!token.name.trim().length) token.name = token.symbol; 31 | 32 | filterOutToken(scatter, token); 33 | 34 | if(!blacklist) scatter.settings.tokens.unshift(token); 35 | else scatter.settings.blacklistTokens.unshift(token); 36 | 37 | return StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 38 | } 39 | 40 | static removeToken(token){ 41 | const scatter = StoreService.get().state.scatter.clone(); 42 | 43 | // Never removing system tokens. 44 | if(StoreService.get().state.scatter.networkTokens().find(x => x.unique() === token.unique())) return true; 45 | 46 | filterOutToken(scatter, token); 47 | StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 48 | } 49 | 50 | static hasToken(token){ 51 | const scatter = StoreService.get().state.scatter.clone(); 52 | 53 | return !!BalanceService.totalBalances().totals[token.unique()] || 54 | !!scatter.settings.tokens.find(x => x.unique() === token.unique()) || 55 | !!scatter.settings.blacklistTokens.find(x => x.unique() === token.unique()); 56 | } 57 | 58 | static async setDisplayCurrency(ticker){ 59 | const scatter = StoreService.get().state.scatter.clone(); 60 | scatter.settings.displayCurrency = ticker; 61 | return StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 62 | } 63 | 64 | static async setDisplayToken(token){ 65 | const scatter = StoreService.get().state.scatter.clone(); 66 | scatter.settings.displayToken = token instanceof Token ? token.uniqueWithChain() : token; 67 | return StoreService.get().dispatch(Actions.SET_SCATTER, scatter); 68 | } 69 | 70 | 71 | static formatAmount(amount, token, div = false){ 72 | const operator = div ? 'div' : 'times'; 73 | let decimalString = ''; 74 | for(let i = 0; i < token.decimals; i++){ decimalString += '0'; } 75 | return new BigNumber(amount.toString(10), 10)[operator](`1${decimalString}`).toString(10); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /packages/core/lib/services/utility/index.js: -------------------------------------------------------------------------------- 1 | import ContactService from './ContactService'; 2 | import Framework from './Framework'; 3 | import SingletonService from './SingletonService'; 4 | import SocketService from './SocketService'; 5 | import StoreService from './StoreService'; 6 | import TokenService from './TokenService'; 7 | import EventService from './EventService'; 8 | 9 | export default { 10 | ContactService, 11 | Framework, 12 | SingletonService, 13 | SocketService, 14 | StoreService, 15 | TokenService, 16 | EventService, 17 | } 18 | -------------------------------------------------------------------------------- /packages/core/lib/store/constants.js: -------------------------------------------------------------------------------- 1 | export const SET_DAPP_DATA = 'setDappData'; 2 | export const SET_DAPP_LOGO = 'setDappLogo'; 3 | export const SET_RESOURCES = 'setResources'; 4 | export const ADD_RESOURCES = 'addResources'; 5 | export const SET_SCATTER = 'setScatter'; 6 | export const LOAD_SCATTER = 'loadScatter'; 7 | export const HOLD_SCATTER = 'holdScatter'; 8 | export const SET_BALANCES = 'setBalances'; 9 | export const REMOVE_BALANCES = 'removeBalances'; 10 | export const SET_PRICES = 'setPrices'; 11 | export const UPDATE_HISTORY = 'updateHistory'; 12 | export const DELTA_HISTORY = 'deltaHistory'; 13 | export const LOAD_HISTORY = 'loadHistory'; 14 | export const SET_PRICE_DATA = 'setPriceData'; -------------------------------------------------------------------------------- /packages/core/lib/store/index.js: -------------------------------------------------------------------------------- 1 | import * as constants from './constants'; 2 | export default {constants}; -------------------------------------------------------------------------------- /packages/core/lib/util/Crypto.js: -------------------------------------------------------------------------------- 1 | import ecc from 'eosjs-ecc'; 2 | const {PrivateKey} = ecc; 3 | 4 | import PluginRepository from '../plugins/PluginRepository'; 5 | 6 | export default class Crypto { 7 | 8 | static async generatePrivateKey(){ 9 | return (await PrivateKey.randomKey()).toBuffer(); 10 | } 11 | 12 | static bufferToPrivateKey(buffer, blockchain){ 13 | return PluginRepository.plugin(blockchain).bufferToHexPrivate(buffer); 14 | } 15 | 16 | static privateKeyToBuffer(privateKey, blockchain){ 17 | return PluginRepository.plugin(blockchain).hexPrivateToBuffer(privateKey); 18 | } 19 | 20 | static bufferToHash(buffer){ 21 | return ecc.sha256(buffer); 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /packages/core/lib/util/DateHelpers.js: -------------------------------------------------------------------------------- 1 | export const dateId = (minusDays = 0) => { 2 | const d = new Date(); 3 | d.setDate(d.getDate()-minusDays); 4 | const date = d.getUTCDate(); 5 | const month = d.getUTCMonth()+1; 6 | const year = d.getUTCFullYear(); 7 | return `${date}-${month}-${year}`; 8 | }; 9 | 10 | export const hourNow = () => { 11 | const d = new Date(); 12 | return d.getHours(); 13 | }; 14 | 15 | export const daysOld = (id, days) => { 16 | const [d2,m2,y2] = id.split('-'); 17 | 18 | const d = new Date(); 19 | const ago = new Date(d.getTime() - (days * 24 * 60 * 60 * 1000)); 20 | const then = new Date(y2, m2-1, d2, 0, 0, 0, 0); 21 | 22 | return then < ago; 23 | }; 24 | 25 | export const utcToLocal = (id, hour = 0) => { 26 | const [d2,m2,y2] = id.split('-'); 27 | 28 | const d = new Date(); 29 | d.setUTCDate(d2); 30 | d.setUTCMonth(m2); 31 | d.setUTCFullYear(y2); 32 | d.setUTCHours(hour); 33 | const date = d.getDate(); 34 | const month = d.getMonth(); 35 | const year = d.getFullYear(); 36 | return [`${date}-${month}-${year}`, d.getHours()]; 37 | }; -------------------------------------------------------------------------------- /packages/core/lib/util/Hasher.js: -------------------------------------------------------------------------------- 1 | import Seeder from "../services/secure/Seeder"; 2 | 3 | const ecc = require('eosjs-ecc'); 4 | const scrypt = require('scrypt-async'); 5 | 6 | export default class Hasher { 7 | 8 | /*** 9 | * Hashes a cleartext using the SHA-256 algorithm. 10 | * This is INSECURE and should only be used for fingerprinting. 11 | * @param cleartext 12 | */ 13 | static unsaltedQuickHash(cleartext) { 14 | return ecc.sha256(cleartext); 15 | } 16 | 17 | /*** 18 | * Hashes a cleartext using scrypt. 19 | * @param cleartext 20 | * @param salt 21 | */ 22 | static async secureHash(cleartext, salt = null) { 23 | return new Promise(async resolve => { 24 | if(!salt) salt = await Seeder.getSalt() || 'SALT_ME'; 25 | scrypt(cleartext, salt, { 26 | N: 16384, 27 | r: 8, 28 | p: 1, 29 | dkLen: 16, 30 | encoding: 'hex' 31 | }, (derivedKey) => { 32 | resolve(derivedKey); 33 | }) 34 | }); 35 | } 36 | } -------------------------------------------------------------------------------- /packages/core/lib/util/Http.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * THIS HTTP SERVICE IS ONLY USED FOR HARDWARE WALLET CONNECTIONS 3 | * 4 | */ 5 | 6 | export const get = async route => { 7 | return Promise.race([ 8 | fetch(route).then(res => res.json()).catch(() => null), 9 | new Promise(resolve => setTimeout(() => resolve(null), 60000)) 10 | ]) 11 | } 12 | 13 | export const post = async (route, data) => { 14 | return Promise.race([ 15 | fetch(route, { 16 | method: "POST", 17 | headers: { 18 | 'Accept': 'application/json, text/plain, */*', 19 | 'Content-Type': 'application/json' 20 | }, 21 | body: JSON.stringify(data) 22 | }).then(res => res.json()).catch(() => null), 23 | new Promise(resolve => setTimeout(() => resolve(null), 120000)) 24 | ]) 25 | }; -------------------------------------------------------------------------------- /packages/core/lib/util/IdGenerator.js: -------------------------------------------------------------------------------- 1 | const nodeCrypto = typeof window === 'undefined' ? require('crypto') : null; 2 | 3 | const getRandomNumber = () => { 4 | const nodeJsEnv = () => parseInt(nodeCrypto.randomBytes(8).toString('hex'), 16) / 0xffffffffffffffff; 5 | const browserEnv = () => { 6 | const arr = new Uint32Array(1); 7 | window.crypto.getRandomValues(arr); 8 | return arr[0]/(0xffffffff + 1); 9 | } 10 | 11 | return nodeCrypto ? nodeJsEnv() : browserEnv(); 12 | } 13 | 14 | export default class IdGenerator { 15 | 16 | static rand(){ 17 | return getRandomNumber(); 18 | } 19 | 20 | /*** 21 | * Generates a random string of specified size 22 | * @param size - The length of the string to generate 23 | * @returns {string} - The generated random string 24 | */ 25 | static text(size){ 26 | let text = ""; 27 | const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 28 | for(let i=0; i max ) return IdGenerator.numeric(max) + IdGenerator.numeric(size - max); 42 | 43 | max = Math.pow(10, size+add); 44 | const min = max / 10, 45 | number = Math.floor(IdGenerator.rand() * (max - min + 1)) + min; 46 | 47 | return ("" + number).substring(add); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /packages/core/lib/util/Mnemonic.js: -------------------------------------------------------------------------------- 1 | import Hasher from "./Hasher"; 2 | import bip39 from 'bip39' 3 | 4 | export class Mnemonic { 5 | 6 | /*** 7 | * Generates a mnemonic from a password 8 | * @param password 9 | * @param salt 10 | * @returns {[string,string]} 11 | */ 12 | static async generateMnemonic(password, salt = null) { 13 | const hash = await Hasher.secureHash(password, salt); 14 | let mnemonic = bip39.entropyToMnemonic(hash); 15 | return [mnemonic, bip39.mnemonicToSeedHex(mnemonic)]; 16 | } 17 | 18 | static async mnemonicToSeed(mnemonic){ 19 | return bip39.mnemonicToSeedHex(mnemonic); 20 | } 21 | } 22 | 23 | export default Mnemonic; -------------------------------------------------------------------------------- /packages/core/lib/util/ObjectHelpers.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * A set of helpers for Objects/Arrays 3 | */ 4 | export default class ObjectHelpers { 5 | 6 | /*** 7 | * Makes a single level array distinct 8 | * @param array 9 | * @returns {*} 10 | */ 11 | static distinct(array){ 12 | return array.reduce((a,b) => (a.includes(b)) ? a : a.concat(b), []); 13 | } 14 | 15 | /*** 16 | * Flattens an array into a single dimension 17 | * @param array 18 | * @returns {*} 19 | */ 20 | static flatten(array){ 21 | if(!Array.isArray(array)) return array; 22 | return array.reduce( 23 | (a, b) => a.concat(Array.isArray(b) ? this.flatten(b) : b), [] 24 | ); 25 | } 26 | 27 | /*** 28 | * Flattens an array into a single dimension 29 | * @param val 30 | * @returns {*} 31 | */ 32 | static flattenObject(val){ 33 | if(typeof val !== 'object') return this.flatten(val); 34 | return this.flatten(Object.keys(val).map(key => { 35 | return this.flattenObject(val[key]); 36 | })); 37 | } 38 | 39 | static shuffle(a) { 40 | for (let i = a.length - 1; i > 0; i--) { 41 | const j = Math.floor(Math.random() * (i + 1)); 42 | [a[i], a[j]] = [a[j], a[i]]; 43 | } 44 | return a; 45 | } 46 | 47 | static objectTake(obj, limit){ 48 | let limited = {}; 49 | if(Object.keys(obj).length < limit) return obj; 50 | Object.keys(obj).map(key => { 51 | if(Object.keys(limited).length >= limit) return; 52 | limited[key] = obj[key]; 53 | }); 54 | return limited; 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /packages/core/lib/util/TestingHelper.js: -------------------------------------------------------------------------------- 1 | export const RUNNING_TESTS = process.env['NODE_ENV'] === 'testing'; 2 | export let SHOW_POPUPS_AS_CONSOLE = false; 3 | 4 | export const setPopupsAsConsole = bool => SHOW_POPUPS_AS_CONSOLE = bool; 5 | -------------------------------------------------------------------------------- /packages/core/lib/util/index.js: -------------------------------------------------------------------------------- 1 | import Crypto from './Crypto' 2 | import * as DateHelpers from './DateHelpers' 3 | import Hasher from './Hasher' 4 | import * as Http from './Http' 5 | import IdGenerator from './IdGenerator' 6 | import Mnemonic from './Mnemonic' 7 | import ObjectHelpers from './ObjectHelpers' 8 | import * as TestingHelper from './TestingHelper' 9 | 10 | export default { 11 | Crypto, 12 | DateHelpers, 13 | Hasher, 14 | Http, 15 | IdGenerator, 16 | Mnemonic, 17 | ObjectHelpers, 18 | TestingHelper, 19 | } -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@walletpack/core", 3 | "version": "1.0.51", 4 | "description": "> TODO: description", 5 | "author": "GetScatter Ltd. 2019", 6 | "homepage": "", 7 | "license": "ISC", 8 | "main": "core.js", 9 | "files": [ 10 | "dist", 11 | "prepare.js" 12 | ], 13 | "scripts": { 14 | "install": "node prepare.js", 15 | "test": "echo \"Error: run tests from root\" && exit 1" 16 | }, 17 | "dependencies": { 18 | "aes-oop": "^1.0.4", 19 | "bignumber.js": "^9.0.0", 20 | "bip39": "^2.6.0", 21 | "eosjs-ecc": "^4.0.4", 22 | "qrcode": "^1.4.1", 23 | "scrypt-async": "^2.0.1" 24 | }, 25 | "gitHead": "70bd19f93b503618a79eb519eef082bfc40b16d7", 26 | "publishConfig": { 27 | "access": "public" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/core/prepare.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const rimraf = require("rimraf"); 4 | 5 | const paths = __dirname.split(path.sep); 6 | const parent = paths[paths.length-2]; 7 | 8 | if(parent === 'packages') return; 9 | 10 | try { 11 | rimraf.sync("./__tests__"); 12 | const files = fs.readdirSync(`./dist`); 13 | files.map(file => { 14 | if(fs.existsSync(`./${file}`)) rimraf.sync(`./${file}`); 15 | fs.renameSync(`./dist/${file}`, `./${file}`); 16 | }) 17 | rimraf.sync("./dist"); 18 | rimraf.sync("./lib"); 19 | } catch(e){ 20 | console.error('Walletpack prepare.js error', e); 21 | } 22 | -------------------------------------------------------------------------------- /packages/eosio/README.md: -------------------------------------------------------------------------------- 1 | # `eosio` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const eosio = require('eosio'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/eosio/__tests__/api.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {assert} = require('chai'); 3 | 4 | require('isomorphic-fetch'); 5 | const LightAPI = require('../lib/api').default; 6 | const Account = require('../../core/lib/models/Account').default; 7 | const Network = require('../../core/lib/models/Network').default; 8 | 9 | const network = Network.fromJson({ 10 | blockchain:'eos', 11 | name:'EOS Mainnet', 12 | host:'nodes.get-scatter.com', 13 | port:443, 14 | protocol:'https', 15 | chainId:'aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906', 16 | }); 17 | 18 | const account = Account.fromJson({ 19 | name:'ramdeathtest', 20 | authority:'active', 21 | publicKey:'', 22 | keypairUnique:'abcd', 23 | networkUnique:network.unique(), 24 | }); 25 | 26 | describe('eosio', () => { 27 | 28 | it('should be able to fetch balances', done => { 29 | new Promise(async () => { 30 | 31 | done(); 32 | }) 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/eosio/lib/api.js: -------------------------------------------------------------------------------- 1 | import Token from "@walletpack/core/models/Token"; 2 | import {Blockchains} from "@walletpack/core/models/Blockchains"; 3 | 4 | 5 | let networks, hosts; 6 | let cache = {}; 7 | 8 | export default class LightAPI { 9 | 10 | static async cacheEndpointsAndNetworks(){ 11 | return fetch(`https://endpoints.light.xeos.me/endpoints.json`).catch(() => null).then(x => x.json()).then(x => { 12 | hosts = Object.keys(x['api-endpoints']).reduce((acc, host) => { 13 | x['api-endpoints'][host].networks.map(network => { 14 | if(!acc[network]) acc[network] = host; 15 | }); 16 | return acc; 17 | }, {}); 18 | networks = Object.keys(x['networks']).reduce((acc, network) => { 19 | acc[x['networks'][network].chainid] = network; 20 | return acc; 21 | }, {}); 22 | return true; 23 | }); 24 | } 25 | 26 | static async getNetworks(){ 27 | if(!networks) await LightAPI.cacheEndpointsAndNetworks(); 28 | return networks; 29 | } 30 | 31 | static async networkString(network){ 32 | const networks = await this.getNetworks(); 33 | if(!networks) return null; 34 | return networks[network.chainId]; 35 | } 36 | 37 | static async fetchBalances(account, network, parseResults){ 38 | const networkString = await this.networkString(network); 39 | if(!networkString) return null; 40 | if(!hosts[networkString]) return null; 41 | 42 | if(cache[account.unique()]) return parseResults(cache[account.unique()]); 43 | 44 | return await Promise.race([ 45 | // Maximum timeout for this request 46 | new Promise(resolve => setTimeout(() => resolve(null), 8000)), 47 | 48 | fetch(`${hosts[networkString]}/api/balances/${networkString}/${account.name}`).then(r => r.json()).then(res => { 49 | 50 | // Caching this response, and then removing it after 5 seconds. 51 | // cache[account.unique()] = res; 52 | // setTimeout(() => delete cache[account.unique()], 5000); 53 | 54 | return parseResults(res); 55 | }).catch(err => { 56 | console.log('err', err); 57 | return null; 58 | }) 59 | ]) 60 | } 61 | 62 | static async balancesFor(account, network){ 63 | const parseResults = res => { 64 | return res.balances.map(balance => { 65 | return Token.fromJson({ 66 | blockchain:Blockchains.EOSIO, 67 | contract:balance.contract, 68 | symbol:balance.currency, 69 | name:balance.currency, 70 | amount:balance.amount, 71 | decimals:balance.decimals, 72 | chainId:network.chainId 73 | }) 74 | }); 75 | }; 76 | 77 | return this.fetchBalances(account, network, parseResults); 78 | } 79 | 80 | static async getAccountsFromPublicKey(publicKey, network){ 81 | const networkString = await this.networkString(network); 82 | if(!networkString) return null; 83 | if(!hosts[networkString]) return null; 84 | 85 | return await Promise.race([ 86 | // Maximum timeout for this request 87 | new Promise(resolve => setTimeout(() => resolve(null), 5000)), 88 | 89 | fetch(`${hosts[networkString]}/api/key/${publicKey}`).then(r => r.json()).then(res => { 90 | if(!res[networkString]) return null; 91 | const rawAccounts = res[networkString].accounts; 92 | let accounts = []; 93 | Object.keys(rawAccounts).map(name => { 94 | rawAccounts[name] 95 | .filter(acc => acc.auth.keys.some(({pubkey}) => pubkey === publicKey)) 96 | .map(acc => accounts.push({name, authority: acc.perm})) 97 | }); 98 | 99 | return accounts; 100 | }).catch(err => { 101 | console.error('err', err); 102 | return null; 103 | }) 104 | ]) 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /packages/eosio/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@walletpack/eosio", 3 | "version": "0.0.61", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/runtime": { 8 | "version": "7.6.0", 9 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.6.0.tgz", 10 | "integrity": "sha512-89eSBLJsxNxOERC0Op4vd+0Bqm6wRMqMbFtV3i0/fbaWw/mJ8Q3eBvgX0G4SyrOOLCtbu98HspF8o09MRT+KzQ==", 11 | "requires": { 12 | "regenerator-runtime": "^0.13.2" 13 | }, 14 | "dependencies": { 15 | "regenerator-runtime": { 16 | "version": "0.13.7", 17 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", 18 | "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" 19 | } 20 | } 21 | }, 22 | "babel-runtime": { 23 | "version": "6.26.0", 24 | "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", 25 | "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", 26 | "requires": { 27 | "core-js": "^2.4.0", 28 | "regenerator-runtime": "^0.11.0" 29 | } 30 | }, 31 | "base-x": { 32 | "version": "3.0.8", 33 | "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz", 34 | "integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==", 35 | "requires": { 36 | "safe-buffer": "^5.0.1" 37 | } 38 | }, 39 | "bigi": { 40 | "version": "1.4.2", 41 | "resolved": "https://registry.npmjs.org/bigi/-/bigi-1.4.2.tgz", 42 | "integrity": "sha1-nGZalfiLiwj8Bc/XMfVhhZ1yWCU=" 43 | }, 44 | "browserify-aes": { 45 | "version": "1.0.6", 46 | "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.0.6.tgz", 47 | "integrity": "sha1-Xncl297x/Vkw1OurSFZ85FHEigo=", 48 | "requires": { 49 | "buffer-xor": "^1.0.2", 50 | "cipher-base": "^1.0.0", 51 | "create-hash": "^1.1.0", 52 | "evp_bytestokey": "^1.0.0", 53 | "inherits": "^2.0.1" 54 | } 55 | }, 56 | "bs58": { 57 | "version": "4.0.1", 58 | "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", 59 | "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", 60 | "requires": { 61 | "base-x": "^3.0.2" 62 | } 63 | }, 64 | "buffer-xor": { 65 | "version": "1.0.3", 66 | "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", 67 | "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" 68 | }, 69 | "bytebuffer": { 70 | "version": "5.0.1", 71 | "resolved": "https://registry.npmjs.org/bytebuffer/-/bytebuffer-5.0.1.tgz", 72 | "integrity": "sha1-WC7qSxqHO20CCkjVjfhfC7ps/d0=", 73 | "requires": { 74 | "long": "~3" 75 | } 76 | }, 77 | "cipher-base": { 78 | "version": "1.0.4", 79 | "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", 80 | "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", 81 | "requires": { 82 | "inherits": "^2.0.1", 83 | "safe-buffer": "^5.0.1" 84 | } 85 | }, 86 | "core-js": { 87 | "version": "2.6.11", 88 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", 89 | "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" 90 | }, 91 | "create-hash": { 92 | "version": "1.1.3", 93 | "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", 94 | "integrity": "sha1-YGBCrIuSYnUPSDyt2rD1gZFy2P0=", 95 | "requires": { 96 | "cipher-base": "^1.0.1", 97 | "inherits": "^2.0.1", 98 | "ripemd160": "^2.0.0", 99 | "sha.js": "^2.4.0" 100 | } 101 | }, 102 | "create-hmac": { 103 | "version": "1.1.6", 104 | "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz", 105 | "integrity": "sha1-rLniIaThe9sHbpBlfEK5PjcmzwY=", 106 | "requires": { 107 | "cipher-base": "^1.0.3", 108 | "create-hash": "^1.1.0", 109 | "inherits": "^2.0.1", 110 | "ripemd160": "^2.0.0", 111 | "safe-buffer": "^5.0.1", 112 | "sha.js": "^2.4.8" 113 | } 114 | }, 115 | "ecurve": { 116 | "version": "1.0.5", 117 | "resolved": "https://registry.npmjs.org/ecurve/-/ecurve-1.0.5.tgz", 118 | "integrity": "sha1-0Ujo/lCmdPmDu1uuCdoOoj4QU14=", 119 | "requires": { 120 | "bigi": "^1.1.0" 121 | } 122 | }, 123 | "eosjs": { 124 | "version": "20.0.3", 125 | "resolved": "https://registry.npmjs.org/eosjs/-/eosjs-20.0.3.tgz", 126 | "integrity": "sha512-h0WSxsDo7AHz5IzpbQDrzyT/CYmbBDtFiiolTdtGd2FdKXEa6LDER4WbNp4qxCY7kD65KeG3knkGmOOFGbPJxw==", 127 | "requires": { 128 | "babel-runtime": "6.26.0", 129 | "eosjs-ecc": "4.0.7", 130 | "text-encoding": "0.7.0" 131 | }, 132 | "dependencies": { 133 | "eosjs-ecc": { 134 | "version": "4.0.7", 135 | "resolved": "https://registry.npmjs.org/eosjs-ecc/-/eosjs-ecc-4.0.7.tgz", 136 | "integrity": "sha512-uuqhqnrDy9XTpKfkhiZqRDUTCCI9oWBalVK5IosL7kpYwA9I3lm68INYFLyWsHpF2xwHqPql8MrMYJ3zfOn5Qg==", 137 | "requires": { 138 | "@babel/runtime": "7.6.0", 139 | "bigi": "1.4.2", 140 | "browserify-aes": "1.0.6", 141 | "bs58": "4.0.1", 142 | "bytebuffer": "5.0.1", 143 | "create-hash": "1.1.3", 144 | "create-hmac": "1.1.6", 145 | "ecurve": "1.0.5", 146 | "randombytes": "2.0.5" 147 | } 148 | } 149 | } 150 | }, 151 | "evp_bytestokey": { 152 | "version": "1.0.3", 153 | "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", 154 | "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", 155 | "requires": { 156 | "md5.js": "^1.3.4", 157 | "safe-buffer": "^5.1.1" 158 | } 159 | }, 160 | "hash-base": { 161 | "version": "3.1.0", 162 | "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", 163 | "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", 164 | "requires": { 165 | "inherits": "^2.0.4", 166 | "readable-stream": "^3.6.0", 167 | "safe-buffer": "^5.2.0" 168 | } 169 | }, 170 | "inherits": { 171 | "version": "2.0.4", 172 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 173 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 174 | }, 175 | "long": { 176 | "version": "3.2.0", 177 | "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz", 178 | "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=" 179 | }, 180 | "md5.js": { 181 | "version": "1.3.5", 182 | "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", 183 | "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", 184 | "requires": { 185 | "hash-base": "^3.0.0", 186 | "inherits": "^2.0.1", 187 | "safe-buffer": "^5.1.2" 188 | } 189 | }, 190 | "randombytes": { 191 | "version": "2.0.5", 192 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz", 193 | "integrity": "sha512-8T7Zn1AhMsQ/HI1SjcCfT/t4ii3eAqco3yOcSzS4mozsOz69lHLsoMXmF9nZgnFanYscnSlUSgs8uZyKzpE6kg==", 194 | "requires": { 195 | "safe-buffer": "^5.1.0" 196 | } 197 | }, 198 | "readable-stream": { 199 | "version": "3.6.0", 200 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 201 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 202 | "requires": { 203 | "inherits": "^2.0.3", 204 | "string_decoder": "^1.1.1", 205 | "util-deprecate": "^1.0.1" 206 | } 207 | }, 208 | "regenerator-runtime": { 209 | "version": "0.11.1", 210 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", 211 | "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" 212 | }, 213 | "ripemd160": { 214 | "version": "2.0.2", 215 | "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", 216 | "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", 217 | "requires": { 218 | "hash-base": "^3.0.0", 219 | "inherits": "^2.0.1" 220 | } 221 | }, 222 | "safe-buffer": { 223 | "version": "5.2.1", 224 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 225 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 226 | }, 227 | "sha.js": { 228 | "version": "2.4.11", 229 | "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", 230 | "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", 231 | "requires": { 232 | "inherits": "^2.0.1", 233 | "safe-buffer": "^5.0.1" 234 | } 235 | }, 236 | "string_decoder": { 237 | "version": "1.3.0", 238 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 239 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 240 | "requires": { 241 | "safe-buffer": "~5.2.0" 242 | } 243 | }, 244 | "text-encoding": { 245 | "version": "0.7.0", 246 | "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.7.0.tgz", 247 | "integrity": "sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA==" 248 | }, 249 | "util-deprecate": { 250 | "version": "1.0.2", 251 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 252 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 253 | } 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /packages/eosio/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@walletpack/eosio", 3 | "version": "0.0.67", 4 | "description": "> TODO: description", 5 | "author": "GetScatter Ltd. 2019", 6 | "homepage": "", 7 | "license": "ISC", 8 | "main": "eosio.js", 9 | "files": [ 10 | "dist", 11 | "prepare.js" 12 | ], 13 | "scripts": { 14 | "install": "node prepare.js", 15 | "test": "echo \"Error: run tests from root\" && exit 1" 16 | }, 17 | "dependencies": { 18 | "@walletpack/core": "^1.0.51", 19 | "eosjs": "^21.0.3", 20 | "eosjs-ecc": "^4.0.7" 21 | }, 22 | "gitHead": "70bd19f93b503618a79eb519eef082bfc40b16d7", 23 | "publishConfig": { 24 | "access": "public" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/eosio/prepare.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const rimraf = require("rimraf"); 4 | 5 | const paths = __dirname.split(path.sep); 6 | const parent = paths[paths.length-2]; 7 | 8 | if(parent === 'packages') return; 9 | 10 | try { 11 | rimraf.sync("./__tests__"); 12 | const files = fs.readdirSync(`./dist`); 13 | files.map(file => { 14 | if(fs.existsSync(`./${file}`)) rimraf.sync(`./${file}`); 15 | fs.renameSync(`./dist/${file}`, `./${file}`); 16 | }) 17 | rimraf.sync("./dist"); 18 | rimraf.sync("./lib"); 19 | } catch(e){ 20 | console.error('Walletpack prepare.js error', e); 21 | } 22 | -------------------------------------------------------------------------------- /packages/ethereum/README.md: -------------------------------------------------------------------------------- 1 | # `ethereum` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const ethereum = require('ethereum'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/ethereum/__tests__/ethereum.test.js: -------------------------------------------------------------------------------- 1 | // 'use strict'; 2 | // 3 | // const ethereum = require('..'); 4 | // 5 | // describe('ethereum', () => { 6 | // it('needs tests'); 7 | // }); 8 | -------------------------------------------------------------------------------- /packages/ethereum/lib/erc20.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "name", 6 | "outputs": [ 7 | { 8 | "name": "", 9 | "type": "string" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "constant": false, 18 | "inputs": [ 19 | { 20 | "name": "_spender", 21 | "type": "address" 22 | }, 23 | { 24 | "name": "_value", 25 | "type": "uint256" 26 | } 27 | ], 28 | "name": "approve", 29 | "outputs": [ 30 | { 31 | "name": "", 32 | "type": "bool" 33 | } 34 | ], 35 | "payable": false, 36 | "stateMutability": "nonpayable", 37 | "type": "function" 38 | }, 39 | { 40 | "constant": true, 41 | "inputs": [], 42 | "name": "totalSupply", 43 | "outputs": [ 44 | { 45 | "name": "", 46 | "type": "uint256" 47 | } 48 | ], 49 | "payable": false, 50 | "stateMutability": "view", 51 | "type": "function" 52 | }, 53 | { 54 | "constant": false, 55 | "inputs": [ 56 | { 57 | "name": "_from", 58 | "type": "address" 59 | }, 60 | { 61 | "name": "_to", 62 | "type": "address" 63 | }, 64 | { 65 | "name": "_value", 66 | "type": "uint256" 67 | } 68 | ], 69 | "name": "transferFrom", 70 | "outputs": [ 71 | { 72 | "name": "", 73 | "type": "bool" 74 | } 75 | ], 76 | "payable": false, 77 | "stateMutability": "nonpayable", 78 | "type": "function" 79 | }, 80 | { 81 | "constant": true, 82 | "inputs": [], 83 | "name": "decimals", 84 | "outputs": [ 85 | { 86 | "name": "", 87 | "type": "uint8" 88 | } 89 | ], 90 | "payable": false, 91 | "stateMutability": "view", 92 | "type": "function" 93 | }, 94 | { 95 | "constant": true, 96 | "inputs": [ 97 | { 98 | "name": "_owner", 99 | "type": "address" 100 | } 101 | ], 102 | "name": "balanceOf", 103 | "outputs": [ 104 | { 105 | "name": "balance", 106 | "type": "uint256" 107 | } 108 | ], 109 | "payable": false, 110 | "stateMutability": "view", 111 | "type": "function" 112 | }, 113 | { 114 | "constant": true, 115 | "inputs": [], 116 | "name": "symbol", 117 | "outputs": [ 118 | { 119 | "name": "", 120 | "type": "string" 121 | } 122 | ], 123 | "payable": false, 124 | "stateMutability": "view", 125 | "type": "function" 126 | }, 127 | { 128 | "constant": false, 129 | "inputs": [ 130 | { 131 | "name": "_to", 132 | "type": "address" 133 | }, 134 | { 135 | "name": "_value", 136 | "type": "uint256" 137 | } 138 | ], 139 | "name": "transfer", 140 | "outputs": [ 141 | { 142 | "name": "", 143 | "type": "bool" 144 | } 145 | ], 146 | "payable": false, 147 | "stateMutability": "nonpayable", 148 | "type": "function" 149 | }, 150 | { 151 | "constant": true, 152 | "inputs": [ 153 | { 154 | "name": "_owner", 155 | "type": "address" 156 | }, 157 | { 158 | "name": "_spender", 159 | "type": "address" 160 | } 161 | ], 162 | "name": "allowance", 163 | "outputs": [ 164 | { 165 | "name": "", 166 | "type": "uint256" 167 | } 168 | ], 169 | "payable": false, 170 | "stateMutability": "view", 171 | "type": "function" 172 | }, 173 | { 174 | "payable": true, 175 | "stateMutability": "payable", 176 | "type": "fallback" 177 | }, 178 | { 179 | "anonymous": false, 180 | "inputs": [ 181 | { 182 | "indexed": true, 183 | "name": "owner", 184 | "type": "address" 185 | }, 186 | { 187 | "indexed": true, 188 | "name": "spender", 189 | "type": "address" 190 | }, 191 | { 192 | "indexed": false, 193 | "name": "value", 194 | "type": "uint256" 195 | } 196 | ], 197 | "name": "Approval", 198 | "type": "event" 199 | }, 200 | { 201 | "anonymous": false, 202 | "inputs": [ 203 | { 204 | "indexed": true, 205 | "name": "from", 206 | "type": "address" 207 | }, 208 | { 209 | "indexed": true, 210 | "name": "to", 211 | "type": "address" 212 | }, 213 | { 214 | "indexed": false, 215 | "name": "value", 216 | "type": "uint256" 217 | } 218 | ], 219 | "name": "Transfer", 220 | "type": "event" 221 | } 222 | ] -------------------------------------------------------------------------------- /packages/ethereum/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@walletpack/ethereum", 3 | "version": "0.0.61", 4 | "description": "> TODO: description", 5 | "author": "GetScatter Ltd. 2019", 6 | "homepage": "", 7 | "license": "ISC", 8 | "main": "ethereum.js", 9 | "files": [ 10 | "dist", 11 | "prepare.js" 12 | ], 13 | "scripts": { 14 | "install": "node prepare.js", 15 | "test": "echo \"Error: run tests from root\" && exit 1" 16 | }, 17 | "dependencies": { 18 | "@walletpack/core": "^1.0.51", 19 | "ethereumjs-tx": "^2.1.0", 20 | "ethereumjs-util": "^6.1.0", 21 | "web3": "^1.2.0", 22 | "web3-provider-engine": "^15.0.4" 23 | }, 24 | "gitHead": "70bd19f93b503618a79eb519eef082bfc40b16d7", 25 | "publishConfig": { 26 | "access": "public" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/ethereum/prepare.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const rimraf = require("rimraf"); 4 | 5 | const paths = __dirname.split(path.sep); 6 | const parent = paths[paths.length-2]; 7 | 8 | if(parent === 'packages') return; 9 | 10 | try { 11 | rimraf.sync("./__tests__"); 12 | const files = fs.readdirSync(`./dist`); 13 | files.map(file => { 14 | if(fs.existsSync(`./${file}`)) rimraf.sync(`./${file}`); 15 | fs.renameSync(`./dist/${file}`, `./${file}`); 16 | }) 17 | rimraf.sync("./dist"); 18 | rimraf.sync("./lib"); 19 | } catch(e){ 20 | console.error('Walletpack prepare.js error', e); 21 | } 22 | -------------------------------------------------------------------------------- /packages/fio/README.md: -------------------------------------------------------------------------------- 1 | # `fio` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const fio = require('fio'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/fio/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@walletpack/fio", 3 | "version": "0.0.29", 4 | "description": "> TODO: description", 5 | "author": "GetScatter Ltd. 2019", 6 | "homepage": "", 7 | "license": "ISC", 8 | "main": "fio.js", 9 | "files": [ 10 | "dist", 11 | "prepare.js" 12 | ], 13 | "scripts": { 14 | "install": "node prepare.js", 15 | "test": "echo \"Error: run tests from root\" && exit 1" 16 | }, 17 | "dependencies": { 18 | "@fioprotocol/fiojs": "^1.0.1", 19 | "@walletpack/core": "^1.0.51" 20 | }, 21 | "devDependencies": { 22 | "@fioprotocol/fiosdk": "latest" 23 | }, 24 | "publishConfig": { 25 | "access": "public" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/fio/prepare.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const rimraf = require("rimraf"); 4 | 5 | const paths = __dirname.split(path.sep); 6 | const parent = paths[paths.length-2]; 7 | 8 | if(parent === 'packages') return; 9 | 10 | try { 11 | rimraf.sync("./__tests__"); 12 | const files = fs.readdirSync(`./dist`); 13 | files.map(file => { 14 | if(fs.existsSync(`./${file}`)) rimraf.sync(`./${file}`); 15 | fs.renameSync(`./dist/${file}`, `./${file}`); 16 | }) 17 | rimraf.sync("./dist"); 18 | rimraf.sync("./lib"); 19 | } catch(e){ 20 | console.error('Walletpack prepare.js error', e); 21 | } 22 | -------------------------------------------------------------------------------- /packages/fio/yarn-error.log: -------------------------------------------------------------------------------- 1 | Arguments: 2 | C:\Program Files\nodejs\node.exe C:\Program Files (x86)\Yarn\bin\yarn.js add @fioprotocol/fiojs 3 | 4 | PATH: 5 | C:\Python27\;C:\Python27\Scripts;C:\Program Files (x86)\Common Files\Intel\Shared Libraries\redist\intel64\compiler;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\Program Files\Git\cmd;C:\WINDOWS\System32\OpenSSH\;C:\Program Files (x86)\Yarn\bin\;C:\Users\nsjames\AppData\Roaming\nvm;C:\Program Files\nodejs;C:\Program Files\nodejs\;C:\ProgramData\chocolatey\bin;C:\Program Files\Java\jdk1.8.0_221\bin;C:\Program Files\SafeNet\Authentication\SAC\x64;C:\Program Files\SafeNet\Authentication\SAC\x32;C:\Program Files\NVIDIA Corporation\NVIDIA NvDLISR;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Users\nsjames\.windows-build-tools\python27\;C:\Program Files\nodejs\node_modules\npm\bin\node-gyp-bin;C:\Users\nsjames\AppData\Roaming\npm\node_modules\windows-build-tools\node_modules\.bin;C:\Users\nsjames\AppData\Roaming\npm\node_modules\.bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Program Files\Git\cmd;C:\Program Files\Git\mingw64\bin;C:\Program Files\Git\usr\bin;C:\Program Files\nodejs\;C:\Program Files (x86)\Yarn\bin\;C:\Users\nsjames\AppData\Local\Microsoft\WindowsApps;C:\Users\nsjames\AppData\Local\Microsoft\WindowsApps;C:\Users\nsjames\AppData\Local\Yarn\bin;C:\Users\nsjames\AppData\Roaming\nvm;C:\Program Files\nodejs;C:\Users\nsjames\AppData\Roaming\npm;C:\kubernetes;;C:\Program Files\Docker Toolbox 6 | 7 | Yarn version: 8 | 1.12.3 9 | 10 | Node version: 11 | 10.15.3 12 | 13 | Platform: 14 | win32 x64 15 | 16 | Trace: 17 | SyntaxError: C:\Work\Libraries\walletpack\packages\fio\package.json: Unexpected token } in JSON at position 405 18 | at JSON.parse () 19 | at C:\Program Files (x86)\Yarn\lib\cli.js:1631:59 20 | at Generator.next () 21 | at step (C:\Program Files (x86)\Yarn\lib\cli.js:304:30) 22 | at C:\Program Files (x86)\Yarn\lib\cli.js:315:13 23 | 24 | npm manifest: 25 | { 26 | "name": "@walletpack/fio", 27 | "version": "0.0.1", 28 | "description": "> TODO: description", 29 | "author": "GetScatter Ltd. 2019", 30 | "homepage": "", 31 | "license": "ISC", 32 | "main": "fio.js", 33 | "files": [ 34 | "dist", 35 | "prepare.js" 36 | ], 37 | "scripts": { 38 | "install": "node prepare.js", 39 | "test": "echo \"Error: run tests from root\" && exit 1" 40 | }, 41 | "dependencies": { 42 | "@walletpack/core": "^1.0.41", 43 | }, 44 | "publishConfig": { 45 | "access": "public" 46 | } 47 | } 48 | 49 | yarn manifest: 50 | No manifest 51 | 52 | Lockfile: 53 | No lockfile 54 | -------------------------------------------------------------------------------- /packages/tron/README.md: -------------------------------------------------------------------------------- 1 | # `tron` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const tron = require('tron'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/tron/__tests__/tron.test.js: -------------------------------------------------------------------------------- 1 | // 'use strict'; 2 | // 3 | // import Account from "@walletpack/core/lib/models/Account"; 4 | // import Network from "@walletpack/core/lib/models/Network"; 5 | // import Keypair from "@walletpack/core/lib/models/Keypair"; 6 | // import {Blockchains} from "@walletpack/core/lib/models/Blockchains"; 7 | // import Token from "@walletpack/core/lib/models/Token"; 8 | // 9 | // const tron = new (require('../lib/tron').default)(); 10 | // 11 | // const keypair = Keypair.fromJson({ 12 | // name:'Testing key', 13 | // // Just a known address with ANTE tokens. 14 | // // publicKeys:[{blockchain:Blockchains.TRX, key:'TFKSq1F1RBhqmLjqktcRk74YpMDGCDQAeX'}], 15 | // publicKeys:[{blockchain:Blockchains.TRX, key:'TF2quv1hTipcZ8FJ8FRsXXLSiJ1C15dqkW'}], 16 | // privateKey:'...' 17 | // }) 18 | // 19 | // const network = Network.fromJson({ 20 | // "name":"Tron Mainnet", 21 | // "host":"api.trongrid.io", 22 | // "port":443, 23 | // "protocol":"https", 24 | // "chainId":"1" 25 | // }) 26 | // 27 | // const account = Account.fromJson({ 28 | // keypairUnique:keypair.unique(), 29 | // networkUnique:network.unique(), 30 | // publicKey:keypair.publicKeys[0].key, 31 | // }); 32 | // 33 | // // Testing with TRONbet's ANTE token: 34 | // // https://www.trontokens.org/token/trc20/TRONbet/TCN77KWWyUyi2A4Cu7vrh5dnmRyvUuME1E 35 | // const token = Token.fromJson({ 36 | // contract:'TCN77KWWyUyi2A4Cu7vrh5dnmRyvUuME1E', 37 | // blockchain:Blockchains.TRX, 38 | // symbol:'ANTE', 39 | // decimals:6, 40 | // chainId:network.chainId 41 | // }) 42 | // 43 | // // Removing need for StoreService's state 44 | // account.network = () => network; 45 | // account.sendable = () => account.publicKey; 46 | // 47 | // describe('tron', () => { 48 | // it('should be able to init', done => { 49 | // new Promise(async() => { 50 | // tron.init(); 51 | // done(); 52 | // }) 53 | // }); 54 | // 55 | // it('should be able to get trc20 balances', done => { 56 | // new Promise(async() => { 57 | // const balances = await tron.balancesFor(account, [token]); 58 | // console.log('balances', balances); 59 | // done(); 60 | // }) 61 | // }); 62 | // 63 | // // it('should be able to parse trc20 transactions', done => { 64 | // // new Promise(async() => { 65 | // // 66 | // // const json = `transaction {"transaction":{"transaction":{},"participants":["TF2quv1hTipcZ8FJ8FRsXXLSiJ1C15dqkW"]},"blockchain":"trx","network":{"id":"216730975559","name":"Tron Mainnet","protocol":"https","host":"api.trongrid.io","port":443,"path":"","blockchain":"trx","chainId":"1","fromOrigin":null,"createdAt":1571601826773,"token":null},"requiredFields":{}}`; 67 | // // 68 | // // const transfer = await tron.transfer({ 69 | // // account, 70 | // // // Random address 71 | // // to:'TU9Rpk8YqTea5oYx1h26a2P6vsGn8faRBt', 72 | // // amount:'100', 73 | // // token, 74 | // // promptForSignature:false 75 | // // }); 76 | // // console.log('transfer', transfer); 77 | // // done(); 78 | // // }) 79 | // // }); 80 | // 81 | // // it('should be able to send trc20 tokens', done => { 82 | // // new Promise(async() => { 83 | // // const transfer = await tron.transfer({ 84 | // // account, 85 | // // // Random address 86 | // // to:'TU9Rpk8YqTea5oYx1h26a2P6vsGn8faRBt', 87 | // // amount:'1', 88 | // // token, 89 | // // promptForSignature:false 90 | // // }); 91 | // // console.log('transfer', transfer); 92 | // // done(); 93 | // // }) 94 | // // }); 95 | // }); 96 | -------------------------------------------------------------------------------- /packages/tron/lib/trc20.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": false, 4 | "inputs": [ 5 | { 6 | "name": "spender", 7 | "type": "address" 8 | }, 9 | { 10 | "name": "value", 11 | "type": "uint256" 12 | } 13 | ], 14 | "name": "approve", 15 | "outputs": [ 16 | { 17 | "name": "", 18 | "type": "bool" 19 | } 20 | ], 21 | "payable": false, 22 | "stateMutability": "nonpayable", 23 | "type": "function", 24 | "signature": "0x095ea7b3" 25 | }, 26 | { 27 | "constant": true, 28 | "inputs": [], 29 | "name": "totalSupply", 30 | "outputs": [ 31 | { 32 | "name": "", 33 | "type": "uint256" 34 | } 35 | ], 36 | "payable": false, 37 | "stateMutability": "view", 38 | "type": "function", 39 | "signature": "0x18160ddd" 40 | }, 41 | { 42 | "constant": false, 43 | "inputs": [ 44 | { 45 | "name": "from", 46 | "type": "address" 47 | }, 48 | { 49 | "name": "to", 50 | "type": "address" 51 | }, 52 | { 53 | "name": "value", 54 | "type": "uint256" 55 | } 56 | ], 57 | "name": "transferFrom", 58 | "outputs": [ 59 | { 60 | "name": "", 61 | "type": "bool" 62 | } 63 | ], 64 | "payable": false, 65 | "stateMutability": "nonpayable", 66 | "type": "function", 67 | "signature": "0x23b872dd" 68 | }, 69 | { 70 | "constant": false, 71 | "inputs": [ 72 | { 73 | "name": "spender", 74 | "type": "address" 75 | }, 76 | { 77 | "name": "addedValue", 78 | "type": "uint256" 79 | } 80 | ], 81 | "name": "increaseAllowance", 82 | "outputs": [ 83 | { 84 | "name": "", 85 | "type": "bool" 86 | } 87 | ], 88 | "payable": false, 89 | "stateMutability": "nonpayable", 90 | "type": "function", 91 | "signature": "0x39509351" 92 | }, 93 | { 94 | "constant": true, 95 | "inputs": [ 96 | { 97 | "name": "owner", 98 | "type": "address" 99 | } 100 | ], 101 | "name": "balanceOf", 102 | "outputs": [ 103 | { 104 | "name": "", 105 | "type": "uint256" 106 | } 107 | ], 108 | "payable": false, 109 | "stateMutability": "view", 110 | "type": "function", 111 | "signature": "0x70a08231" 112 | }, 113 | { 114 | "constant": false, 115 | "inputs": [ 116 | { 117 | "name": "spender", 118 | "type": "address" 119 | }, 120 | { 121 | "name": "subtractedValue", 122 | "type": "uint256" 123 | } 124 | ], 125 | "name": "decreaseAllowance", 126 | "outputs": [ 127 | { 128 | "name": "", 129 | "type": "bool" 130 | } 131 | ], 132 | "payable": false, 133 | "stateMutability": "nonpayable", 134 | "type": "function", 135 | "signature": "0xa457c2d7" 136 | }, 137 | { 138 | "constant": false, 139 | "inputs": [ 140 | { 141 | "name": "to", 142 | "type": "address" 143 | }, 144 | { 145 | "name": "value", 146 | "type": "uint256" 147 | } 148 | ], 149 | "name": "transfer", 150 | "outputs": [ 151 | { 152 | "name": "", 153 | "type": "bool" 154 | } 155 | ], 156 | "payable": false, 157 | "stateMutability": "nonpayable", 158 | "type": "function", 159 | "signature": "0xa9059cbb" 160 | }, 161 | { 162 | "constant": true, 163 | "inputs": [ 164 | { 165 | "name": "owner", 166 | "type": "address" 167 | }, 168 | { 169 | "name": "spender", 170 | "type": "address" 171 | } 172 | ], 173 | "name": "allowance", 174 | "outputs": [ 175 | { 176 | "name": "", 177 | "type": "uint256" 178 | } 179 | ], 180 | "payable": false, 181 | "stateMutability": "view", 182 | "type": "function", 183 | "signature": "0xdd62ed3e" 184 | }, 185 | { 186 | "inputs": [ 187 | { 188 | "name": "name", 189 | "type": "string" 190 | }, 191 | { 192 | "name": "symbol", 193 | "type": "string" 194 | }, 195 | { 196 | "name": "decimals", 197 | "type": "uint8" 198 | }, 199 | { 200 | "name": "cap", 201 | "type": "uint256" 202 | } 203 | ], 204 | "payable": false, 205 | "stateMutability": "nonpayable", 206 | "type": "constructor", 207 | "signature": "constructor" 208 | }, 209 | { 210 | "anonymous": false, 211 | "inputs": [ 212 | { 213 | "indexed": true, 214 | "name": "from", 215 | "type": "address" 216 | }, 217 | { 218 | "indexed": true, 219 | "name": "to", 220 | "type": "address" 221 | }, 222 | { 223 | "indexed": false, 224 | "name": "value", 225 | "type": "uint256" 226 | } 227 | ], 228 | "name": "Transfer", 229 | "type": "event", 230 | "signature": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" 231 | }, 232 | { 233 | "anonymous": false, 234 | "inputs": [ 235 | { 236 | "indexed": true, 237 | "name": "owner", 238 | "type": "address" 239 | }, 240 | { 241 | "indexed": true, 242 | "name": "spender", 243 | "type": "address" 244 | }, 245 | { 246 | "indexed": false, 247 | "name": "value", 248 | "type": "uint256" 249 | } 250 | ], 251 | "name": "Approval", 252 | "type": "event", 253 | "signature": "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925" 254 | }, 255 | { 256 | "constant": true, 257 | "inputs": [], 258 | "name": "name", 259 | "outputs": [ 260 | { 261 | "name": "", 262 | "type": "string" 263 | } 264 | ], 265 | "payable": false, 266 | "stateMutability": "view", 267 | "type": "function", 268 | "signature": "0x06fdde03" 269 | }, 270 | { 271 | "constant": true, 272 | "inputs": [], 273 | "name": "symbol", 274 | "outputs": [ 275 | { 276 | "name": "", 277 | "type": "string" 278 | } 279 | ], 280 | "payable": false, 281 | "stateMutability": "view", 282 | "type": "function", 283 | "signature": "0x95d89b41" 284 | }, 285 | { 286 | "constant": true, 287 | "inputs": [], 288 | "name": "decimals", 289 | "outputs": [ 290 | { 291 | "name": "", 292 | "type": "uint8" 293 | } 294 | ], 295 | "payable": false, 296 | "stateMutability": "view", 297 | "type": "function", 298 | "signature": "0x313ce567" 299 | } 300 | ] 301 | -------------------------------------------------------------------------------- /packages/tron/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@walletpack/tron", 3 | "version": "0.0.63", 4 | "description": "> TODO: description", 5 | "author": "GetScatter Ltd. 2019", 6 | "homepage": "", 7 | "license": "ISC", 8 | "main": "tron.js", 9 | "files": [ 10 | "dist", 11 | "prepare.js" 12 | ], 13 | "scripts": { 14 | "install": "node prepare.js", 15 | "test": "echo \"Error: run tests from root\" && exit 1" 16 | }, 17 | "dependencies": { 18 | "@walletpack/core": "^1.0.51", 19 | "elliptic": "^6.5.2", 20 | "ethereumjs-util": "^6.1.0", 21 | "tronweb": "^2.8.0" 22 | }, 23 | "gitHead": "70bd19f93b503618a79eb519eef082bfc40b16d7", 24 | "publishConfig": { 25 | "access": "public" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/tron/prepare.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const rimraf = require("rimraf"); 4 | 5 | const paths = __dirname.split(path.sep); 6 | const parent = paths[paths.length-2]; 7 | 8 | if(parent === 'packages') return; 9 | 10 | try { 11 | rimraf.sync("./__tests__"); 12 | const files = fs.readdirSync(`./dist`); 13 | files.map(file => { 14 | if(fs.existsSync(`./${file}`)) rimraf.sync(`./${file}`); 15 | fs.renameSync(`./dist/${file}`, `./${file}`); 16 | }) 17 | rimraf.sync("./dist"); 18 | rimraf.sync("./lib"); 19 | } catch(e){ 20 | console.error('Walletpack prepare.js error', e); 21 | } 22 | -------------------------------------------------------------------------------- /scripts/copy-files.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const packages = fs.readdirSync(`./packages`); 5 | packages.map(pack => { 6 | const jsons = fs.readdirSync(`./packages/${pack}/lib`).filter(x => x.indexOf('.json') > -1); 7 | jsons.map(json => { 8 | fs.copyFileSync(`./packages/${pack}/lib/${json}`, `./packages/${pack}/dist/${json}`) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /scripts/prepare.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const rimraf = require("rimraf"); 4 | 5 | const paths = __dirname.split(path.sep); 6 | const parent = paths[paths.length-2]; 7 | 8 | if(parent === 'packages') return; 9 | 10 | try { 11 | rimraf.sync("./__tests__"); 12 | const files = fs.readdirSync(`./dist`); 13 | files.map(file => { 14 | if(fs.existsSync(`./${file}`)) rimraf.sync(`./${file}`); 15 | fs.renameSync(`./dist/${file}`, `./${file}`); 16 | }) 17 | rimraf.sync("./dist"); 18 | rimraf.sync("./lib"); 19 | } catch(e){ 20 | console.error('Walletpack prepare.js error', e); 21 | } 22 | -------------------------------------------------------------------------------- /scripts/prepublish.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const packages = fs.readdirSync('./packages'); 3 | packages.map(pdir => fs.copyFileSync('./scripts/prepare.js', `./packages/${pdir}/prepare.js`)); -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack') 3 | const fs = require('fs'); 4 | 5 | const getPackagePath = x => `./packages/${x}/src/index.js`; 6 | const packageFiles = fs.readdirSync('./packages'); 7 | const entry = packageFiles.reduce((o, file) => Object.assign(o, {[`${file}.min.js`]: getPackagePath(file)}), {}); 8 | 9 | module.exports = { 10 | entry, 11 | output: { 12 | path: path.resolve(__dirname, './bundles'), 13 | filename: 'walletpack-[name]' 14 | }, 15 | resolve: { 16 | modules:[ 17 | "node_modules" 18 | ] 19 | }, 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.js$/, 24 | use: { 25 | loader: 'babel-loader', 26 | options: { 27 | presets: [ 28 | '@babel/preset-env' 29 | ], 30 | plugins:[ 31 | '@babel/plugin-transform-runtime' 32 | ] 33 | } 34 | }, 35 | exclude: /node_modules/ 36 | } 37 | ], 38 | }, 39 | plugins: [ 40 | 41 | ], 42 | stats: { colors: true }, 43 | // devtool: false, 44 | devtool: 'inline-source-map', 45 | externals: { 46 | '@walletpack/core': 'WalletPack' 47 | } 48 | } 49 | --------------------------------------------------------------------------------