├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── frontend ├── .eslintignore ├── .meteor │ ├── .finished-upgraders │ ├── .gitignore │ ├── .id │ ├── packages │ ├── platforms │ ├── release │ └── versions ├── client │ ├── main.js │ ├── style │ │ ├── index.css │ │ └── style.scss │ └── templates │ │ └── head.html ├── imports │ ├── api │ │ ├── limits.js │ │ ├── offers.js │ │ ├── tokenEvents.js │ │ ├── tokens.js │ │ ├── transactions.js │ │ ├── weth.js │ │ └── wgnt.js │ ├── startup │ │ └── client │ │ │ ├── index.js │ │ │ └── network.js │ ├── test │ │ └── dapple-token-spy.js │ ├── ui │ │ └── client │ │ │ ├── footer.html │ │ │ ├── headers │ │ │ ├── accountselector.html │ │ │ ├── accountselector.js │ │ │ ├── balance.html │ │ │ ├── balance.js │ │ │ ├── currencyselector.html │ │ │ ├── currencyselector.js │ │ │ ├── marketdetails.html │ │ │ ├── messages.html │ │ │ ├── messages.js │ │ │ ├── networkstatus.html │ │ │ ├── tabs.html │ │ │ ├── tabs.js │ │ │ └── volumes.html │ │ │ ├── helpers.js │ │ │ ├── index.html │ │ │ ├── noaccount.html │ │ │ ├── noaccount.js │ │ │ ├── noethereum.html │ │ │ ├── shared.js │ │ │ ├── whatisthis.html │ │ │ └── widgets │ │ │ ├── cancelmodal.html │ │ │ ├── cancelmodal.js │ │ │ ├── chart.html │ │ │ ├── chart.js │ │ │ ├── depositbalance.html │ │ │ ├── depositbalance.js │ │ │ ├── ethtokens.html │ │ │ ├── ethtokens.js │ │ │ ├── ethtokens.test.js │ │ │ ├── gnttokens.html │ │ │ ├── gnttokens.js │ │ │ ├── history.html │ │ │ ├── history.js │ │ │ ├── lasttrades.html │ │ │ ├── lasttrades.js │ │ │ ├── maindeposit.html │ │ │ ├── maindeposit.js │ │ │ ├── maintrades.html │ │ │ ├── maintransfer.html │ │ │ ├── maintransfer.js │ │ │ ├── markets.html │ │ │ ├── markets.js │ │ │ ├── myorders.html │ │ │ ├── myorders.js │ │ │ ├── newallowance.html │ │ │ ├── newallowance.js │ │ │ ├── neworder.html │ │ │ ├── neworder.js │ │ │ ├── offermodal.html │ │ │ ├── offermodal.js │ │ │ ├── orderbook.html │ │ │ ├── orderrow.html │ │ │ ├── orderrow.js │ │ │ ├── orders.html │ │ │ ├── orders.js │ │ │ ├── progress-bar.html │ │ │ ├── progress-bar.js │ │ │ ├── progressblock.html │ │ │ ├── progressblock.js │ │ │ ├── redeemer-modal.html │ │ │ ├── redeemer-modal.js │ │ │ ├── sendtokens.html │ │ │ ├── sendtokens.js │ │ │ ├── transferconfirmation.html │ │ │ ├── transferconfirmation.js │ │ │ ├── wrapper-update.html │ │ │ └── wrapper-update.js │ └── utils │ │ ├── Chart.min.js │ │ ├── conversion.js │ │ ├── functions.js │ │ └── redeemer.js ├── package.json ├── packages │ └── dapple │ │ ├── config.json │ │ ├── contracts-abi │ │ ├── ds-eth-token.js │ │ ├── expiring-market.js │ │ ├── maker-otc.js │ │ ├── matching-market.js │ │ ├── simple-market.js │ │ └── token-wrapper.js │ │ ├── package-post-init.js │ │ ├── package-pre-init.js │ │ └── package.js └── public │ ├── apple-touch-icon-114x114.png │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-144x144.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-57x57.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-72x72.png │ ├── apple-touch-icon-76x76.png │ ├── clock.svg │ ├── close_x.svg │ ├── counter_1.svg │ ├── counter_2.svg │ ├── counter_3.svg │ ├── cross_normal.svg │ ├── cross_pressed.svg │ ├── dapphub_icn_metamask.svg │ ├── dot_red.svg │ ├── eth_circle_icon.svg │ ├── eth_circle_icon_full.svg │ ├── ethereum-logo.svg │ ├── favicon-128.png │ ├── favicon-16x16.png │ ├── favicon-196x196.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── favicon.ico │ ├── fonts │ ├── Montserrat-Medium.woff │ ├── Montserrat-Medium.woff2 │ ├── Montserrat-SemiBold.woff │ └── Montserrat-SemiBold.woff2 │ ├── ic_add_circle_24px.svg │ ├── ic_compare_arrows_black_24px.svg │ ├── ic_remove_24px.svg │ ├── loading.svg │ ├── loadingLarge.svg │ ├── logo-oasis-hover.png │ ├── logo-oasis.png │ ├── logo.svg │ ├── maker_circle_icon.svg │ ├── maker_circle_icon_full.svg │ ├── metamask-logo.svg │ ├── mist-logo.svg │ ├── mstile-144x144.png │ ├── mstile-150x150.png │ ├── mstile-310x150.png │ ├── mstile-310x310.png │ ├── mstile-70x70.png │ ├── myallowance-maximum.svg │ ├── myallowance-personal.svg │ ├── order-warning-red.svg │ ├── order-warning.svg │ └── remove_button.svg ├── gulpfile.js └── package.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw* 2 | .DS_Store 3 | .publish/ 4 | .vscode/ 5 | .idea 6 | *.iml 7 | scripts/blockchain/tmp 8 | dist/ 9 | node_modules/ 10 | npm-debug.log 11 | nohup.out 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Oasis Header](https://cloud.githubusercontent.com/assets/5337809/24866427/5f73a6fc-1e0a-11e7-90df-afff1def7c3a.png)]() 2 | --- 3 | [![Stories in Ready](https://badge.waffle.io/MakerDAO/maker-market.png?label=ready&title=Ready)](https://waffle.io/MakerDAO/maker-market) 4 | [![Build Status](https://api.travis-ci.org/makerdao/maker-market.svg?branch=master)](https://travis-ci.org/makerdao/maker-market) 5 | 6 | 7 | This is a simple on-chain OTC market for ERC20 Standard Tokens on the Ethereum Blockchain. You can either pick an order from the order book (in which case delivery will happen instantly), or submit a new order yourself. 8 | 9 | **Oasis is undergoing alpha testing: Proceed at your own risk, and use only small amounts of ETH and MKR.** 10 | 11 | ## Overview 12 | 13 | This dapp uses Meteor as frontend; the contract side can be tested and deployed using dapple. 14 | 15 | ## Usage (for Users) 16 | 17 | Ensure you have a locally running ethereum node. 18 | 19 | ## Installation (for Developers) 20 | 21 | Requirements: 22 | 23 | * geth `brew install ethereum` (or [`apt-get` for ubuntu](https://github.com/ethereum/go-ethereum/wiki/Installation-Instructions-for-Ubuntu)) 24 | * solidity https://solidity.readthedocs.org/en/latest/installing-solidity.html 25 | * meteor `curl https://install.meteor.com/ | sh` 26 | * meteor-build-client, `npm install -g meteor-build-client` 27 | 28 | Clone and install: 29 | 30 | ```bash 31 | git clone https://github.com/OasisDEX/oasis.git 32 | cd oasis 33 | npm install 34 | ``` 35 | 36 | ## Usage (for Developers) 37 | 38 | To run the frontend, start meteor: 39 | 40 | ```bash 41 | cd frontend 42 | npm install 43 | meteor 44 | ``` 45 | 46 | You can access the user interface on [http://localhost:3000/](http://localhost:3000/) 47 | 48 | To deploy the frontend to Github Pages: 49 | 50 | ```bash 51 | gulp deploy 52 | ``` 53 | 54 | ## Development 55 | 56 | This project uses the [AirBnB style guide](https://github.com/airbnb/javascript) for coding standard guidelines. 57 | We use [ESLint](http://eslint.org/docs/user-guide/getting-started) to automatically check for common code problems or style errors. 58 | There's an eslintConfig section in frontend/package.json for the configuration of ESLint. 59 | You can run the linter with: 60 | 61 | ```bash 62 | cd frontend 63 | meteor npm run lint 64 | ``` 65 | 66 | ## License 67 | This project is licensed under the terms of the Apache License 2.0. 68 | 69 | ## TODOs 70 | See https://waffle.io/MakerDAO/maker-market 71 | 72 | ## Acknowledgements 73 | * Simple Market contract by [Nikolai Mushegian](https://github.com/nmushegian) 74 | * User interface design by [Daniel Brockman](https://github.com/dbrock) 75 | * Blockchain Script by [Chris Hitchcott](https://github.com/hitchcott) 76 | -------------------------------------------------------------------------------- /frontend/.eslintignore: -------------------------------------------------------------------------------- 1 | packages/dapple/package.js 2 | packages/dapple/package-pre-init.js 3 | packages/dapple/package-post-init.js 4 | packages/dapple/maker.js 5 | packages/dapple/contracts-abi/maker-otc.js 6 | packages/dapple/contracts-abi/token-wrapper.js 7 | packages/dapple/contracts-abi/ds-eth-token.js 8 | imports/utils/Chart.min.js 9 | -------------------------------------------------------------------------------- /frontend/.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | 1.2.0-standard-minifiers-package 10 | 1.2.0-meteor-platform-split 11 | 1.2.0-cordova-changes 12 | 1.2.0-breaking-changes 13 | 1.3.0-split-minifiers-package 14 | 1.4.0-remove-old-dev-bundle-link 15 | 1.4.1-add-shell-server-package 16 | -------------------------------------------------------------------------------- /frontend/.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | dev_bundle 2 | local 3 | -------------------------------------------------------------------------------- /frontend/.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | 133f4p51avjp8h1qfjf57 8 | -------------------------------------------------------------------------------- /frontend/.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # Check this file (and the other files in this directory) into your repository. 3 | # 4 | # 'meteor add' and 'meteor remove' will edit this file for you, 5 | # but you can also edit it by hand. 6 | 7 | meteor-base@1.0.4 # Packages every Meteor app needs to have 8 | mobile-experience@1.0.4 # Packages for a great mobile UX 9 | mongo@1.1.14 # The database Meteor supports right now 10 | blaze-html-templates@1.0.4 # Compile .html files into Meteor Blaze views 11 | session@1.1.7 # Client-side reactive dictionary for your app 12 | jquery@1.11.10 # Helpful client-side library 13 | tracker@1.1.1 # Meteor's client-side reactive programming library 14 | 15 | es5-shim@4.6.15 # ECMAScript 5 compatibility for older browsers. 16 | ecmascript@0.6.1 # Enable ECMAScript2015+ syntax in app code 17 | 18 | ethereum:web3 19 | makerotc:dapple 20 | numeral:numeral 21 | dburles:collection-helpers 22 | ethereum:tools 23 | manuel:viewmodel 24 | twbs:bootstrap 25 | practicalmeteor:mocha 26 | velocity:meteor-stubs 27 | hwillson:stub-collections 28 | dispatch:mocha-phantomjs 29 | standard-minifier-css@1.3.2 30 | standard-minifier-js@1.2.1 31 | shell-server@0.2.1 32 | fourseven:scss 33 | momentjs:moment 34 | mikowals:batch-insert 35 | -------------------------------------------------------------------------------- /frontend/.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /frontend/.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.4.2.3 2 | -------------------------------------------------------------------------------- /frontend/.meteor/versions: -------------------------------------------------------------------------------- 1 | 3stack:bignumber@2.0.7 2 | allow-deny@1.0.5 3 | amplify@1.0.0 4 | autoupdate@1.3.12 5 | babel-compiler@6.13.0 6 | babel-runtime@1.0.1 7 | base64@1.0.10 8 | binary-heap@1.0.10 9 | blaze@2.1.9 10 | blaze-html-templates@1.0.5 11 | blaze-tools@1.0.10 12 | boilerplate-generator@1.0.11 13 | caching-compiler@1.1.9 14 | caching-html-compiler@1.0.7 15 | callback-hook@1.0.10 16 | check@1.2.4 17 | coffeescript@1.11.1_4 18 | cosmos:browserify@0.10.0 19 | dburles:collection-helpers@1.1.0 20 | ddp@1.2.5 21 | ddp-client@1.3.2 22 | ddp-common@1.2.8 23 | ddp-server@1.3.12 24 | deps@1.0.12 25 | diff-sequence@1.0.7 26 | dispatch:mocha-phantomjs@0.1.7 27 | dispatch:phantomjs-tests@0.0.5 28 | ecmascript@0.6.1 29 | ecmascript-runtime@0.3.15 30 | ejson@1.0.13 31 | es5-shim@4.6.15 32 | ethereum:tools@0.6.0 33 | ethereum:web3@0.15.3 34 | fastclick@1.0.13 35 | fourseven:scss@3.13.0 36 | frozeman:persistent-minimongo@0.1.8 37 | frozeman:storage@0.1.9 38 | geojson-utils@1.0.10 39 | hot-code-push@1.0.4 40 | html-tools@1.0.11 41 | htmljs@1.0.11 42 | http@1.2.10 43 | hwillson:stub-collections@1.0.3 44 | id-map@1.0.9 45 | jquery@1.11.10 46 | launch-screen@1.0.12 47 | livedata@1.0.18 48 | localstorage@1.0.12 49 | logging@1.1.16 50 | makerotc:dapple@0.0.1 51 | manuel:isdev@1.0.0 52 | manuel:reactivearray@1.0.5 53 | manuel:viewmodel@4.1.9 54 | manuel:viewmodel-debug@2.7.1 55 | meteor@1.6.0 56 | meteor-base@1.0.4 57 | mikowals:batch-insert@1.1.14 58 | minifier-css@1.2.15 59 | minifier-js@1.2.15 60 | minimongo@1.0.19 61 | mobile-experience@1.0.4 62 | mobile-status-bar@1.0.13 63 | modules@0.7.7 64 | modules-runtime@0.7.7 65 | momentjs:moment@2.17.1 66 | mongo@1.1.14 67 | mongo-id@1.0.6 68 | npm-mongo@2.2.11_2 69 | numeral:numeral@1.5.3_1 70 | observe-sequence@1.0.14 71 | ordered-dict@1.0.9 72 | practicalmeteor:chai@2.1.0_1 73 | practicalmeteor:loglevel@1.2.0_2 74 | practicalmeteor:mocha@2.4.5_6 75 | practicalmeteor:mocha-core@1.0.1 76 | practicalmeteor:sinon@1.14.1_2 77 | promise@0.8.8 78 | random@1.0.10 79 | reactive-dict@1.1.8 80 | reactive-var@1.0.11 81 | reload@1.1.11 82 | retry@1.0.9 83 | routepolicy@1.0.12 84 | session@1.1.7 85 | sha@1.0.9 86 | shell-server@0.2.1 87 | spacebars@1.0.13 88 | spacebars-compiler@1.0.13 89 | standard-minifier-css@1.3.2 90 | standard-minifier-js@1.2.1 91 | templating@1.2.15 92 | templating-compiler@1.2.15 93 | templating-runtime@1.2.15 94 | templating-tools@1.0.5 95 | tmeasday:test-reporter-helpers@0.2.1 96 | tracker@1.1.1 97 | twbs:bootstrap@3.3.6 98 | ui@1.0.12 99 | underscore@1.0.10 100 | url@1.0.11 101 | velocity:meteor-stubs@1.1.1 102 | webapp@1.3.12 103 | webapp-hashing@1.0.9 104 | -------------------------------------------------------------------------------- /frontend/client/main.js: -------------------------------------------------------------------------------- 1 | import '/imports/startup/client'; 2 | -------------------------------------------------------------------------------- /frontend/client/style/index.css: -------------------------------------------------------------------------------- 1 | /** { font: inherit; margin: 0; padding: 0 } 2 | * { outline: none } 3 | * { box-sizing: border-box } 4 | 5 | body { font-family: sans-serif } 6 | body { line-height: 1rem } 7 | body { font-size: .8rem } 8 | body > div.container { padding: 1rem } 9 | body > div.container { padding-bottom: 3rem } 10 | body > div.container { max-width: 20rem } 11 | body > div.container { margin: auto } 12 | 13 | h1, h2, h3 { color: #546979 } 14 | h1, h2, h3 { line-height: 2rem } 15 | h1, h2, h3 { margin-top: 1rem } 16 | 17 | h1 { text-align: center } 18 | h1 { font-size: 1.5rem } 19 | h1 img { width: 8rem } 20 | h1 img { margin-left: 1rem } 21 | h1 span { position: relative } 22 | h1 span { top: 2.2rem } 23 | h1 span { left: -1.25rem } 24 | 25 | h2 { border-top: .1rem solid currentcolor } 26 | h2, h3 { font-weight: bold } 27 | h2 span { font-size: .6rem } 28 | h2 span { font-weight: normal } 29 | h2 span { margin-left: .5rem } 30 | h2 span { color: gray } 31 | h2 .nav-tabs a:focus { outline: none } 32 | 33 | p { max-width: 30rem } 34 | p { margin: 0 0 10px } 35 | 36 | table { border-collapse: collapse } 37 | table { width: 100% } 38 | th { text-transform: uppercase } 39 | th { font-size: .6rem } 40 | td, th { padding: .2rem } 41 | 42 | small { color: lightgray } 43 | .o td { text-align: right } 44 | .o th { text-align: right } 45 | .o th { color: gray } 46 | .o tbody tr.confirmed td.buy { cursor: pointer } 47 | .o tbody tr.confirmed td.cant-buy { cursor: not-allowed } 48 | .o tbody tr:hover { background: #eee } 49 | .o tbody tr:hover+tr.helper-row { background: #eee } 50 | .o tbody tr.cancelled { color: grey } 51 | .o tbody tr.cancelled td { position: relative } 52 | .o tbody tr.cancelled td:before { content: " " } 53 | .o tbody tr.cancelled td:before { position: absolute } 54 | .o tbody tr.cancelled td:before { bottom: 50% } 55 | .o tbody tr.cancelled td:before { left: 0 } 56 | .o tbody tr.cancelled td:before { border-bottom: 1px solid #333 } 57 | .o tbody tr.cancelled td:before { width: 100% } 58 | .o tbody tr.pending { color: grey } 59 | .o tbody tr.bought { font-weight: 600 } 60 | .o tbody tr.helper-row { color: grey } 61 | .o tbody tr.helper-row { font-size: smaller } 62 | 63 | .nostretch { width: 1% } 64 | .nostretch { white-space: nowrap } 65 | 66 | td.bid, th.bid { color: darkgreen } 67 | td.ask, th.ask { color: darkred } 68 | 69 | form th { text-align: right } 70 | form th { padding: .2rem 1rem } 71 | form th { color: gray } 72 | form th { width: 50% } 73 | form tfoot td { text-align: right } 74 | form tfoot td { padding-top: 1rem } 75 | 76 | .radio label { width: 8rem } 77 | .radio input[type=radio] { margin-top: 5px } 78 | [type=number] { width: 8rem } 79 | 80 | button { border: .075rem solid currentcolor } 81 | button { padding: .1rem .8rem } 82 | button { text-transform: uppercase } 83 | button { font-size: .6rem } 84 | button { border-radius: .2rem } 85 | button { cursor: pointer } 86 | button.btn-default:disabled { color: #eee } 87 | 88 | .input { border: none } 89 | .input { border-bottom: .075rem solid #546979 } 90 | .input { background: none } 91 | .input { padding: .1rem 0rem } 92 | .input { text-transform: uppercase } 93 | .input[disabled] { cursor: not-allowed } 94 | 95 | @media (min-width: 640px) 96 | { :root { font-size: 20pt } } 97 | 98 | @media (min-width: 1280px) { 99 | body > div.container { max-width: 40rem } 100 | h1 { text-align: left } 101 | h1 img { margin-left: -1.2rem } 102 | .o { overflow: hidden } 103 | .row { overflow: hidden } 104 | section { float: right; width: 48% } 105 | section:first-of-type { float: left } 106 | } 107 | 108 | #spnSwitchCurrencies { 109 | margin: 0px 10px; 110 | vertical-align: middle; 111 | cursor: pointer; 112 | } 113 | */ 114 | -------------------------------------------------------------------------------- /frontend/client/templates/head.html: -------------------------------------------------------------------------------- 1 | 2 | Oasis 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /frontend/imports/api/limits.js: -------------------------------------------------------------------------------- 1 | import { Mongo } from 'meteor/mongo'; 2 | import { Session } from 'meteor/session'; 3 | import { Dapple } from 'meteor/makerotc:dapple'; 4 | import { BigNumber } from 'meteor/ethereum:web3'; 5 | 6 | class LimitsCollection extends Mongo.Collection { 7 | // Sync token sell limits asynchronously 8 | sync() { 9 | function getMinSell(sellToken) { 10 | const sellTokenAddress = Dapple.getTokenAddress(sellToken); 11 | 12 | return new Promise((resolve, reject) => { 13 | Dapple['maker-otc'].objects.otc.getMinSell(sellTokenAddress, (error, amount) => { 14 | if (!error) { 15 | resolve([sellToken, amount]); 16 | } else { 17 | reject(error); 18 | } 19 | }); 20 | }); 21 | } 22 | 23 | const promises = Dapple.getTokens() 24 | .map((token) => getMinSell(token)) 25 | .map((promise) => { 26 | promise.then((tokenAndAmount) => { 27 | const token = tokenAndAmount[0]; 28 | const amount = tokenAndAmount[1]; 29 | super.upsert(token, { $set: { limit: amount.toString() } }); 30 | }); 31 | }); 32 | 33 | Promise.all(promises).then(() => { 34 | Session.set('limitsLoaded', true); 35 | }); 36 | } 37 | 38 | limitForToken(token) { 39 | const record = super.findOne(token); 40 | return record ? new BigNumber(record.limit) : new BigNumber(0); 41 | } 42 | } 43 | 44 | export default new LimitsCollection(null); 45 | -------------------------------------------------------------------------------- /frontend/imports/api/tokens.js: -------------------------------------------------------------------------------- 1 | import { Mongo } from 'meteor/mongo'; 2 | import { Session } from 'meteor/session'; 3 | import { Dapple, web3Obj } from 'meteor/makerotc:dapple'; 4 | import { convertTo18Precision } from '/imports/utils/conversion'; 5 | 6 | class TokensCollection extends Mongo.Collection { 7 | /** 8 | * Syncs the quote and base currencies' balances and allowances of selected account, 9 | * usually called for each new block 10 | */ 11 | sync() { 12 | const network = Session.get('network'); 13 | const address = web3Obj.eth.defaultAccount; 14 | if (address) { 15 | web3Obj.eth.getBalance(address, (error, balance) => { 16 | const newETHBalance = balance.toString(10); 17 | if (!error && !Session.equals('ETHBalance', newETHBalance)) { 18 | Session.set('ETHBalance', newETHBalance); 19 | } 20 | }); 21 | 22 | // Get GNTBalance 23 | // XXX EIP20 24 | Dapple.getToken('GNT', (error, token) => { 25 | if (!error) { 26 | token.balanceOf(address, (callError, balance) => { 27 | const newGNTBalance = balance.toString(10); 28 | if (!error && !Session.equals('GNTBalance', newGNTBalance)) { 29 | Session.set('GNTBalance', newGNTBalance); 30 | } 31 | }); 32 | const broker = Session.get('GNTBroker'); 33 | if (typeof broker === 'undefined' || broker === '0x0000000000000000000000000000000000000000') { 34 | Session.set('GNTBrokerBalance', 0); 35 | } else { 36 | token.balanceOf(broker, (callError, balance) => { 37 | if (!callError) { 38 | const newGNTBrokerBalance = balance.toString(10); 39 | Session.set('GNTBrokerBalance', newGNTBrokerBalance); 40 | } 41 | }); 42 | } 43 | } 44 | }); 45 | 46 | // const ALL_TOKENS = _.uniq([Session.get('quoteCurrency'), Session.get('baseCurrency')]); 47 | const ALL_TOKENS = Dapple.getTokens(); 48 | 49 | if (network !== 'private') { 50 | // Sync token balances and allowances asynchronously 51 | ALL_TOKENS.forEach((tokenId) => { 52 | // XXX EIP20 53 | Dapple.getToken(tokenId, (error, token) => { 54 | if (!error) { 55 | token.balanceOf(address, (callError, balance) => { 56 | if (!error) { 57 | super.upsert(tokenId, { $set: { 58 | balance: convertTo18Precision(balance, tokenId).toString(10), 59 | realBalance: balance.toString(10), 60 | } }); 61 | Session.set('balanceLoaded', true); 62 | if (tokenId === 'W-GNT') { 63 | /** 64 | * https://github.com/makerdao/token-wrapper/blob/master/src/wrapper.sol#L63 65 | * 66 | * Basically the argument is not used but since some changes in web3 67 | * https://github.com/ethereum/web3.js/pull/866/commits/77da88a6718cf6eeb45e470104f95b8832f30e34 68 | * 69 | * which enforces you to use all arguments of a given method, 70 | * we pass arbitrary address in order to circumvent the issue. 71 | * 72 | * Usage of Session.get('address') has NO MEANING whatsoever. 73 | */ 74 | token.getBroker.call(Session.get('address'), (e, broker) => { 75 | if (!e) { 76 | super.upsert('W-GNT', { $set: { broker } }); 77 | Session.set('GNTBroker', broker); 78 | } 79 | }); 80 | } 81 | } 82 | }); 83 | const contractAddress = Dapple['maker-otc'].environments[Dapple.env].otc.value; 84 | token.allowance(address, contractAddress, (callError, allowance) => { 85 | if (!error) { 86 | super.upsert(tokenId, { $set: { 87 | allowance: convertTo18Precision(allowance, tokenId).toString(10), 88 | realAllowance: allowance.toString(10), 89 | } }); 90 | Session.set('allowanceLoaded', true); 91 | } 92 | }); 93 | } 94 | }); 95 | }); 96 | } else { 97 | ALL_TOKENS.forEach((token) => { 98 | super.upsert(token, { $set: { balance: '0', allowance: '0' } }); 99 | }); 100 | } 101 | } 102 | } 103 | } 104 | 105 | export default new TokensCollection(null); 106 | -------------------------------------------------------------------------------- /frontend/imports/api/transactions.js: -------------------------------------------------------------------------------- 1 | import { Mongo } from 'meteor/mongo'; 2 | import { web3Obj } from 'meteor/makerotc:dapple'; 3 | 4 | class TransactionsCollection extends Mongo.Collection { 5 | 6 | add(type, transactionHash, object) { 7 | // console.log('tx', type, transactionHash, object); 8 | super.insert({ type, tx: transactionHash, object }); 9 | } 10 | 11 | findType(type) { 12 | return super.find({ type }).map(value => value.object); 13 | } 14 | 15 | observeRemoved(type, callback) { 16 | return super.find({ type }).observe({ removed: callback }); 17 | } 18 | 19 | sync() { 20 | const open = super.find().fetch(); 21 | 22 | // Sync all open transactions non-blocking and asynchronously 23 | const syncTransaction = (index) => { 24 | if (index >= 0 && index < open.length) { 25 | const document = open[index]; 26 | web3Obj.eth.getTransactionReceipt(document.tx, (error, result) => { 27 | if (!error && result != null) { 28 | if (result.logs.length > 0) { 29 | console.log('tx_success', document.tx, result.gasUsed); 30 | } else { 31 | console.error('tx_oog', document.tx, result.gasUsed); 32 | } 33 | super.update({ tx: document.tx }, { $set: { receipt: result } }, () => { 34 | super.remove({ tx: document.tx }); 35 | }); 36 | } 37 | // Sync next transaction 38 | syncTransaction(index + 1); 39 | }); 40 | } 41 | }; 42 | syncTransaction(0); 43 | } 44 | } 45 | 46 | export default new TransactionsCollection(null); 47 | -------------------------------------------------------------------------------- /frontend/imports/api/weth.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor'; 2 | import { Session } from 'meteor/session'; 3 | import Transactions from './transactions'; 4 | 5 | class WETH { 6 | watchDeposit() { 7 | Transactions.observeRemoved('ethtokens_deposit', (document) => { 8 | if (document.receipt.logs.length === 0) { 9 | Session.set('ETHDepositProgress', 0); 10 | Session.set('ETHDepositProgressMessage', ''); 11 | Session.set('ETHDepositErrorMessage', 'Wrap went wrong. Please execute the wrap again.'); 12 | } else { 13 | Session.set('ETHDepositProgress', 100); 14 | Session.set('ETHDepositProgressMessage', 'Wrap Done!'); 15 | Meteor.setTimeout(() => { 16 | Session.set('ETHDepositProgress', 0); 17 | Session.set('ETHDepositProgressMessage', ''); 18 | }, 10000); 19 | } 20 | }); 21 | } 22 | 23 | watchWithdraw() { 24 | Transactions.observeRemoved('ethtokens_withdraw', (document) => { 25 | if (document.receipt.logs.length === 0) { 26 | Session.set('ETHWithdrawProgress', 0); 27 | Session.set('ETHWithdrawProgressMessage', ''); 28 | Session.set('ETHWithdrawErrorMessage', 'Unwrapping went wrong. Please execute the withdraw again.'); 29 | } else { 30 | Session.set('ETHWithdrawProgress', 100); 31 | Session.set('ETHWithdrawProgressMessage', 'Unwrap Done!'); 32 | Meteor.setTimeout(() => { 33 | Session.set('ETHWithdrawProgress', 0); 34 | Session.set('ETHWithdrawProgressMessage', ''); 35 | }, 10000); 36 | } 37 | }); 38 | } 39 | } 40 | 41 | export default new WETH(); 42 | -------------------------------------------------------------------------------- /frontend/imports/api/wgnt.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor'; 2 | import { Session } from 'meteor/session'; 3 | import { Dapple, web3Obj } from 'meteor/makerotc:dapple'; 4 | import { formatError } from '/imports/utils/functions'; 5 | import Transactions from './transactions'; 6 | 7 | const TRANSFER_TO_BROKER_GAS = 150000; 8 | const CLEAR_BROKER_GAS = 150000; 9 | 10 | class WGNT { 11 | watchBrokerCreation() { 12 | Transactions.observeRemoved('gnttokens_create_broker', (document) => { 13 | if (document.receipt.logs.length === 0) { 14 | Session.set('GNTDepositProgress', 0); 15 | Session.set('GNTDepositProgressMessage', ''); 16 | Session.set('GNTDepositErrorMessage', 'Creating Broker went wrong. Please execute the desposit again.'); 17 | } else { 18 | const broker = document.receipt.logs[0].topics[1]; 19 | console.log('Broker: ', broker); 20 | Session.set('GNTDepositProgress', 40); 21 | Session.set('GNTDepositProgressMessage', 'Transfering to Broker... (Waiting for your approval)'); 22 | // We get the broker, we transfer GNT to it 23 | Dapple.getToken('GNT', (err, gntToken) => { 24 | gntToken.transfer(broker, web3Obj.toWei(document.object.amount), { gas: TRANSFER_TO_BROKER_GAS }, 25 | (txError, tx) => { 26 | if (!txError) { 27 | console.log('TX Transfer to Broker:', tx); 28 | Session.set('GNTDepositProgress', 50); 29 | Session.set('GNTDepositProgressMessage', 30 | 'Transfering to Broker... (waiting for transaction confirmation)'); 31 | Transactions.add('gnttokens_transfer', tx, { type: 'deposit', broker }); 32 | } else { 33 | Session.set('GNTDepositProgress', 0); 34 | Session.set('GNTDepositProgressMessage', ''); 35 | Session.set('GNTDepositErrorMessage', formatError(txError)); 36 | } 37 | }); 38 | }); 39 | } 40 | }); 41 | } 42 | 43 | watchBrokerTransfer() { 44 | Transactions.observeRemoved('gnttokens_transfer', (document) => { 45 | if (document.receipt.logs.length === 0) { 46 | Session.set('GNTDepositProgress', 0); 47 | Session.set('GNTDepositProgressMessage', ''); 48 | Session.set('GNTDepositErrorMessage', 'Transfering to Broker went wrong. Please execute the desposit again.'); 49 | } else { 50 | console.log('Transfer to Broker done'); 51 | Session.set('GNTDepositProgress', 75); 52 | Session.set('GNTDepositProgressMessage', 'Clearing Broker... (Waiting for your approval)'); 53 | Dapple['token-wrapper'].classes.DepositBroker.at( 54 | document.object.broker.slice(-40)).clear({ gas: CLEAR_BROKER_GAS }, (txError, tx) => { 55 | if (!txError) { 56 | console.log('TX Clear Broker:', tx); 57 | Session.set('GNTDepositProgress', 90); 58 | Session.set('GNTDepositProgressMessage', 'Clearing Broker... (waiting for transaction confirmation)'); 59 | Transactions.add('gnttokens_clear', tx, { type: 'deposit' }); 60 | } else { 61 | Session.set('GNTDepositProgress', 0); 62 | Session.set('GNTDepositProgressMessage', ''); 63 | Session.set('GNTDepositErrorMessage', formatError(txError)); 64 | } 65 | }); 66 | } 67 | }); 68 | } 69 | 70 | watchBrokerClear() { 71 | Transactions.observeRemoved('gnttokens_clear', (document) => { 72 | if (document.receipt.logs.length === 0) { 73 | Session.set('GNTDepositProgress', 0); 74 | Session.set('GNTDepositProgressMessage', ''); 75 | Session.set('GNTDepositErrorMessage', 76 | 'Clearing Broker went wrong. Please execute the clearing manually again to get the wrapped coin.'); 77 | } else { 78 | Session.set('GNTDepositProgress', 100); 79 | Session.set('GNTDepositProgressMessage', 'Wrap Done!'); 80 | Meteor.setTimeout(() => { 81 | Session.set('GNTDepositProgress', 0); 82 | Session.set('GNTDepositProgressMessage', ''); 83 | }, 10000); 84 | } 85 | }); 86 | } 87 | 88 | watchWithdraw() { 89 | Transactions.observeRemoved('gnttokens_withdraw', (document) => { 90 | if (document.receipt.logs.length === 0) { 91 | Session.set('GNTWithdrawProgress', 0); 92 | Session.set('GNTWithdrawProgressMessage', ''); 93 | Session.set('GNTWithdrawErrorMessage', 'Withdrawing went wrong. Please execute the withdraw again.'); 94 | } else { 95 | Session.set('GNTWithdrawProgress', 100); 96 | Session.set('GNTWithdrawProgressMessage', 'Withdraw Done!'); 97 | Meteor.setTimeout(() => { 98 | Session.set('GNTWithdrawProgress', 0); 99 | Session.set('GNTWithdrawProgressMessage', ''); 100 | }, 10000); 101 | } 102 | }); 103 | } 104 | } 105 | 106 | export default new WGNT(); 107 | -------------------------------------------------------------------------------- /frontend/imports/startup/client/index.js: -------------------------------------------------------------------------------- 1 | // Import to load these templates 2 | import '../../ui/client/widgets/depositbalance.js'; 3 | import '../../ui/client/widgets/cancelmodal.js'; 4 | import '../../ui/client/widgets/chart.js'; 5 | import '../../ui/client/widgets/progressblock.js'; 6 | import '../../ui/client/widgets/ethtokens.js'; 7 | import '../../ui/client/widgets/gnttokens.js'; 8 | import '../../ui/client/widgets/history.js'; 9 | import '../../ui/client/widgets/lasttrades.html'; 10 | import '../../ui/client/widgets/maintrades.html'; 11 | import '../../ui/client/widgets/maindeposit.js'; 12 | import '../../ui/client/widgets/maintransfer.js'; 13 | import '../../ui/client/widgets/markets.js'; 14 | import '../../ui/client/widgets/myorders.js'; 15 | import '../../ui/client/widgets/newallowance.js'; 16 | import '../../ui/client/widgets/neworder.js'; 17 | import '../../ui/client/widgets/offermodal.js'; 18 | import '../../ui/client/widgets/orders.js'; 19 | import '../../ui/client/widgets/orderbook.html'; 20 | import '../../ui/client/widgets/sendtokens.js'; 21 | import '../../ui/client/widgets/wrapper-update'; 22 | import '../../ui/client/widgets/redeemer-modal'; 23 | 24 | import '../../ui/client/headers/accountselector.js'; 25 | import '../../ui/client/headers/balance.js'; 26 | import '../../ui/client/headers/currencyselector.js'; 27 | import '../../ui/client/headers/marketdetails.html'; 28 | import '../../ui/client/headers/messages.js'; 29 | import '../../ui/client/headers/networkstatus.html'; 30 | import '../../ui/client/headers/tabs.js'; 31 | import '../../ui/client/headers/volumes.html'; 32 | 33 | 34 | import '../../ui/client/index.html'; 35 | import '../../ui/client/footer.html'; 36 | import '../../ui/client/noethereum.html'; 37 | import '../../ui/client/noaccount.js'; 38 | import '../../ui/client/whatisthis.html'; 39 | 40 | import '../../ui/client/helpers.js'; 41 | 42 | // Start network 43 | import './network.js'; 44 | -------------------------------------------------------------------------------- /frontend/imports/test/dapple-token-spy.js: -------------------------------------------------------------------------------- 1 | export default class DappleTokenSpy { 2 | constructor(canLookup, canCall) { 3 | this.canLookup = canLookup; 4 | this.canCall = canCall; 5 | this.lastCall = {}; 6 | } 7 | getToken(tokenName, tokenCb) { 8 | if (this.canLookup) { 9 | const token = { 10 | deposit: (options, cb) => { 11 | this.lastCall = { 12 | fn: 'deposit', 13 | options, 14 | }; 15 | if (this.canCall) { 16 | cb(false, 'txid'); 17 | } else { 18 | cb('token.deposit call error', null); 19 | } 20 | }, 21 | withdraw: (amount, options, cb) => { 22 | this.lastCall = { 23 | fn: 'withdraw', 24 | amount, 25 | options, 26 | }; 27 | if (this.canCall) { 28 | cb(false, 'txid'); 29 | } else { 30 | cb('token.withdraw call error', null); 31 | } 32 | }, 33 | }; 34 | tokenCb(false, token); 35 | } else { 36 | tokenCb('token lookup error', null); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/footer.html: -------------------------------------------------------------------------------- 1 | 68 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/headers/accountselector.html: -------------------------------------------------------------------------------- 1 | 25 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/headers/accountselector.js: -------------------------------------------------------------------------------- 1 | import { Session } from 'meteor/session'; 2 | import { Template } from 'meteor/templating'; 3 | import { web3Obj } from 'meteor/makerotc:dapple'; 4 | 5 | import Tokens from '/imports/api/tokens'; 6 | 7 | import './accountselector.html'; 8 | 9 | Template.accountSelector.helpers({ 10 | accounts() { 11 | return Session.get('accounts'); 12 | }, 13 | currentAccount() { 14 | return Session.get('address'); 15 | }, 16 | }); 17 | 18 | Template.accountSelector.events({ 19 | change(event) { 20 | Session.set('address', event.target.value); 21 | localStorage.setItem('address', event.target.value); 22 | web3Obj.eth.defaultAccount = event.target.value; 23 | Tokens.sync(); 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/headers/balance.html: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/headers/balance.js: -------------------------------------------------------------------------------- 1 | import { Template } from 'meteor/templating'; 2 | 3 | import './balance.html'; 4 | 5 | Template.balance.events({ 6 | 'click button.btn-change-allowance': (event, templateInstance) => { 7 | const token = templateInstance.data.currency; 8 | $(`#allowanceModal${token}`).data('refer', ''); 9 | $(`#allowanceModal${token}`).modal('show'); 10 | }, 11 | }); 12 | 13 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/headers/currencyselector.html: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/headers/currencyselector.js: -------------------------------------------------------------------------------- 1 | import { Session } from 'meteor/session'; 2 | import { Template } from 'meteor/templating'; 3 | 4 | import Tokens from '/imports/api/tokens'; 5 | 6 | import './currencyselector.html'; 7 | 8 | Template.currencySelector.viewmodel({ 9 | autorun() { 10 | this.quoteCurrency(Session.get('quoteCurrency')); 11 | this.baseCurrency(Session.get('baseCurrency')); 12 | }, 13 | quoteCurrencies: Dapple.getQuoteTokens(), 14 | baseCurrencies: Dapple.getBaseTokens(), 15 | showDropdownQuoteCurrencies() { 16 | return this.quoteCurrencies().length > 1; 17 | }, 18 | showDropdownBaseCurrencies() { 19 | return this.baseCurrencies().length > 1; 20 | }, 21 | quoteCurrency: '', 22 | baseCurrency: '', 23 | quoteHelper: '', 24 | baseHelper: '', 25 | quoteChange() { 26 | // XXX EIP20 27 | Dapple.getToken(this.quoteCurrency(), (error, token) => { 28 | if (!error) { 29 | token.totalSupply((callError) => { 30 | if (!callError) { 31 | this.quoteHelper(''); 32 | localStorage.setItem('quoteCurrency', this.quoteCurrency()); 33 | Session.set('quoteCurrency', this.quoteCurrency()); 34 | if (this.baseCurrency() === this.quoteCurrency()) { 35 | this.baseHelper('Tokens are the same'); 36 | } 37 | if (location.hash.indexOf('#trade') !== -1) { 38 | location.hash = `#trade/${this.baseCurrency()}/${this.quoteCurrency()}`; 39 | } 40 | Tokens.sync(); 41 | } else { 42 | this.quoteHelper('Token not found'); 43 | } 44 | }); 45 | } else { 46 | this.quoteHelper('Token not found'); 47 | } 48 | }); 49 | }, 50 | baseChange() { 51 | // XXX EIP20 52 | Dapple.getToken(this.baseCurrency(), (error, token) => { 53 | if (!error) { 54 | token.totalSupply((callError) => { 55 | if (!callError) { 56 | this.baseHelper(''); 57 | localStorage.setItem('baseCurrency', this.baseCurrency()); 58 | Session.set('baseCurrency', this.baseCurrency()); 59 | if (this.baseCurrency() === this.quoteCurrency()) { 60 | this.baseHelper('Tokens are the same'); 61 | } 62 | if (location.hash.indexOf('#trade') !== -1) { 63 | location.hash = `#trade/${this.baseCurrency()}/${this.quoteCurrency()}`; 64 | } 65 | Tokens.sync(); 66 | } else { 67 | this.baseHelper('Token not found'); 68 | } 69 | }); 70 | } else { 71 | this.baseHelper('Token not found'); 72 | } 73 | }); 74 | }, 75 | }); 76 | 77 | Template.currencySelector.events({ 78 | 'click #spnSwitchCurrencies': function switchCurrencies() { 79 | const quoteCurrency = Session.get('quoteCurrency'); 80 | const baseCurrency = Session.get('baseCurrency'); 81 | Session.set('quoteCurrency', baseCurrency); 82 | Session.set('baseCurrency', quoteCurrency); 83 | }, 84 | }); 85 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/headers/marketdetails.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/headers/messages.html: -------------------------------------------------------------------------------- 1 | 36 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/headers/messages.js: -------------------------------------------------------------------------------- 1 | import { Template } from 'meteor/templating'; 2 | // import { Session } from 'meteor/session'; 3 | 4 | import './messages.html'; 5 | 6 | Template.messages.viewmodel({ 7 | warningOpen: true, 8 | updateOpen: true, 9 | closeMessage(type) { 10 | if (type === 'update') { 11 | this.updateOpen(false); 12 | } else if (type === 'warning') { 13 | this.warningOpen(false); 14 | } 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/headers/networkstatus.html: -------------------------------------------------------------------------------- 1 | 60 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/headers/tabs.html: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/headers/tabs.js: -------------------------------------------------------------------------------- 1 | import { Template } from 'meteor/templating'; 2 | import { Session } from 'meteor/session'; 3 | import { doHashChange } from '/imports/utils/functions'; 4 | 5 | import './tabs.html'; 6 | 7 | Template.tabs.viewmodel({ 8 | currentTab: '', 9 | changeTab(e) { 10 | const tab = e.target.hash; 11 | if (tab !== this.currentTab) { 12 | this.currentTab = tab; 13 | if (tab !== '#trade') { 14 | location.hash = tab; 15 | } else { 16 | location.hash = `#trade/${Session.get('baseCurrency')}/${Session.get('quoteCurrency')}`; 17 | } 18 | doHashChange(); 19 | } 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/headers/volumes.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/index.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | {{#if and isConnected hasAccount}} 4 |
5 |
6 | {{> networkStatus }} 7 |
8 |
9 | {{> volumes }} 10 |
11 |
12 | {{> accountSelector }} 13 |
14 |
15 |
16 | {{> messages}} 17 |
18 | {{#if ready}} 19 | {{#if contractExists}} 20 |
21 | {{> tabs }} 22 |
23 |
24 |
{{> maintrades}}
25 |
{{> maintransfer}}
26 |
{{> maindeposit}}
27 |
28 | {{/if}} 29 | {{/if}} 30 | {{/if}} 31 | {{> wrapperUpdate }} 32 | {{> redeemer }} 33 | {{#unless isConnected}} 34 | {{> noEthereum}} 35 | {{else}} 36 | {{#unless hasAccount}} 37 | {{> noAccount}} 38 | {{/unless}} 39 | {{/unless}} 40 |
41 | {{> footer}} 42 | 43 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/noaccount.html: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/noaccount.js: -------------------------------------------------------------------------------- 1 | import { Template } from 'meteor/templating'; 2 | import { web3Obj } from 'meteor/makerotc:dapple'; 3 | 4 | import './noaccount.html'; 5 | 6 | Template.noAccount.helpers({ 7 | metamask: function metamask() { 8 | return web3Obj && 9 | web3Obj.currentProvider && 10 | ( 11 | web3Obj.currentProvider.isMetaMask || 12 | web3Obj.currentProvider.constructor.name === 'MetamaskInpageProvider' 13 | ); 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/noethereum.html: -------------------------------------------------------------------------------- 1 | 29 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/shared.js: -------------------------------------------------------------------------------- 1 | import { ViewModel } from 'meteor/manuel:viewmodel'; 2 | 3 | ViewModel.share({ 4 | newOffer: { 5 | offerAmount: 0, 6 | offerPrice: 0, 7 | offerTotal: 0, 8 | offerType: '', 9 | offerError: '', 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/whatisthis.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/cancelmodal.html: -------------------------------------------------------------------------------- 1 | 49 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/cancelmodal.js: -------------------------------------------------------------------------------- 1 | import { Template } from 'meteor/templating'; 2 | 3 | import { Offers } from '/imports/api/offers'; 4 | 5 | import './cancelmodal.html'; 6 | 7 | 8 | Template.cancelmodal.viewmodel({ 9 | cancel() { 10 | const offerId = this.templateInstance.data.offer._id; 11 | Offers.cancelOffer(offerId); 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/chart.html: -------------------------------------------------------------------------------- 1 | 62 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/depositbalance.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/depositbalance.js: -------------------------------------------------------------------------------- 1 | import { Session } from 'meteor/session'; 2 | import { Template } from 'meteor/templating'; 3 | 4 | import Tokens from '/imports/api/tokens'; 5 | 6 | import './depositbalance.html'; 7 | 8 | Template.depositbalance.viewmodel({ 9 | selectedToken: 'ETH', 10 | wethBalance() { 11 | try { 12 | const token = Tokens.findOne('W-ETH'); 13 | return token.balance; 14 | } catch (e) { 15 | return '0'; 16 | } 17 | }, 18 | wgntBalance() { 19 | try { 20 | const token = Tokens.findOne('W-GNT'); 21 | return token.balance; 22 | } catch (e) { 23 | return '0'; 24 | } 25 | }, 26 | selected(token) { 27 | return token === this.selectedToken() ? 'selected' : ''; 28 | }, 29 | baseChange(e) { 30 | if (e.currentTarget.id === 'eth-balance') { 31 | Session.set('tokenTemplate', 'ethtokens'); 32 | this.selectedToken('ETH'); 33 | } else if (e.currentTarget.id === 'gnt-balance') { 34 | Session.set('tokenTemplate', 'gnttokens'); 35 | this.selectedToken('GNT'); 36 | } 37 | }, 38 | }); 39 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/ethtokens.html: -------------------------------------------------------------------------------- 1 | 85 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/ethtokens.js: -------------------------------------------------------------------------------- 1 | import { Session } from 'meteor/session'; 2 | import { Template } from 'meteor/templating'; 3 | import { BigNumber } from 'meteor/ethereum:web3'; 4 | import { web3Obj } from 'meteor/makerotc:dapple'; 5 | 6 | import Transactions from '/imports/api/transactions'; 7 | import Tokens from '/imports/api/tokens'; 8 | import WETH from '/imports/api/weth'; 9 | import { uppercaseFirstLetter, formatError } from '/imports/utils/functions'; 10 | 11 | import './ethtokens.html'; 12 | 13 | const TRANSACTION_TYPE_WITHDRAW = 'ethtokens_withdraw'; 14 | const TRANSACTION_TYPE_DEPOSIT = 'ethtokens_deposit'; 15 | const DEPOSIT_GAS = 150000; 16 | const WITHDRAW_GAS = 150000; 17 | const DEPOSIT = 'deposit'; 18 | const WITHDRAW = 'withdraw'; 19 | 20 | Template.ethtokens.viewmodel({ 21 | type() { 22 | const depositType = (this !== null && this !== undefined) ? this.depositType() : ''; 23 | return depositType; 24 | }, 25 | amount: '', 26 | lastError: '', 27 | shouldShowMaxBtn: false, 28 | shouldShowWrapWarning: false, 29 | fillAmount() { 30 | let amount = '0'; 31 | try { 32 | if (this.type() === DEPOSIT) { 33 | amount = web3Obj.fromWei(Session.get('ETHBalance')); 34 | } else if (this.type() === WITHDRAW) { 35 | amount = web3Obj.fromWei(Tokens.findOne('W-ETH').balance); 36 | } 37 | } catch (e) { 38 | amount = '0'; 39 | } 40 | this.amount(amount); 41 | }, 42 | onFocus() { 43 | if (this.title().toLowerCase() === 'unwrap') { this.shouldShowMaxBtn(true); } 44 | if (this.title().toLowerCase() === 'wrap') { this.shouldShowWrapWarning(true); } 45 | }, 46 | onBlur() { 47 | if (this.title().toLowerCase() === 'unwrap') { this.shouldShowMaxBtn(false); } 48 | if (this.title().toLowerCase() === 'wrap') { this.shouldShowWrapWarning(false); } 49 | }, 50 | focusOnInput(event) { 51 | $(event.target).find('input.with-max-btn').focus(); 52 | }, 53 | progress() { 54 | return Session.get(`ETH${uppercaseFirstLetter(this.type())}Progress`); 55 | }, 56 | progressMessage() { 57 | return Session.get(`ETH${uppercaseFirstLetter(this.type())}ProgressMessage`); 58 | }, 59 | errorMessage() { 60 | return Session.get(`ETH${uppercaseFirstLetter(this.type())}ErrorMessage`); 61 | }, 62 | maxAmount() { 63 | let maxAmount = '0'; 64 | try { 65 | if (this.type() === DEPOSIT) { 66 | maxAmount = web3Obj.fromWei(Session.get('ETHBalance')); 67 | } else if (this.type() === WITHDRAW) { 68 | maxAmount = web3Obj.fromWei(Tokens.findOne('W-ETH').balance); 69 | } 70 | } catch (e) { 71 | maxAmount = '0'; 72 | } 73 | return maxAmount; 74 | }, 75 | canDeposit() { 76 | try { 77 | const amount = new BigNumber(this.amount()); 78 | const maxAmount = new BigNumber(this.maxAmount()); 79 | return amount.gt(0) && amount.lte(maxAmount); 80 | } catch (e) { 81 | return false; 82 | } 83 | }, 84 | deposit(event) { 85 | event.preventDefault(); 86 | this.lastError(''); 87 | 88 | if (this.type() === DEPOSIT) { 89 | const options = { 90 | gas: DEPOSIT_GAS, 91 | value: web3Obj.toWei(this.amount()), 92 | }; 93 | // XXX EIP20 94 | Dapple.getToken('W-ETH', (error, token) => { 95 | if (!error) { 96 | Session.set('ETHDepositProgress', 33); 97 | Session.set('ETHDepositProgressMessage', 'Starting wrap... (waiting for your approval)'); 98 | Session.set('ETHDepositErrorMessage', ''); 99 | token.deposit(options, (txError, tx) => { 100 | if (!txError) { 101 | Session.set('ETHDepositProgress', 66); 102 | Session.set('ETHDepositProgressMessage', 'Executing wrap... (waiting for transaction confirmation)'); 103 | Session.set('ETHDepositErrorMessage', ''); 104 | Transactions.add(TRANSACTION_TYPE_DEPOSIT, tx, { type: DEPOSIT, amount: this.amount() }); 105 | } else { 106 | Session.set('ETHDepositProgress', 0); 107 | Session.set('ETHDepositProgressMessage', ''); 108 | Session.set('ETHDepositErrorMessage', formatError(txError)); 109 | } 110 | }); 111 | WETH.watchDeposit(); 112 | } else { 113 | Session.set('ETHDepositProgress', 0); 114 | Session.set('ETHDepositProgressMessage', ''); 115 | Session.set('ETHDepositErrorMessage', error.toString()); 116 | } 117 | }); 118 | } else { 119 | // XXX EIP20 120 | Dapple.getToken('W-ETH', (error, token) => { 121 | if (!error) { 122 | Session.set('ETHWithdrawProgress', 33); 123 | Session.set('ETHWithdrawProgressMessage', 'Starting unwrap... (waiting for your approval)'); 124 | Session.set('ETHWithdrawErrorMessage', ''); 125 | token.withdraw(web3Obj.toWei(this.amount()), { gas: WITHDRAW_GAS }, (txError, tx) => { 126 | if (!txError) { 127 | Session.set('ETHWithdrawProgress', 66); 128 | Session.set('ETHWithdrawProgressMessage', 'Executing unwrap... (waiting for transaction confirmation)'); 129 | Transactions.add(TRANSACTION_TYPE_WITHDRAW, tx, { type: WITHDRAW, amount: this.amount() }); 130 | } else { 131 | Session.set('ETHWithdrawProgress', 0); 132 | Session.set('ETHWithdrawProgressMessage', ''); 133 | Session.set('ETHWithdrawErrorMessage', formatError(txError)); 134 | } 135 | }); 136 | WETH.watchWithdraw(); 137 | } else { 138 | Session.set('ETHWithdrawProgress', 0); 139 | Session.set('ETHWithdrawProgressMessage', ''); 140 | Session.set('ETHWithdrawErrorMessage', error.toString()); 141 | } 142 | }); 143 | } 144 | }, 145 | }); 146 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/ethtokens.test.js: -------------------------------------------------------------------------------- 1 | import { chai } from 'meteor/practicalmeteor:chai'; 2 | import { MeteorStubs } from 'meteor/velocity:meteor-stubs'; 3 | import StubCollections from 'meteor/hwillson:stub-collections'; 4 | import DappleTokenSpy from '/imports/test/dapple-token-spy'; 5 | import { Session } from 'meteor/session'; 6 | import { Template } from 'meteor/templating'; 7 | 8 | import Transactions from '/imports/api/transactions'; 9 | import Tokens from '/imports/api/tokens'; 10 | 11 | import './ethtokens.js'; 12 | 13 | const fakeEvent = { 14 | preventDefault() { 15 | return; 16 | }, 17 | }; 18 | 19 | describe('EthTokens View Model', () => { 20 | let vm; 21 | let dappleBackup; 22 | beforeEach(() => { 23 | vm = Template.ethtokens.createViewModel(); 24 | MeteorStubs.install(); 25 | StubCollections.stub([Tokens, Transactions]); 26 | dappleBackup = Dapple; 27 | }); 28 | afterEach(() => { 29 | MeteorStubs.uninstall(); 30 | StubCollections.restore(); 31 | Dapple = dappleBackup; 32 | }); 33 | it('should have default properties', () => { 34 | chai.assert.equal(vm.type(), 'deposit'); 35 | chai.assert.equal(vm.amount(), ''); 36 | chai.assert.equal(vm.lastError(), ''); 37 | }); 38 | describe('maxAmount', () => { 39 | it('should return ETH balance when depositing', () => { 40 | vm.type('deposit'); 41 | Session.set('ETHBalance', '1230000000000000000'); 42 | chai.assert.equal(vm.maxAmount(), '1.23'); 43 | }); 44 | it('should return W-ETH balance when withdrawing', () => { 45 | vm.type('withdraw'); 46 | Tokens.insert({ _id: 'W-ETH', balance: '3450000000000000000' }); 47 | chai.assert.equal(vm.maxAmount(), '3.45'); 48 | }); 49 | }); 50 | describe('canDeposit', () => { 51 | beforeEach(() => { 52 | Session.set('ETHBalance', '1230000000000000000'); 53 | }); 54 | it('should return true when depositing more than zero and less than maxAmount', () => { 55 | vm.type('deposit'); 56 | vm.amount('1.00'); 57 | chai.assert.isTrue(vm.canDeposit()); 58 | }); 59 | it('should return false when depositing zero', () => { 60 | vm.type('deposit'); 61 | vm.amount('0'); 62 | chai.assert.isFalse(vm.canDeposit()); 63 | }); 64 | it('should return false when depositing more than maxAmount', () => { 65 | vm.type('deposit'); 66 | vm.amount('3.50'); 67 | chai.assert.isFalse(vm.canDeposit()); 68 | }); 69 | }); 70 | describe('deposit', () => { 71 | beforeEach(() => { 72 | Session.set('ETHBalance', '1230000000000000000'); 73 | Tokens.insert({ _id: 'W-ETH', balance: '3450000000000000000' }); 74 | }); 75 | it('should call token.deposit when depositing', () => { 76 | const tokenSpy = new DappleTokenSpy(true, true); 77 | Dapple = tokenSpy; 78 | vm.type('deposit'); 79 | vm.amount('1.00'); 80 | vm.deposit(fakeEvent); 81 | const tx = Transactions.findOne(); 82 | chai.assert.equal(tx.tx, 'txid'); 83 | chai.assert.equal(tx.type, 'ethtokens'); 84 | chai.assert.deepEqual(tx.object, { type: 'deposit', amount: '1.00' }); 85 | }); 86 | it('should set lastError when an error happens getting the token upon deposit', () => { 87 | const tokenSpy = new DappleTokenSpy(false, true); 88 | Dapple = tokenSpy; 89 | vm.type('deposit'); 90 | vm.amount('1.00'); 91 | vm.deposit(fakeEvent); 92 | chai.assert.equal(vm.lastError(), 'token lookup error'); 93 | }); 94 | it('should set lastError when an error happens getting the token upon withdraw', () => { 95 | const tokenSpy = new DappleTokenSpy(false, true); 96 | Dapple = tokenSpy; 97 | vm.type('withdraw'); 98 | vm.amount('1.00'); 99 | vm.deposit(fakeEvent); 100 | chai.assert.equal(vm.lastError(), 'token lookup error'); 101 | }); 102 | it('should set lastError when an error happens calling the token upon deposit', () => { 103 | const tokenSpy = new DappleTokenSpy(true, false); 104 | Dapple = tokenSpy; 105 | vm.type('deposit'); 106 | vm.amount('1.00'); 107 | vm.deposit(fakeEvent); 108 | chai.assert.equal(vm.lastError(), 'token.deposit call error'); 109 | }); 110 | it('should set lastError when an error happens calling the token upon withdraw', () => { 111 | const tokenSpy = new DappleTokenSpy(true, false); 112 | Dapple = tokenSpy; 113 | vm.type('withdraw'); 114 | vm.amount('1.00'); 115 | vm.deposit(fakeEvent); 116 | chai.assert.equal(vm.lastError(), 'token.withdraw call error'); 117 | }); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/gnttokens.html: -------------------------------------------------------------------------------- 1 | 81 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/gnttokens.js: -------------------------------------------------------------------------------- 1 | import { Session } from 'meteor/session'; 2 | import { Template } from 'meteor/templating'; 3 | import { BigNumber } from 'meteor/ethereum:web3'; 4 | import { web3Obj } from 'meteor/makerotc:dapple'; 5 | 6 | import Transactions from '/imports/api/transactions'; 7 | import Tokens from '/imports/api/tokens'; 8 | import { uppercaseFirstLetter, formatError } from '/imports/utils/functions'; 9 | 10 | import './gnttokens.html'; 11 | 12 | const TRANSACTION_TYPE_WITHDRAW = 'gnttokens_withdraw'; 13 | const WITHDRAW_GAS = 150000; 14 | const CLEAR_BROKER_GAS = 150000; 15 | const DEPOSIT = 'deposit'; 16 | const WITHDRAW = 'withdraw'; 17 | 18 | Template.gnttokens.viewmodel({ 19 | type() { 20 | const depositType = (this !== null && this !== undefined) ? this.depositType() : ''; 21 | return depositType; 22 | }, 23 | amount: '', 24 | lastError: '', 25 | shouldShowMaxBtn: false, 26 | fillAmount() { 27 | let amount = '0'; 28 | try { 29 | if (this.type() === DEPOSIT) { 30 | amount = web3Obj.fromWei(Session.get('GNTBalance')); 31 | } else if (this.type() === WITHDRAW) { 32 | amount = web3Obj.fromWei(Tokens.findOne('W-GNT').balance); 33 | } 34 | } catch (e) { 35 | amount = '0'; 36 | } 37 | this.amount(amount); 38 | }, 39 | onFocus() { 40 | this.shouldShowMaxBtn(true); 41 | }, 42 | onBlur() { 43 | this.shouldShowMaxBtn(false); 44 | }, 45 | focusOnInput(event) { 46 | $(event.target).find('input.with-max-btn').focus(); 47 | }, 48 | broker() { 49 | return Session.get('GNTBroker'); 50 | }, 51 | brokerBalance() { 52 | const balance = Session.get('GNTBrokerBalance'); 53 | if (balance !== '0') { 54 | return balance; 55 | } 56 | return 0; 57 | }, 58 | clearBroker(event) { 59 | event.preventDefault(); 60 | Dapple['token-wrapper'].classes.DepositBroker.at(this.broker()).clear({ gas: CLEAR_BROKER_GAS }, (txError, tx) => { 61 | if (!txError) { 62 | Session.set('GNTDepositProgress', 90); 63 | Session.set('GNTDepositProgressMessage', 'Clearing Broker... (waiting for transaction confirmation)'); 64 | Session.set('GNTDepositErrorMessage', ''); 65 | Transactions.add('gnttokens_clear', tx, { type: 'deposit' }); 66 | } else { 67 | Session.set('GNTDepositProgress', 0); 68 | Session.set('GNTDepositProgressMessage', ''); 69 | Session.set('GNTDepositErrorMessage', formatError(txError)); 70 | } 71 | }); 72 | }, 73 | progress() { 74 | return Session.get(`GNT${uppercaseFirstLetter(this.type())}Progress`); 75 | }, 76 | progressMessage() { 77 | return Session.get(`GNT${uppercaseFirstLetter(this.type())}ProgressMessage`); 78 | }, 79 | errorMessage() { 80 | return Session.get(`GNT${uppercaseFirstLetter(this.type())}ErrorMessage`); 81 | }, 82 | maxAmount() { 83 | let maxAmount = '0'; 84 | try { 85 | if (this.type() === DEPOSIT) { 86 | maxAmount = web3Obj.fromWei(Session.get('GNTBalance')); 87 | } else if (this.type() === WITHDRAW) { 88 | maxAmount = web3Obj.fromWei(Tokens.findOne('W-GNT').balance); 89 | } 90 | } catch (e) { 91 | maxAmount = '0'; 92 | } 93 | return maxAmount; 94 | }, 95 | canDeposit() { 96 | try { 97 | const amount = new BigNumber(this.amount()); 98 | const maxAmount = new BigNumber(this.maxAmount()); 99 | return amount.gt(0) && amount.lte(maxAmount); 100 | } catch (e) { 101 | return false; 102 | } 103 | }, 104 | deposit(event) { 105 | event.preventDefault(); 106 | this.lastError(''); 107 | 108 | if (this.type() === DEPOSIT) { 109 | // XXX EIP20 110 | Dapple.getToken('W-GNT', (error, token) => { 111 | if (!error) { 112 | Session.set('GNTDepositProgress', 10); 113 | Session.set('GNTDepositProgressMessage', 'Checking if Broker already exists...'); 114 | Session.set('GNTDepositErrorMessage', ''); 115 | token.getBroker.call((e, broker) => { 116 | if (!e) { 117 | // Check value of broker 118 | if (broker !== '0x0000000000000000000000000000000000000000') { 119 | const tx = Session.get('address') + Date.now(); 120 | Transactions.insert({ 121 | type: 'gnttokens_create_broker', 122 | tx, 123 | object: { 124 | type: DEPOSIT, 125 | amount: this.amount(), 126 | }, 127 | receipt: { 128 | logs: [{ topics: ['', broker] }], 129 | }, 130 | }); 131 | Transactions.remove({ tx }); 132 | } else { 133 | // Create broker 134 | Session.set('GNTDepositProgress', 20); 135 | Session.set('GNTDepositProgressMessage', 'Creating Broker... (waiting for your approval)'); 136 | token.createBroker((txError, tx) => { 137 | if (!txError) { 138 | Session.set('GNTDepositProgress', 30); 139 | Session.set('GNTDepositProgressMessage', 140 | 'Creating Broker... (waiting for transaction confirmation)'); 141 | Transactions.add('gnttokens_create_broker', tx, { type: DEPOSIT, amount: this.amount() }); 142 | } else { 143 | Session.set('GNTDepositProgress', 0); 144 | Session.set('GNTDepositProgressMessage', ''); 145 | Session.set('GNTDepositErrorMessage', formatError(txError)); 146 | } 147 | }); 148 | } 149 | } 150 | }); 151 | } else { 152 | Session.set('GNTDepositProgress', 0); 153 | Session.set('GNTDepositProgressMessage', ''); 154 | Session.set('GNTDepositErrorMessage', error.toString()); 155 | } 156 | }); 157 | } else { 158 | // XXX EIP20 159 | Dapple.getToken('W-GNT', (error, token) => { 160 | if (!error) { 161 | Session.set('GNTWithdrawProgress', 33); 162 | Session.set('GNTWithdrawProgressMessage', 'Starting unwrapping... (waiting for your approval)'); 163 | Session.set('GNTWithdrawErrorMessage', ''); 164 | token.withdraw(web3Obj.toWei(this.amount()), { gas: WITHDRAW_GAS }, (txError, tx) => { 165 | if (!txError) { 166 | Session.set('GNTWithdrawProgress', 66); 167 | Session.set('GNTWithdrawProgressMessage', 168 | 'Executing unwrapping... (waiting for transaction confirmation)'); 169 | Transactions.add(TRANSACTION_TYPE_WITHDRAW, tx, { type: WITHDRAW, amount: this.amount() }); 170 | } else { 171 | Session.set('GNTWithdrawProgress', 0); 172 | Session.set('GNTWithdrawProgressMessage', ''); 173 | Session.set('GNTWithdrawErrorMessage', formatError(txError)); 174 | } 175 | }); 176 | } else { 177 | Session.set('GNTWithdrawProgress', 0); 178 | Session.set('GNTWithdrawProgressMessage', ''); 179 | Session.set('GNTWithdrawErrorMessage', error.toString()); 180 | } 181 | }); 182 | } 183 | }, 184 | }); 185 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/history.html: -------------------------------------------------------------------------------- 1 | 99 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/history.js: -------------------------------------------------------------------------------- 1 | import { Session } from 'meteor/session'; 2 | import { Template } from 'meteor/templating'; 3 | 4 | import TokenEvents from '/imports/api/tokenEvents'; 5 | import { txHref } from '/imports/utils/functions'; 6 | 7 | import './history.html'; 8 | 9 | Template.history.viewmodel({ 10 | autorun() { 11 | if (this.historyType() === 'depositHistory') { 12 | Session.set('loadingWrapHistory', true); 13 | } 14 | if (this.historyType() === 'transferHistory') { 15 | Session.set('loadingTransferHistory', true); 16 | } 17 | }, 18 | currencyClass(token) { 19 | return token === Session.get('quoteCurrency') ? 'quote-currency' : 'base-currency'; 20 | }, 21 | historyCount() { 22 | return this.history().count(); 23 | }, 24 | history() { 25 | const address = Session.get('address'); 26 | return TokenEvents.find({ 27 | type: { $in: ['deposit', 'withdrawal'] }, 28 | $or: [{ to: address }, { from: address }], 29 | }, { sort: { blockNumber: -1 } }); 30 | }, 31 | transferHistory() { 32 | const address = Session.get('address'); 33 | return TokenEvents.find({ 34 | type: { $in: ['transfer'] }, 35 | $or: [{ to: address }, { from: address }], //this triggers reactiveness when the user switches between addresses 36 | }, { sort: { blockNumber: -1 } }); 37 | }, 38 | transferHistoryCount() { 39 | return this.transferHistory().count(); 40 | }, 41 | }); 42 | 43 | Template.history.events({ 44 | 'click tr.clickable': function offer(event) { 45 | event.preventDefault(); 46 | if (this.transactionHash) { 47 | window.open(txHref(this.transactionHash), '_blank'); 48 | } 49 | }, 50 | }); 51 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/lasttrades.html: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/lasttrades.js: -------------------------------------------------------------------------------- 1 | import './lasttrades.html'; 2 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/maindeposit.html: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/maindeposit.js: -------------------------------------------------------------------------------- 1 | import { Session } from 'meteor/session'; 2 | import { Template } from 'meteor/templating'; 3 | import './maindeposit.html'; 4 | import { doTabShow } from '../../../utils/functions.js'; 5 | 6 | Template.maindeposit.viewmodel({ 7 | depositData() { 8 | return { title: 'WRAP', depositType: 'deposit' }; 9 | }, 10 | withdrawData() { 11 | return { title: 'UNWRAP', depositType: 'withdraw' }; 12 | }, 13 | tokenTemplate() { 14 | if (typeof Session.get('tokenTemplate') === 'undefined') { 15 | Session.set('tokenTemplate', 'ethtokens'); 16 | } 17 | return Session.get('tokenTemplate'); 18 | }, 19 | }); 20 | 21 | Template.maindeposit.onRendered(() => { 22 | doTabShow(); 23 | }); 24 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/maintrades.html: -------------------------------------------------------------------------------- 1 | 66 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/maintransfer.html: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/maintransfer.js: -------------------------------------------------------------------------------- 1 | import { Template } from 'meteor/templating'; 2 | import './maintransfer.html'; 3 | import { doTabShow } from '../../../utils/functions.js'; 4 | 5 | Template.maintransfer.onRendered(() => { 6 | doTabShow(); 7 | }); 8 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/markets.html: -------------------------------------------------------------------------------- 1 | 83 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/myorders.html: -------------------------------------------------------------------------------- 1 | 31 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/myorders.js: -------------------------------------------------------------------------------- 1 | import { Template } from 'meteor/templating'; 2 | import { Status } from '/imports/api/offers'; 3 | 4 | import './myorders.html'; 5 | 6 | Template.myorders.viewmodel({ 7 | status: Status, 8 | orderStatus: [Status.OPEN, Status.CLOSED], 9 | filterByStatus: Status.OPEN, 10 | }); 11 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/newallowance.html: -------------------------------------------------------------------------------- 1 | 109 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/newallowance.js: -------------------------------------------------------------------------------- 1 | import { Template } from 'meteor/templating'; 2 | import { BigNumber } from 'meteor/ethereum:web3'; 3 | import { Dapple, web3Obj } from 'meteor/makerotc:dapple'; 4 | import { $ } from 'meteor/jquery'; 5 | 6 | import Transactions from '/imports/api/transactions'; 7 | import { formatError } from '/imports/utils/functions'; 8 | 9 | import { convertToTokenPrecision } from '/imports/utils/conversion'; 10 | 11 | import './newallowance.html'; 12 | 13 | const APPROVE_GAS = 150000; 14 | 15 | Template.newallowance.viewmodel({ 16 | value: '', 17 | allowance() { 18 | return Template.currentData().token.allowance; 19 | }, 20 | pending() { 21 | return Transactions.findType('allowance_'.concat(Template.currentData().token._id)); 22 | }, 23 | lastError: '', 24 | autorun() { 25 | // Initialize value 26 | this.value(web3Obj.fromWei(this.templateInstance.data.token.allowance)); 27 | }, 28 | setTotalAllowance() { 29 | this.value(web3Obj.fromWei(this.templateInstance.data.token.balance)); 30 | }, 31 | canChange() { 32 | try { 33 | return this.pending().length === 0 && this.value() !== '' && 34 | !(new BigNumber(this.value()).equals(new BigNumber(web3Obj.fromWei(this.allowance())))); 35 | } catch (e) { 36 | return false; 37 | } 38 | }, 39 | change(event) { 40 | event.preventDefault(); 41 | 42 | this.lastError(''); 43 | 44 | const contractAddress = Dapple['maker-otc'].environments[Dapple.env].otc.value; 45 | const options = { gas: APPROVE_GAS }; 46 | 47 | // XXX EIP20 48 | Dapple.getToken(this.templateInstance.data.token._id, (error, token) => { 49 | if (!error) { 50 | token.approve(contractAddress, 51 | convertToTokenPrecision(this.value(), this.templateInstance.data.token._id), options, (txError, tx) => { 52 | if (!txError) { 53 | Transactions.add('allowance_'.concat(this.templateInstance.data.token._id), tx, 54 | { value: this.value(), token: this.templateInstance.data.token._id }); 55 | Transactions.observeRemoved('allowance_'.concat(this.templateInstance.data.token._id), () => { 56 | const refer = $(`#allowanceModal${this.templateInstance.data.token._id}`).data('refer'); 57 | $(`#allowanceModal${this.templateInstance.data.token._id}`).modal('hide'); 58 | if (refer === 'newOrder') { 59 | $('#newOrderModal').modal('show'); 60 | } else if (refer === 'existingOrder') { 61 | $('#offerModal').modal('show'); 62 | } 63 | }); 64 | } else { 65 | this.lastError(formatError(txError)); 66 | } 67 | }); 68 | } else { 69 | this.lastError(error.toString()); 70 | } 71 | }); 72 | }, 73 | }); 74 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/neworder.html: -------------------------------------------------------------------------------- 1 | 112 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/orderbook.html: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/orderrow.html: -------------------------------------------------------------------------------- 1 | 41 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/orderrow.js: -------------------------------------------------------------------------------- 1 | import { Session } from 'meteor/session'; 2 | import { Blaze } from 'meteor/blaze'; 3 | import { Template } from 'meteor/templating'; 4 | import { $ } from 'meteor/jquery'; 5 | 6 | import { txHref } from '/imports/utils/functions'; 7 | 8 | import './orderrow.html'; 9 | 10 | Template.orderRow.events({ 11 | 'click .cancel': function cancel(event, templateInstance) { 12 | event.preventDefault(); 13 | event.stopPropagation(); 14 | const orderId = templateInstance.data.order._id; 15 | Session.set('selectedOffer', orderId); 16 | $('#cancelModal').modal('show'); 17 | }, 18 | 'click tr': function offer(event, templateInstance) { 19 | event.preventDefault(); 20 | if (templateInstance.data.canAccept) { 21 | const orderId = templateInstance.data.order._id; 22 | /* eslint-disable no-underscore-dangle */ 23 | if (Blaze._globalHelpers.isBuyEnabled()) { 24 | $('#offerModal').modal('show'); 25 | Session.set('selectedOffer', orderId); 26 | } else { 27 | const order = { 28 | id: orderId, 29 | type: templateInstance.data.section, 30 | }; 31 | Session.set('selectedOrder', order); 32 | } 33 | } 34 | if (templateInstance.data.canOpenTxLink) { 35 | window.open(txHref(templateInstance.data.order.transactionHash), '_blank'); 36 | } 37 | }, 38 | }); 39 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/orders.html: -------------------------------------------------------------------------------- 1 | 38 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/orders.js: -------------------------------------------------------------------------------- 1 | import { Blaze } from 'meteor/blaze'; 2 | import { Template } from 'meteor/templating'; 3 | 4 | import './orders.html'; 5 | import './orderrow.js'; 6 | 7 | Template.orders.helpers({ 8 | /* eslint-disable no-underscore-dangle */ 9 | /* replaced by scrolling orders 10 | moreBtn: function showMoreBtn() { 11 | const type = Template.instance().data.type; 12 | if (type && type === 'lastTrades') { 13 | const totalOrders = Blaze._globalHelpers.countLastTrades(); 14 | return (this.orders.count() < totalOrders); 15 | } 16 | const totalOffers = Blaze._globalHelpers.countOffers(type); 17 | return (this.orders.count() < totalOffers); 18 | }, 19 | */ 20 | ordersCount: function ordersCount() { 21 | return Template.instance().data.orders.count(); 22 | }, 23 | section: function section() { 24 | return Template.instance().data.type; 25 | }, 26 | orderCount: function countOffersOrders() { 27 | const type = Template.instance().data.type; 28 | if (type && type === 'lastTrades') { 29 | return parseInt(Blaze._globalHelpers.countLastTrades(), 10); 30 | } 31 | return parseInt(Blaze._globalHelpers.countOffers(this.priceClass), 10); 32 | }, 33 | /* eslint-enable no-underscore-dangle */ 34 | }); 35 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/progress-bar.html: -------------------------------------------------------------------------------- 1 | 12 | 13 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/progress-bar.js: -------------------------------------------------------------------------------- 1 | import { Template } from 'meteor/templating'; 2 | 3 | import './progress-bar.html'; 4 | 5 | Template.progressbar.viewmodel({ 6 | autorun() { 7 | const newWidth = (this.value() / this.max()) * 100; 8 | this.templateInstance.$('.dex-progress').width(`${newWidth}%`); 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/progressblock.html: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/progressblock.js: -------------------------------------------------------------------------------- 1 | import './progressblock.html'; 2 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/redeemer-modal.html: -------------------------------------------------------------------------------- 1 | 52 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/redeemer-modal.js: -------------------------------------------------------------------------------- 1 | import { Template } from 'meteor/templating'; 2 | import { Session } from 'meteor/session'; 3 | import Redeemer from '/imports/utils/redeemer'; 4 | import './progress-bar.js'; 5 | import './redeemer-modal.html'; 6 | 7 | Template.redeemer.viewmodel({ 8 | message: '', 9 | current: 0, 10 | inProgress: false, 11 | 12 | resetProgressBar() { 13 | this.current(0); 14 | this.message(''); 15 | this.inProgress(false); 16 | }, 17 | onInterruptedWrapping(error) { 18 | this.resetProgressBar(); 19 | this.message('Unwrapping interrupted! Please try again!'); 20 | console.debug('Received error during unwrapping: ', error); 21 | }, 22 | 23 | async redeem() { 24 | const account = Session.get('address'); 25 | const redeemer = new Redeemer(Dapple.env); 26 | this.inProgress(true); 27 | 28 | const allowance = await redeemer.allowanceOf(account); 29 | const balance = await redeemer.balanceOf(account); 30 | 31 | if (allowance < balance) { 32 | this.current(33); 33 | this.message('Confirming redeem process...'); 34 | await redeemer.approve(account); 35 | } 36 | 37 | this.current(66); 38 | this.message('Redeeming new MKR tokens'); 39 | await redeemer.redeem(); 40 | 41 | this.current(100); 42 | this.message('Redeeming Done!'); 43 | setTimeout(() => { 44 | this.inProgress(false); 45 | /** 46 | * Nasty workaround because $('#redeemer').modal('hide') not working on surge. 47 | * Even invoked within dev console it's still not closing the modal. 48 | * @type {*} 49 | */ 50 | const modal = $('#redeemer'); 51 | modal.removeClass('in'); 52 | modal.css('display', 'none'); 53 | 54 | const body = $('body'); 55 | body.removeClass('modal-open'); 56 | body.css('padding-right', '0'); 57 | 58 | const redeemerModal = document.getElementById('redeemer'); 59 | redeemerModal.dispatchEvent(new Event('hide-modal')); 60 | }, 1000); 61 | }, 62 | }); 63 | 64 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/sendtokens.html: -------------------------------------------------------------------------------- 1 | 103 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/sendtokens.js: -------------------------------------------------------------------------------- 1 | import { Template } from 'meteor/templating'; 2 | import { BigNumber } from 'meteor/ethereum:web3'; 3 | import { Dapple, web3Obj } from 'meteor/makerotc:dapple'; 4 | 5 | import Transactions from '/imports/api/transactions'; 6 | import Tokens from '/imports/api/tokens'; 7 | import { formatError } from '/imports/utils/functions'; 8 | 9 | import { convertToTokenPrecision } from '/imports/utils/conversion'; 10 | 11 | import './transferconfirmation'; 12 | import './sendtokens.html'; 13 | 14 | const TRANSFER_GAS = 150000; 15 | const TRANSACTION_TYPE = 'transfer'; 16 | 17 | Template.sendtokens.viewmodel({ 18 | currency: 'MKR', 19 | currencies: Dapple.getTokens(), 20 | recipient: '', 21 | lastError: '', 22 | amount: '', 23 | validAmount: true, 24 | shouldShowMaxBtn: false, 25 | onFocus() { 26 | this.shouldShowMaxBtn(true); 27 | }, 28 | onBlur() { 29 | this.shouldShowMaxBtn(false); 30 | }, 31 | focusOnInput(event) { 32 | $(event.target).find('input.with-max-btn').focus(); 33 | }, 34 | precision() { 35 | return Dapple.getTokenSpecs(this.currency()).precision; 36 | }, 37 | pending() { 38 | return Transactions.findType(TRANSACTION_TYPE); 39 | }, 40 | resetAmount() { 41 | this.amount(0); 42 | }, 43 | maxAmount() { 44 | try { 45 | const token = Tokens.findOne(this.currency()); 46 | return web3Obj.fromWei(token.balance).toString(10); 47 | } catch (e) { 48 | return '0'; 49 | } 50 | }, 51 | isWrappedToken() { 52 | return this.currency().indexOf('W-') !== -1; 53 | }, 54 | canTransfer() { 55 | this.validAmount(true); 56 | if (this.precision() === 0 && this.amount() % 1 !== 0) { 57 | this.validAmount(false); 58 | return false; 59 | } 60 | try { 61 | const amount = new BigNumber(this.amount()); 62 | const maxAmount = new BigNumber(this.maxAmount()); 63 | const recipient = this.recipient(); 64 | return /^(0x)?[0-9a-f]{40}$/i.test(recipient) && amount.gt(0) && amount.lte(maxAmount); 65 | } catch (e) { 66 | return false; 67 | } 68 | }, 69 | fillAmount() { 70 | this.amount(this.maxAmount()); 71 | }, 72 | transfer(event) { 73 | function process(transaction) { 74 | let recipient = transaction.recipient().toLowerCase(); 75 | if (!(/^0x/.test(recipient))) { 76 | recipient = '0x'.concat(recipient); 77 | } 78 | 79 | const options = { gas: TRANSFER_GAS }; 80 | 81 | // XXX EIP20 82 | Dapple.getToken(transaction.currency(), (error, token) => { 83 | if (!error) { 84 | token.transfer(recipient, convertToTokenPrecision(transaction.amount(), transaction.currency()), options, 85 | (txError, tx) => { 86 | if (!txError) { 87 | Transactions.add(TRANSACTION_TYPE, tx, { 88 | recipient, 89 | amount: transaction.amount(), 90 | token: transaction.currency(), 91 | }); 92 | } else { 93 | transaction.lastError(formatError(txError)); 94 | } 95 | }); 96 | } else { 97 | transaction.lastError(error.toString()); 98 | } 99 | }); 100 | } 101 | 102 | event.preventDefault(); 103 | const transaction = this; 104 | 105 | if (this.isWrappedToken()) { 106 | // https://www.w3.org/TR/css-position-3/#painting-order - point 8 107 | // - for some reason the opacity of all order-s ection is 0.89. This creates stacking order. z-index of modal is ignored. 108 | $('.transfer-section').css('opacity', 1); 109 | $('#transferconfirmation').modal('show'); 110 | $('#transferconfirmation').on('transfer:confirmed', (onConfirmation) => { 111 | onConfirmation.stopPropagation(); 112 | process(transaction); 113 | }); 114 | } else { 115 | process(transaction); 116 | } 117 | }, 118 | }); 119 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/transferconfirmation.html: -------------------------------------------------------------------------------- 1 | 41 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/transferconfirmation.js: -------------------------------------------------------------------------------- 1 | import { Template } from 'meteor/templating'; 2 | 3 | import './transferconfirmation.html'; 4 | 5 | Template.transferconfirmation.viewmodel({ 6 | confirm() { 7 | $('#transferconfirmation').trigger('transfer:confirmed'); 8 | }, 9 | }); 10 | 11 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/wrapper-update.html: -------------------------------------------------------------------------------- 1 | 52 | -------------------------------------------------------------------------------- /frontend/imports/ui/client/widgets/wrapper-update.js: -------------------------------------------------------------------------------- 1 | import { Template } from 'meteor/templating'; 2 | import { Session } from 'meteor/session'; 3 | import { web3Obj } from 'meteor/makerotc:dapple'; 4 | 5 | import Transactions from '/imports/api/transactions'; 6 | import { formatError } from '/imports/utils/functions'; 7 | import './progress-bar.js'; 8 | import './wrapper-update.html'; 9 | 10 | const TRANSACTION_TYPE_WITHDRAW = 'ethtokens_withdraw'; 11 | const WITHDRAW_GAS = 150000; 12 | const WITHDRAW = 'withdraw'; 13 | 14 | Template.wrapperUpdate.viewmodel({ 15 | message: '', 16 | current: 0, 17 | inProgress: false, 18 | 19 | resetProgressBar() { 20 | this.current(0); 21 | this.message(''); 22 | this.inProgress(false); 23 | }, 24 | onInterruptedWrapping(error) { 25 | this.resetProgressBar(); 26 | this.message('Unwrapping interrupted! Please try again!'); 27 | console.debug('Received error during unwrapping: ', error); 28 | }, 29 | unwrap() { 30 | const amount = Session.get('oldWrapperBalance'); 31 | this.inProgress(true); 32 | Dapple.getToken('OW-ETH', (error, token) => { 33 | if (!error) { 34 | this.current(33); 35 | this.message('Start unwrapping ...'); 36 | token.withdraw(amount.toString(10), { gas: WITHDRAW_GAS }, (txError, tx) => { 37 | if (!txError) { 38 | this.current(66); 39 | this.message('Unwrapping... (waiting for transaction confirmation)'); 40 | const txChecking = setInterval(() => { 41 | web3Obj.eth.getTransactionReceipt(tx, (err, result) => { 42 | if (!err) { 43 | if (result) { 44 | console.log('Receipt status :', result.status); 45 | if (result.status === '0x1') { 46 | this.current(100); 47 | this.message('Unwrapping Done!'); 48 | } else { 49 | this.onInterruptedWrapping('Missing logs inside the receipt!'); 50 | } 51 | 52 | clearInterval(txChecking); 53 | 54 | setTimeout(() => { 55 | this.inProgress(false); 56 | /** 57 | * Nasty workaround because $('#wrapperUpdate').modal('hide') not working on surge. 58 | * Even invoked within dev console it's still not closing the modal. 59 | * @type {*} 60 | */ 61 | const modal = $('#wrapperUpdate'); 62 | modal.removeClass('in'); 63 | modal.css('display', 'none'); 64 | 65 | const body = $('body'); 66 | body.removeClass('modal-open'); 67 | body.css('padding-right', '0'); 68 | }, 1000); 69 | } 70 | } else { 71 | this.onInterruptedWrapping(txError); 72 | } 73 | }); 74 | }, 1000); 75 | } else { 76 | this.onInterruptedWrapping(txError); 77 | } 78 | }); 79 | } else { 80 | this.onInterruptedWrapping(error); 81 | } 82 | }); 83 | }, 84 | }); 85 | 86 | -------------------------------------------------------------------------------- /frontend/imports/utils/conversion.js: -------------------------------------------------------------------------------- 1 | import { Dapple } from 'meteor/makerotc:dapple'; 2 | import { BigNumber } from 'meteor/ethereum:web3'; 3 | 4 | export function convertToTokenPrecision(amount, token) { 5 | if (typeof token !== 'undefined' && token !== '') { 6 | const tokenSpecs = Dapple.getTokenSpecs(token); 7 | if (tokenSpecs) { 8 | let value = amount; 9 | if (!(amount instanceof BigNumber)) { 10 | value = new BigNumber(amount); 11 | } 12 | return value.times(new BigNumber(10).pow(tokenSpecs.precision)).valueOf(); 13 | } 14 | throw new Error('Precision not found when converting'); 15 | } 16 | throw new Error('Token not found when converting'); 17 | } 18 | 19 | export function convertTo18Precision(amount, token) { 20 | if (typeof token !== 'undefined' && token !== '') { 21 | const tokenSpecs = Dapple.getTokenSpecs(token); 22 | if (tokenSpecs) { 23 | if (tokenSpecs.precision === 18) { 24 | return amount; 25 | } 26 | let value = amount; 27 | if (!(amount instanceof BigNumber)) { 28 | value = new BigNumber(amount); 29 | } 30 | return value.times(new BigNumber(10).pow(18 - tokenSpecs.precision)).valueOf(); 31 | } 32 | throw new Error('Precision not found when converting'); 33 | } 34 | throw new Error('Token not found when converting'); 35 | } 36 | -------------------------------------------------------------------------------- /frontend/imports/utils/functions.js: -------------------------------------------------------------------------------- 1 | import { Session } from 'meteor/session'; 2 | import { $ } from 'meteor/jquery'; 3 | import { BigNumber } from 'meteor/ethereum:web3'; 4 | import { Dapple, web3Obj } from 'meteor/makerotc:dapple'; 5 | import { Spacebars } from 'meteor/spacebars'; 6 | 7 | /** 8 | * Best case scenario: 9 | * - a valid contract address is passed and we have corresponding token symbol 10 | * then we return the symbol for the token. 11 | * 12 | * If the token is a valid address but not known to the market, default value will be returned 13 | * If the token is not known, default value is returned 14 | * 15 | * @param addressOrToken - of type 16 | * @param defaultToken - of type - used as value if token is invalid value 17 | * (not an address, not an address known to the market, not a symbol, not a symbol known to market. (mandatory) 18 | * 19 | * @return string 20 | * 21 | * @throws error if the method is invoked with missing attributes or the types of the attributes are different than expected 22 | */ 23 | function asToken(addressOrToken, defaultToken) { 24 | const allTokens = Dapple.getTokens(); 25 | 26 | if (!defaultToken 27 | || typeof defaultToken !== 'string' 28 | || !allTokens.includes(defaultToken)) { 29 | throw Error('Wrong usage of the API. Read documentation'); 30 | } 31 | 32 | if (!addressOrToken || typeof addressOrToken !== 'string') { 33 | return defaultToken; 34 | } 35 | const isAnAddress = web3Obj.isAddress(addressOrToken); 36 | let currency = defaultToken.toUpperCase(); 37 | let token = addressOrToken.toUpperCase(); 38 | 39 | if (isAnAddress) { 40 | token = Dapple.getTokenByAddress(addressOrToken).toUpperCase(); 41 | } 42 | 43 | if (token && allTokens.includes(token)) { 44 | currency = token; 45 | } 46 | 47 | return currency; 48 | } 49 | 50 | export function uppercaseFirstLetter(word) { 51 | return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); 52 | } 53 | 54 | // This is needed for Metamask errors 55 | // See: https://github.com/MetaMask/metamask-plugin/issues/672 56 | export function formatError(error) { 57 | return error.toString().split('\n')[0]; 58 | } 59 | 60 | export function doTabShow() { 61 | if (location.hash.indexOf('#wrap') !== -1) { 62 | $('.nav-tabs a[href=#wrap]').tab('show'); 63 | } else if (location.hash.indexOf('#transfer') !== -1) { 64 | $('.nav-tabs a[href=#transfer]').tab('show'); 65 | } else { 66 | $('.nav-tabs a[href=#trade]').tab('show'); 67 | } 68 | } 69 | 70 | export function doHashChange() { 71 | // For now is the only currency on the left side 72 | localStorage.setItem('quoteCurrency', 'W-ETH'); 73 | 74 | let quoteCurrency = null; 75 | let baseCurrency = null; 76 | 77 | if (location.hash.indexOf('#wrap') === -1 && location.hash.indexOf('#transfer') === -1) { 78 | if (location.hash.indexOf('#trade') === -1) { 79 | location.hash = `#trade/${localStorage.getItem('baseCurrency') || 'MKR'}` 80 | + `/${localStorage.getItem('quoteCurrency') || 'W-ETH'}`; 81 | } 82 | const coins = location.hash.replace(/#trade\//g, '').split('/'); 83 | 84 | /** 85 | * The default values for base and quote are respectively: 86 | * MKR and W-ETH in all scenarios. The reason for this is 87 | * because those are the main currencies that MAKER is dealing with. 88 | */ 89 | const base = coins[0]; 90 | baseCurrency = asToken(base, 'MKR'); 91 | 92 | const quote = coins[1]; 93 | quoteCurrency = asToken(quote, 'W-ETH'); 94 | 95 | if (baseCurrency === quoteCurrency) { 96 | quoteCurrency = 'W-ETH'; 97 | baseCurrency = 'MKR'; 98 | } 99 | 100 | // Looking for any existing pair that contains the currencies provided in the URL 101 | const pair = Dapple.generatePairs().find((currentPair) => 102 | (currentPair.base === baseCurrency && currentPair.quote === quoteCurrency) 103 | || (currentPair.base === quoteCurrency && currentPair.quote === baseCurrency)); 104 | 105 | // if such pair exists we use it to set the base and quote otherwise we default 106 | if (pair) { 107 | baseCurrency = pair.base; 108 | quoteCurrency = pair.quote; 109 | } else { 110 | quoteCurrency = 'W-ETH'; 111 | baseCurrency = 'MKR'; 112 | } 113 | 114 | Session.set('newPairSelected', pair); 115 | 116 | location.hash = `#trade/${baseCurrency}/${quoteCurrency}`; 117 | } 118 | 119 | doTabShow(); 120 | 121 | Session.set('quoteCurrency', quoteCurrency || localStorage.getItem('quoteCurrency')); 122 | Session.set('baseCurrency', baseCurrency || localStorage.getItem('baseCurrency')); 123 | } 124 | 125 | export function txHref(tx) { 126 | let txLink = ''; 127 | if (Dapple['maker-otc'].objects) { 128 | const network = Session.get('network'); 129 | let networkPrefix = ''; 130 | if (network === 'kovan') { 131 | networkPrefix = 'kovan.'; 132 | } 133 | txLink = `https://${networkPrefix}etherscan.io/tx/${tx}`; 134 | } 135 | return txLink; 136 | } 137 | 138 | export function fractionSeparator() { 139 | const formatter = new Intl.NumberFormat(navigator.language); 140 | 141 | // Usage of this line is define the separator of fractions based on users locale 142 | return formatter.format(0.123).charAt(1); 143 | } 144 | 145 | export function thousandSeparator(number) { 146 | const parts = number.toString().split('.'); 147 | const formatter = new Intl.NumberFormat(navigator.language); 148 | 149 | const whole = formatter.format(parts[0]); 150 | const fraction = (parts[1] ? `${fractionSeparator()}${parts[1]}` : ''); 151 | 152 | return whole + fraction; 153 | } 154 | 155 | export function formatNumber(number, dec) { 156 | let decimals = dec; 157 | if (decimals instanceof Spacebars.kw) { 158 | decimals = 5; 159 | } 160 | let n = number; 161 | if (typeof number !== 'object') { 162 | n = new BigNumber(`${number}`); 163 | } 164 | const d = (new BigNumber(10)).pow(decimals); 165 | n = n.mul(d).trunc().div(d).toFixed(decimals, 6); 166 | return thousandSeparator(n); 167 | } 168 | 169 | export function removeOutliersFromArray(data, fieldName, deviation) { 170 | const l = data.length; 171 | if (data.length <= 2) { 172 | return data; 173 | } 174 | let sum = 0; // stores sum of elements 175 | let sumsq = 0; // stores sum of squares 176 | for (let i = 0; i < l; ++i) { 177 | sum += data[i][fieldName]; 178 | sumsq += data[i][fieldName] * data[i][fieldName]; 179 | } 180 | const mean = sum / l; 181 | const varience = (sumsq / l) - (mean * mean); 182 | const sd = Math.sqrt(varience); 183 | const newData = []; // uses for data which is 3 standard deviations from the mean 184 | for (let i = 0; i < l; ++i) { 185 | if (data[i][fieldName] > mean - (deviation * sd) && data[i][fieldName] < mean + (deviation * sd)) { 186 | newData.push(data[i]); 187 | } 188 | } 189 | return newData; 190 | } 191 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "maker-market", 3 | "private": true, 4 | "scripts": { 5 | "lint": "eslint .", 6 | "test": "meteor test --port 3100 --once --driver-package dispatch:mocha-phantomjs", 7 | "test:browser": "meteor test --port 3100 --driver-package practicalmeteor:mocha", 8 | "test:watch": "TEST_WATCH=1 meteor test --port 3100 --driver-package dispatch:mocha-phantomjs", 9 | "start": "meteor run" 10 | }, 11 | "eslintConfig": { 12 | "parser": "babel-eslint", 13 | "parserOptions": { 14 | "allowImportExportEverywhere": true 15 | }, 16 | "plugins": [ 17 | "meteor" 18 | ], 19 | "extends": [ 20 | "airbnb", 21 | "plugin:meteor/recommended" 22 | ], 23 | "settings": { 24 | "import/resolver": "meteor" 25 | }, 26 | "rules": { 27 | "import/no-unresolved": [ 28 | 2, 29 | { 30 | "ignore": [ 31 | "^meteor/" 32 | ] 33 | } 34 | ], 35 | "meteor/no-session": [ 36 | 0 37 | ], 38 | "max-len": [ 39 | 2, 40 | 120, 41 | 2, 42 | { 43 | "ignoreComments": true 44 | } 45 | ], 46 | "import/no-extraneous-dependencies": 0, 47 | "no-underscore-dangle": [ 48 | "error", 49 | { 50 | "allow": [ 51 | "_id" 52 | ] 53 | } 54 | ] 55 | }, 56 | "env": { 57 | "browser": true, 58 | "jquery": true, 59 | "node": true 60 | }, 61 | "globals": { 62 | "Dapple": true, 63 | "describe": false, 64 | "it": false, 65 | "before": false, 66 | "beforeEach": false, 67 | "after": false, 68 | "afterEach": false 69 | } 70 | }, 71 | "dependencies": { 72 | "babel-runtime": "^6.18.0", 73 | "meteor-node-stubs": "~0.2.0", 74 | "promise-latest": "^1.0.4" 75 | }, 76 | "devDependencies": { 77 | "babel-eslint": "^6.1.2", 78 | "eslint": "^3.2.2", 79 | "eslint-config-airbnb": "^10.0.0", 80 | "eslint-import-resolver-meteor": "^0.3.1", 81 | "eslint-plugin-import": "^1.12.0", 82 | "eslint-plugin-jsx-a11y": "^2.0.1", 83 | "eslint-plugin-meteor": "^4.0.0", 84 | "eslint-plugin-react": "^6.0.0" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /frontend/packages/dapple/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "market": { 3 | "kovan": { 4 | "address": "0x8cf1cab422a0b6b554077a361f8419cdf122a9f9", 5 | "blockNumber": 5216718 6 | }, 7 | "live": { 8 | "address": "0x14fbca95be7e99c15cc2996c6c9d841e54b79425", 9 | "blockNumber": 4751582 10 | } 11 | }, 12 | "tokens": { 13 | "kovan": { 14 | "OW-ETH": "0x53eccc9246c1e537d79199d0c7231e425a40f896", 15 | "W-ETH": "0xd0a1e359811322d97991e03f863a0c30c2cf029c", 16 | "DAI": "0xc4375b7de8af5a38a93548eb8453a498222c4ff2", 17 | "SAI": "0x228bf3d5be3ee4b80718b89b68069b023c32131e", 18 | "MKR": "0xaaf64bfcc32d0f15873a02163e7e500671a4ffcd", 19 | "DGD": "0xbb7697d091a2b9428053e2d42d088fcd2a6a0aaf", 20 | "GNT": "0xece9fa304cc965b00afc186f5d0281a00d3dbbfd", 21 | "W-GNT": "0xbd1ceb35769eb44b641c8e257005817183fc2817", 22 | "REP": "0x99e846cfe0321260e51963a2114bc4008d092e24", 23 | "ICN": "0x8a55df5de91eceb816bd9263d2e5f35fd516d4d0", 24 | "1ST": "0x846f258ac72f8a60920d9b613ce9e91f8a7a7b54", 25 | "SNGLS": "0xf7d57c676ac2bc4997ca5d4d34adc0d072213d29", 26 | "VSL": "0x2e65483308968f5210167a23bdb46ec94752fe39", 27 | "PLU": "0x00a0fcaa32b47c4ab4a8fdda6d108e5c1ffd8e4f", 28 | "MLN": "0xc3ce96164012ed51c9b1e34a9323fdc38c96ad8a", 29 | "RHOC": "0x7352c20e00d3c89037a8959699741c341771ed59", 30 | "TIME": "0xd944954588061c969fbd828d1f00c297c3511dbd", 31 | "GUP": "0xa786d73316e43c3361145241755566e72424274c", 32 | "BAT": "0x485bd6f93f3cd63d5da117c8205173b542da8e7e", 33 | "NMR": "0x13ec2f6ab4be5a55800193e7b22e83042405bb64" 34 | }, 35 | "live": { 36 | "OW-ETH": "0xecf8f87f810ecf450940c9f60066b4a7a501d6a7", 37 | "W-ETH": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", 38 | "DAI": "0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359", 39 | "SAI": "0x59adcf176ed2f6788a41b8ea4c4904518e62b6a4", 40 | "MKR": "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2", 41 | "DGD": "0xe0b7927c4af23765cb51314a0e0521a9645f0e2a", 42 | "GNT": "0xa74476443119a942de498590fe1f2454d7d4ac0d", 43 | "W-GNT": "0x01afc37f4f85babc47c0e2d0eababc7fb49793c8", 44 | "REP": "0xe94327d07fc17907b4db788e5adf2ed424addff6", 45 | "ICN": "0x888666ca69e0f178ded6d75b5726cee99a87d698", 46 | "1ST": "0xaf30d2a7e90d7dc361c8c4585e9bb7d2f6f15bc7", 47 | "SNGLS": "0xaec2e87e0a235266d9c5adc9deb4b2e29b54d009", 48 | "VSL": "0x5c543e7ae0a1104f78406c340e9c64fd9fce5170", 49 | "PLU": "0xd8912c10681d8b21fd3742244f44658dba12264e", 50 | "MLN": "0xbeb9ef514a379b997e0798fdcc901ee474b6d9a1", 51 | "RHOC": "0x168296bb09e24a88805cb9c33356536b980d3fc5", 52 | "TIME": "0x6531f133e6deebe7f2dce5a0441aa7ef330b4e53", 53 | "GUP": "0xf7b098298f7c69fc14610bf71d5e02c60792894c", 54 | "BAT": "0x0d8775f648430679a709e98d2b0cb6250d2887ef", 55 | "NMR": "0x1776e1f26f98b1a5df9cd347953a26dd3cb46671" 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /frontend/packages/dapple/contracts-abi/maker-otc.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | import MatchingMarketABI from './matching-market'; 4 | import ExpiringMarketABI from './expiring-market'; 5 | import SimpleMarketABI from './simple-market'; 6 | 7 | if (typeof Dapple === 'undefined') { 8 | Dapple = {}; 9 | } 10 | 11 | Dapple['maker-otc'] = (function builder() { 12 | var environments = { 13 | 'develop': {}, 14 | 'live': { 15 | 'otc': { 16 | 'value': '', 17 | 'type': 'MatchingMarket', 18 | }, 19 | }, 20 | 'kovan': { 21 | 'otc': { 22 | 'value': '', 23 | 'type': 'MatchingMarket', 24 | }, 25 | }, 26 | }; 27 | 28 | function ContractWrapper(headers, _web3) { 29 | if (!_web3) { 30 | throw new Error('Must supply a web3 connection!'); 31 | } 32 | 33 | this.headers = headers; 34 | this._class = _web3.eth.contract(headers.interface); 35 | } 36 | 37 | ContractWrapper.prototype.deploy = function () { 38 | throw new Error('Module was built without any deploy data.'); 39 | }; 40 | 41 | ContractWrapper.prototype.new = function () { 42 | throw new Error('Module was built without any deploy data.'); 43 | }; 44 | 45 | var passthroughs = ['at']; 46 | for (var i = 0; i < passthroughs.length; i += 1) { 47 | ContractWrapper.prototype[passthroughs[i]] = (function (passthrough) { 48 | return function () { 49 | return this._class[passthrough].apply(this._class, arguments); 50 | }; 51 | })(passthroughs[i]); 52 | } 53 | 54 | function constructor(_web3, env) { 55 | if (!env) { 56 | env = { 57 | 'objects': {}, 58 | 'type': 'internal', 59 | }; 60 | } 61 | if (!('objects' in env) && typeof env === 'object') { 62 | env = { objects: env }; 63 | } 64 | while (typeof env !== 'object') { 65 | if (!(env in environments)) { 66 | throw new Error('Cannot resolve environment name: ' + env); 67 | } 68 | env = environments[env]; 69 | } 70 | 71 | this.headers = { 72 | 'MatchingMarket': MatchingMarketABI, 73 | 'ExpiringMarket': ExpiringMarketABI, 74 | 'SimpleMarket': SimpleMarketABI, 75 | }; 76 | 77 | this.classes = {}; 78 | for (var key in this.headers) { 79 | this.classes[key] = new ContractWrapper(this.headers[key], _web3); 80 | } 81 | 82 | this.objects = {}; 83 | for (var i in env.objects) { 84 | var obj = env.objects[i]; 85 | if (!(obj['type'].split('[')[0] in this.classes)) continue; 86 | this.objects[i] = this.classes[obj['type'].split('[')[0]].at(obj.value); 87 | } 88 | } 89 | 90 | return { 91 | class: constructor, 92 | environments: environments, 93 | }; 94 | })(); 95 | 96 | if (typeof module !== 'undefined' && module.exports) { 97 | module.exports = Dapple['maker-otc']; 98 | } 99 | -------------------------------------------------------------------------------- /frontend/packages/dapple/contracts-abi/simple-market.js: -------------------------------------------------------------------------------- 1 | const SimpleMarketABI = { 2 | interface: [{ 3 | constant: false, 4 | inputs: [{ name: 'pay_gem', type: 'address' }, { name: 'buy_gem', type: 'address' }, { 5 | name: 'pay_amt', 6 | type: 'uint128', 7 | }, { name: 'buy_amt', type: 'uint128' }], 8 | name: 'make', 9 | outputs: [{ name: 'id', type: 'bytes32' }], 10 | payable: false, 11 | stateMutability: 'nonpayable', 12 | type: 'function', 13 | }, { 14 | constant: true, 15 | inputs: [], 16 | name: 'last_offer_id', 17 | outputs: [{ name: '', type: 'uint256' }], 18 | payable: false, 19 | stateMutability: 'view', 20 | type: 'function', 21 | }, { 22 | constant: false, 23 | inputs: [{ name: 'id', type: 'uint256' }], 24 | name: 'cancel', 25 | outputs: [{ name: 'success', type: 'bool' }], 26 | payable: false, 27 | stateMutability: 'nonpayable', 28 | type: 'function', 29 | }, { 30 | constant: true, 31 | inputs: [{ name: 'id', type: 'uint256' }], 32 | name: 'getOffer', 33 | outputs: [{ name: '', type: 'uint256' }, { name: '', type: 'address' }, { 34 | name: '', 35 | type: 'uint256', 36 | }, { name: '', type: 'address' }], 37 | payable: false, 38 | stateMutability: 'view', 39 | type: 'function', 40 | }, { 41 | constant: false, 42 | inputs: [{ name: 'id', type: 'bytes32' }, { name: 'maxTakeAmount', type: 'uint128' }], 43 | name: 'take', 44 | outputs: [], 45 | payable: false, 46 | stateMutability: 'nonpayable', 47 | type: 'function', 48 | }, { 49 | constant: false, 50 | inputs: [{ name: 'id_', type: 'bytes32' }], 51 | name: 'bump', 52 | outputs: [], 53 | payable: false, 54 | stateMutability: 'nonpayable', 55 | type: 'function', 56 | }, { 57 | constant: true, 58 | inputs: [{ name: 'id', type: 'uint256' }], 59 | name: 'isActive', 60 | outputs: [{ name: 'active', type: 'bool' }], 61 | payable: false, 62 | stateMutability: 'view', 63 | type: 'function', 64 | }, { 65 | constant: true, 66 | inputs: [{ name: '', type: 'uint256' }], 67 | name: 'offers', 68 | outputs: [{ name: 'pay_amt', type: 'uint256' }, { name: 'pay_gem', type: 'address' }, { 69 | name: 'buy_amt', 70 | type: 'uint256', 71 | }, { name: 'buy_gem', type: 'address' }, { name: 'owner', type: 'address' }, { 72 | name: 'active', 73 | type: 'bool', 74 | }, { name: 'timestamp', type: 'uint64' }], 75 | payable: false, 76 | stateMutability: 'view', 77 | type: 'function', 78 | }, { 79 | constant: false, 80 | inputs: [{ name: 'id', type: 'bytes32' }], 81 | name: 'kill', 82 | outputs: [], 83 | payable: false, 84 | stateMutability: 'nonpayable', 85 | type: 'function', 86 | }, { 87 | constant: true, 88 | inputs: [{ name: 'id', type: 'uint256' }], 89 | name: 'getOwner', 90 | outputs: [{ name: 'owner', type: 'address' }], 91 | payable: false, 92 | stateMutability: 'view', 93 | type: 'function', 94 | }, { 95 | constant: false, 96 | inputs: [{ name: 'id', type: 'uint256' }, { name: 'quantity', type: 'uint256' }], 97 | name: 'buy', 98 | outputs: [{ name: '', type: 'bool' }], 99 | payable: false, 100 | stateMutability: 'nonpayable', 101 | type: 'function', 102 | }, { 103 | constant: false, 104 | inputs: [{ name: 'pay_amt', type: 'uint256' }, { name: 'pay_gem', type: 'address' }, { 105 | name: 'buy_amt', 106 | type: 'uint256', 107 | }, { name: 'buy_gem', type: 'address' }], 108 | name: 'offer', 109 | outputs: [{ name: 'id', type: 'uint256' }], 110 | payable: false, 111 | stateMutability: 'nonpayable', 112 | type: 'function', 113 | }, { 114 | anonymous: false, 115 | inputs: [{ indexed: false, name: 'id', type: 'uint256' }], 116 | name: 'LogItemUpdate', 117 | type: 'event', 118 | }, { 119 | anonymous: false, 120 | inputs: [{ indexed: false, name: 'pay_amt', type: 'uint256' }, { 121 | indexed: true, 122 | name: 'pay_gem', 123 | type: 'address', 124 | }, { indexed: false, name: 'buy_amt', type: 'uint256' }, { 125 | indexed: true, 126 | name: 'buy_gem', 127 | type: 'address', 128 | }], 129 | name: 'LogTrade', 130 | type: 'event', 131 | }, { 132 | anonymous: false, 133 | inputs: [{ indexed: true, name: 'id', type: 'bytes32' }, { 134 | indexed: true, 135 | name: 'pair', 136 | type: 'bytes32', 137 | }, { indexed: true, name: 'maker', type: 'address' }, { 138 | indexed: false, 139 | name: 'pay_gem', 140 | type: 'address', 141 | }, { indexed: false, name: 'buy_gem', type: 'address' }, { 142 | indexed: false, 143 | name: 'pay_amt', 144 | type: 'uint128', 145 | }, { indexed: false, name: 'buy_amt', type: 'uint128' }, { 146 | indexed: false, 147 | name: 'timestamp', 148 | type: 'uint64', 149 | }], 150 | name: 'LogMake', 151 | type: 'event', 152 | }, { 153 | anonymous: false, 154 | inputs: [{ indexed: true, name: 'id', type: 'bytes32' }, { 155 | indexed: true, 156 | name: 'pair', 157 | type: 'bytes32', 158 | }, { indexed: true, name: 'maker', type: 'address' }, { 159 | indexed: false, 160 | name: 'pay_gem', 161 | type: 'address', 162 | }, { indexed: false, name: 'buy_gem', type: 'address' }, { 163 | indexed: false, 164 | name: 'pay_amt', 165 | type: 'uint128', 166 | }, { indexed: false, name: 'buy_amt', type: 'uint128' }, { 167 | indexed: false, 168 | name: 'timestamp', 169 | type: 'uint64', 170 | }], 171 | name: 'LogBump', 172 | type: 'event', 173 | }, { 174 | anonymous: false, 175 | inputs: [{ indexed: false, name: 'id', type: 'bytes32' }, { 176 | indexed: true, 177 | name: 'pair', 178 | type: 'bytes32', 179 | }, { indexed: true, name: 'maker', type: 'address' }, { 180 | indexed: false, 181 | name: 'pay_gem', 182 | type: 'address', 183 | }, { indexed: false, name: 'buy_gem', type: 'address' }, { 184 | indexed: true, 185 | name: 'taker', 186 | type: 'address', 187 | }, { indexed: false, name: 'take_amt', type: 'uint128' }, { 188 | indexed: false, 189 | name: 'give_amt', 190 | type: 'uint128', 191 | }, { indexed: false, name: 'timestamp', type: 'uint64' }], 192 | name: 'LogTake', 193 | type: 'event', 194 | }, { 195 | anonymous: false, 196 | inputs: [{ indexed: true, name: 'id', type: 'bytes32' }, { 197 | indexed: true, 198 | name: 'pair', 199 | type: 'bytes32', 200 | }, { indexed: true, name: 'maker', type: 'address' }, { 201 | indexed: false, 202 | name: 'pay_gem', 203 | type: 'address', 204 | }, { indexed: false, name: 'buy_gem', type: 'address' }, { 205 | indexed: false, 206 | name: 'pay_amt', 207 | type: 'uint128', 208 | }, { indexed: false, name: 'buy_amt', type: 'uint128' }, { 209 | indexed: false, 210 | name: 'timestamp', 211 | type: 'uint64', 212 | }], 213 | name: 'LogKill', 214 | type: 'event', 215 | }], 216 | }; 217 | 218 | export { SimpleMarketABI as default }; 219 | -------------------------------------------------------------------------------- /frontend/packages/dapple/package-post-init.js: -------------------------------------------------------------------------------- 1 | const config = require('./config.json'); 2 | const ENVs = { 3 | 'test': 'kovan', 4 | 'main': 'live', 5 | 'private': 'default', 6 | }; 7 | 8 | Dapple.init = function init(env) { 9 | var predefinedEnv = ENVs[env]; 10 | 11 | if (!predefinedEnv) { 12 | predefinedEnv = env; 13 | } 14 | 15 | Dapple.env = predefinedEnv; 16 | Dapple['maker-otc']['environments'][Dapple.env].otc.value = config.market[Dapple.env].address; 17 | Dapple['maker-otc']['environments'][Dapple.env].otc.blockNumber = config.market[Dapple.env].blockNumber; 18 | Dapple['maker-otc'].class(web3Obj, Dapple['maker-otc'].environments[Dapple.env]); 19 | Dapple['ds-eth-token'].class(web3Obj, Dapple['ds-eth-token'].environments[Dapple.env]); 20 | Dapple['token-wrapper'].class(web3Obj, Dapple['token-wrapper'].environments[Dapple.env]); 21 | 22 | if (env) { 23 | // Check if contract exists on new environment 24 | const contractAddress = Dapple['maker-otc'].environments[Dapple.env].otc.value; 25 | web3Obj.eth.getCode(contractAddress, (error, code) => { 26 | Session.set('contractExists', !error && typeof code === 'string' && code !== '' && code !== '0x'); 27 | }); 28 | } 29 | }; 30 | 31 | const tokens = config.tokens; 32 | 33 | // http://numeraljs.com/ for formats 34 | const tokenSpecs = { 35 | 'OW-ETH': { precision: 18, format: '0,0.00[0000000000000000]' }, 36 | 'W-ETH': { precision: 18, format: '0,0.00[0000000000000000]' }, 37 | DAI: { precision: 18, format: '0,0.00[0000000000000000]' }, 38 | SAI: { precision: 18, format: '0,0.00[0000000000000000]' }, 39 | MKR: { precision: 18, format: '0,0.00[0000000000000000]' }, 40 | DGD: { precision: 9, format: '0,0.00[0000000]' }, 41 | GNT: { precision: 18, format: '0,0.00[0000000000000000]' }, 42 | 'W-GNT': { precision: 18, format: '0,0.00[0000000000000000]' }, 43 | REP: { precision: 18, format: '0,0.00[0000000000000000]' }, 44 | ICN: { precision: 18, format: '0,0.00[0000000000000000]' }, 45 | '1ST': { precision: 18, format: '0,0.00[0000000000000000]' }, 46 | SNGLS: { precision: 0, format: '0,0' }, 47 | VSL: { precision: 18, format: '0,0.00[0000000000000000]' }, 48 | PLU: { precision: 18, format: '0,0.00[0000000000000000]' }, 49 | MLN: { precision: 18, format: '0,0.00[0000000000000000]' }, 50 | RHOC: { precision: 8, format: '0,0.00[000000]' }, 51 | TIME: { precision: 8, format: '0,0.00[000000]' }, 52 | GUP: { precision: 3, format: '0,0.00[0]' }, 53 | BAT: { precision: 18, format: '0,0.00[0000000000000000]' }, 54 | NMR: { precision: 18, format: '0,0.00[0000000000000000]' }, 55 | }; 56 | 57 | Dapple.getQuoteTokens = () => ['W-ETH']; 58 | 59 | Dapple.getBaseTokens = () => ['W-GNT', 'DGD', 'ICN', '1ST', 'SNGLS', 'VSL', 'PLU', 'MLN', 'RHOC', 'TIME', 'GUP', 'BAT', 'NMR']; 60 | 61 | Dapple.getTokens = () => ['W-ETH', 'MKR', 'DGD', 'GNT', 'W-GNT', 'ICN', '1ST', 'SNGLS', 'VSL', 'PLU', 'MLN', 'RHOC', 'TIME', 'GUP', 'BAT', 'NMR', 'SAI', 'DAI']; 62 | 63 | Dapple.generatePairs = () => { 64 | const TradingPairs = [ 65 | { 66 | base: 'MKR', 67 | quote: 'W-ETH', 68 | priority: 10, 69 | }, 70 | { 71 | base: 'W-ETH', 72 | quote: 'DAI', 73 | priority: 9, 74 | }, 75 | { 76 | base: 'MKR', 77 | quote: 'DAI', 78 | priority: 8, 79 | }, 80 | { 81 | base: 'SAI', 82 | quote: 'DAI', 83 | priority: 7, 84 | }, 85 | ]; 86 | 87 | Dapple.getBaseTokens().forEach((base) => { 88 | Dapple.getQuoteTokens().forEach((quote) => { 89 | TradingPairs.push({ 90 | base, 91 | quote, 92 | priority: 0, 93 | }); 94 | }); 95 | }); 96 | return TradingPairs; 97 | }; 98 | 99 | Dapple.getTokenSpecs = (symbol) => { 100 | if (typeof (tokenSpecs[symbol]) !== 'undefined') { 101 | return tokenSpecs[symbol]; 102 | } 103 | return tokenSpecs['W-ETH']; 104 | }; 105 | 106 | Dapple.getTokenAddress = (symbol) => tokens[Dapple.env][symbol]; 107 | 108 | Dapple.getTokenByAddress = (address) => _.invert(tokens[Dapple.env])[address]; 109 | 110 | Dapple.getToken = (symbol, callback) => { 111 | if (!(Dapple.env in tokens)) { 112 | callback('Unknown environment', null); 113 | return; 114 | } 115 | if (!(symbol in tokens[Dapple.env])) { 116 | callback(`Unknown token "${symbol}"`, null); 117 | return; 118 | } 119 | 120 | const address = Dapple.getTokenAddress(symbol); 121 | let tokenClass = 'DSTokenBase'; 122 | let that = Dapple['ds-eth-token']; 123 | 124 | if (symbol === 'W-ETH' || symbol === 'OW-ETH') { 125 | tokenClass = 'DSEthToken'; 126 | } else if (symbol === 'W-GNT') { 127 | tokenClass = 'TokenWrapper'; 128 | that = Dapple['token-wrapper']; 129 | } 130 | 131 | try { 132 | that.classes[tokenClass].at(address, (error, token) => { 133 | if (!error) { 134 | token.abi = that.classes[tokenClass].abi; 135 | callback(false, token); 136 | } else { 137 | callback(error, token); 138 | } 139 | }); 140 | } catch (e) { 141 | callback(e, null); 142 | } 143 | }; 144 | -------------------------------------------------------------------------------- /frontend/packages/dapple/package-pre-init.js: -------------------------------------------------------------------------------- 1 | // console.log('package-pre-init start') 2 | import { Session } from 'meteor/session'; 3 | 4 | web3Obj = new Web3(); 5 | Session.set('web3ObjReady', false); 6 | Session.set('web3Counter', 0); 7 | 8 | const web3Interval = setInterval( 9 | function() { 10 | if (window.web3) { 11 | web3Obj.setProvider(window.web3.currentProvider); 12 | console.log('Using current provider'); 13 | initWeb3(); 14 | } else { 15 | let counter = Session.get('web3Counter'); 16 | counter++; 17 | Session.set('web3Counter', counter); 18 | if (counter >= 3) { 19 | web3Obj.setProvider(new Web3.providers.HttpProvider('http://localhost:8545')); 20 | console.log('Using new provider'); 21 | initWeb3(); 22 | } 23 | } 24 | }, 300 25 | ); 26 | 27 | function initWeb3() { 28 | window.web3 = web3Obj; 29 | clearInterval(Session.get('web3Interval')); 30 | Session.delete('web3Interval'); 31 | Session.set('web3ObjReady', true); 32 | } 33 | 34 | Session.set('web3Interval', web3Interval); 35 | // console.log('package-pre-init done') 36 | 37 | if (typeof module !== 'undefined' && module.exports) { 38 | module.exports = web3Obj; 39 | } 40 | -------------------------------------------------------------------------------- /frontend/packages/dapple/package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | name: 'makerotc:dapple', 3 | version: '0.0.1', 4 | summary: 'Dapple related code for MakerOTC', 5 | git: '', 6 | documentation: 'README.md', 7 | }); 8 | 9 | Package.onUse((api) => { 10 | api.versionsFrom('1.4.0.1'); 11 | 12 | api.use('ecmascript', 'client'); 13 | api.use('ethereum:web3', 'client'); 14 | 15 | api.addFiles(['package-pre-init.js'], 'client'); 16 | api.addFiles(['contracts-abi/maker-otc.js'], 'client'); 17 | api.addFiles(['contracts-abi/ds-eth-token.js'], 'client'); 18 | api.addFiles(['contracts-abi/token-wrapper.js'], 'client'); 19 | api.addFiles(['contracts-abi/simple-market.js'], 'client'); 20 | api.addFiles(['contracts-abi/expiring-market.js'], 'client'); 21 | api.addFiles(['contracts-abi/matching-market.js'], 'client'); 22 | api.addFiles(['package-post-init.js'], 'client'); 23 | 24 | api.export('web3Obj', 'client'); 25 | api.export('Dapple', 'client'); 26 | }); 27 | -------------------------------------------------------------------------------- /frontend/public/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /frontend/public/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /frontend/public/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /frontend/public/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /frontend/public/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /frontend/public/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /frontend/public/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /frontend/public/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /frontend/public/clock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | clock 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /frontend/public/close_x.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | finish_button 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /frontend/public/counter_1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 12 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 1 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /frontend/public/counter_2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 12 Copy 2 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 2 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /frontend/public/counter_3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 14 Copy 2 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 3 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /frontend/public/cross_normal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | finish_button copy 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /frontend/public/cross_pressed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | finish_button copy 2 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /frontend/public/dapphub_icn_metamask.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | metamask copy 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /frontend/public/dot_red.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 12 Copy 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /frontend/public/eth_circle_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | eth 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /frontend/public/eth_circle_icon_full.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | eth 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /frontend/public/ethereum-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /frontend/public/favicon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/favicon-128.png -------------------------------------------------------------------------------- /frontend/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/favicon-16x16.png -------------------------------------------------------------------------------- /frontend/public/favicon-196x196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/favicon-196x196.png -------------------------------------------------------------------------------- /frontend/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/favicon-32x32.png -------------------------------------------------------------------------------- /frontend/public/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/favicon-96x96.png -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/fonts/Montserrat-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/fonts/Montserrat-Medium.woff -------------------------------------------------------------------------------- /frontend/public/fonts/Montserrat-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/fonts/Montserrat-Medium.woff2 -------------------------------------------------------------------------------- /frontend/public/fonts/Montserrat-SemiBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/fonts/Montserrat-SemiBold.woff -------------------------------------------------------------------------------- /frontend/public/fonts/Montserrat-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/fonts/Montserrat-SemiBold.woff2 -------------------------------------------------------------------------------- /frontend/public/ic_add_circle_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/public/ic_compare_arrows_black_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ic_compare_arrows_black_24px 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /frontend/public/ic_remove_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/public/loading.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/loadingLarge.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/logo-oasis-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/logo-oasis-hover.png -------------------------------------------------------------------------------- /frontend/public/logo-oasis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/logo-oasis.png -------------------------------------------------------------------------------- /frontend/public/maker_circle_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ICON_MAKER 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /frontend/public/maker_circle_icon_full.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ICON_MAKER Copy 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /frontend/public/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/mstile-144x144.png -------------------------------------------------------------------------------- /frontend/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/mstile-150x150.png -------------------------------------------------------------------------------- /frontend/public/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/mstile-310x150.png -------------------------------------------------------------------------------- /frontend/public/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/mstile-310x310.png -------------------------------------------------------------------------------- /frontend/public/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OasisDEX/oasis/340e621ec2382ed72dd2f44a270647f3d6c12a89/frontend/public/mstile-70x70.png -------------------------------------------------------------------------------- /frontend/public/myallowance-maximum.svg: -------------------------------------------------------------------------------- 1 | Created with Sketch. 2 | -------------------------------------------------------------------------------- /frontend/public/myallowance-personal.svg: -------------------------------------------------------------------------------- 1 | Created with Sketch. 2 | -------------------------------------------------------------------------------- /frontend/public/order-warning-red.svg: -------------------------------------------------------------------------------- 1 | Created with Sketch. 2 | -------------------------------------------------------------------------------- /frontend/public/order-warning.svg: -------------------------------------------------------------------------------- 1 | Created with Sketch. -------------------------------------------------------------------------------- /frontend/public/remove_button.svg: -------------------------------------------------------------------------------- 1 | Created with Sketch. -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var exec = require('child_process').exec 2 | 3 | var gulp = require('gulp') 4 | var gulpsync = require('gulp-sync')(gulp) 5 | var ghPages = require('gulp-gh-pages') 6 | var surge = require('gulp-surge') 7 | var rename = require('gulp-rename'); 8 | 9 | // meteor-build-client ../build 10 | gulp.task('build-meteor', function (cb) { 11 | exec('meteor-build-client ../dist --path ""', {cwd: 'frontend'}, function (err, res, failed) { 12 | if (err) { 13 | console.log(err) 14 | } else if (failed) { 15 | process.stdout.write(failed) 16 | } else { 17 | process.stdout.write('\u001b[32mMeteor build completed!\n') 18 | } 19 | cb(err) 20 | }) 21 | }) 22 | 23 | // gh-pages 24 | gulp.task('deploy-gh-pages', function () { 25 | require('fs').writeFileSync('./dist/CNAME', 'oasisdex.com'); 26 | return gulp.src('./dist/**/*') 27 | .pipe(ghPages()) 28 | }) 29 | 30 | gulp.task('deploy-surge', [], function () { 31 | return surge({ 32 | project: './dist', // Path to your static build directory 33 | domain: 'https://oasisdex.surge.sh' // Your domain or Surge subdomain 34 | }) 35 | }) 36 | 37 | gulp.task('deploy', gulpsync.sync(['build-meteor', 'deploy-gh-pages'])) 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Maker-Market", 3 | "scripts": { 4 | "build": "gulp build", 5 | "test": "standard" 6 | }, 7 | "devDependencies": { 8 | "babel-eslint": "6.0.0", 9 | "gulp": "^3.9.1", 10 | "gulp-gh-pages": "^0.5.4", 11 | "gulp-rename": "^1.2.2", 12 | "gulp-surge": "^0.1.0", 13 | "gulp-sync": "^0.1.4", 14 | "meteor-build-client": "^0.3.0", 15 | "standard": "6.0.7" 16 | }, 17 | "standard": { 18 | "parser": "babel-eslint", 19 | "globals": [ 20 | "Meteor", 21 | "Session", 22 | "localStorage", 23 | "Template", 24 | "EthTools", 25 | "Dapple", 26 | "BigNumber", 27 | "web3", 28 | "Spacebars", 29 | "Offers", 30 | "Trades", 31 | "Status", 32 | "PRICE_CURRENCY", 33 | "Tokens", 34 | "Transactions", 35 | "$", 36 | "_" 37 | ], 38 | "ignore": [ 39 | "dapple_packages/**", 40 | "frontend/.meteor/**", 41 | "frontend/packages/**", 42 | "scripts/**" 43 | ] 44 | }, 45 | "dependencies": { 46 | "surge": "^0.19.0" 47 | } 48 | } 49 | --------------------------------------------------------------------------------