├── ethereum-contracts ├── .gitattributes ├── .vscode │ └── settings.json ├── .solcover.js ├── contracts │ ├── Bindings.sol │ ├── test │ │ └── TestToken.sol │ ├── Migrations.sol │ ├── ChaosnetChallenges │ │ ├── SimplePuzzle.sol │ │ ├── ShiftInPuzzle.sol │ │ └── Puzzle.sol │ └── DEX.sol ├── patches │ ├── truffle-typings+1.0.8.patch │ ├── solidity-coverage+0.6.7.patch │ ├── openzeppelin-solidity+2.4.0.patch │ └── typechain-target-web3-v1+1.0.2.patch ├── tsconfig.json ├── tslint.json ├── README.md ├── test │ └── helper │ │ ├── testUtils.ts │ │ └── logs.ts ├── types │ └── chai │ │ └── index.d.ts ├── migrations │ ├── networks.js │ ├── 2_dex.js │ └── 3_puzzles.js ├── package.json └── truffle.js ├── banner.jpg ├── preview.png ├── react-client ├── src │ ├── react-app-env.d.ts │ ├── lib │ │ ├── className.ts │ │ ├── history.ts │ │ ├── utils.ts │ │ ├── icons.tsx │ │ ├── debounce.ts │ │ ├── environmentVariables.ts │ │ ├── declarations │ │ │ └── declarations.d.ts │ │ ├── estimatePrice.ts │ │ ├── conversion.ts │ │ ├── getWeb3.ts │ │ ├── market.ts │ │ └── contractAddresses.ts │ ├── styles │ │ ├── images │ │ │ ├── icons │ │ │ │ ├── check.svg │ │ │ │ ├── icon-next.svg │ │ │ │ ├── icon-prev.svg │ │ │ │ ├── howitworks.svg │ │ │ │ ├── howitworks-white.svg │ │ │ │ ├── icon-end.svg │ │ │ │ ├── tutorial.svg │ │ │ │ ├── icon-start.svg │ │ │ │ ├── tutorial-white.svg │ │ │ │ ├── faq.svg │ │ │ │ ├── faq-white.svg │ │ │ │ ├── docs.svg │ │ │ │ ├── stats.svg │ │ │ │ └── docs-white.svg │ │ │ ├── copy.svg │ │ │ ├── exit.svg │ │ │ ├── exit-black.svg │ │ │ ├── previous.svg │ │ │ ├── rp-flag-de.svg │ │ │ ├── next.svg │ │ │ ├── arrow-right.svg │ │ │ ├── arrow.svg │ │ │ ├── out-tx.svg │ │ │ ├── qr.svg │ │ │ ├── logo-small.svg │ │ │ ├── warning.svg │ │ │ └── rp-flag-uk.svg │ │ ├── scss │ │ │ ├── _variables.scss │ │ │ ├── _liquidity.scss │ │ │ ├── _confirmTrade.scss │ │ │ ├── _exchange.scss │ │ │ ├── _history.scss │ │ │ ├── _stats.scss │ │ │ ├── _tutorial.scss │ │ │ └── _header.scss │ │ └── index.scss │ ├── components │ │ ├── views │ │ │ ├── Popup.tsx │ │ │ ├── SelectMarketWrapper.tsx │ │ │ ├── TokenBalance.tsx │ │ │ ├── exchange-forms │ │ │ │ ├── OrderForm.tsx │ │ │ │ └── LiquidityForm.tsx │ │ │ ├── order-popup │ │ │ │ ├── TokenAllowance.tsx │ │ │ │ ├── DepositReceived.tsx │ │ │ │ ├── SubmitToEthereum.tsx │ │ │ │ └── AskForAddress.tsx │ │ │ ├── tutorial-popup │ │ │ │ ├── Tutorial.tsx │ │ │ │ └── TutorialPages.tsx │ │ │ └── LoggedOutPopup.tsx │ │ ├── ErrorBoundary.tsx │ │ └── controllers │ │ │ ├── Exchange.tsx │ │ │ └── PromptDetails.tsx │ ├── state │ │ ├── popupContainer.tsx │ │ ├── connect.tsx │ │ ├── generalTypes.ts │ │ └── persistentContainer.tsx │ ├── index.tsx │ ├── contracts │ │ ├── clean.js │ │ └── devnet │ │ │ └── DEX.json │ └── sentry.ts ├── public │ ├── favicon.ico │ ├── og:image.png │ └── index.html ├── tsconfig.json ├── package.json └── tslint.json ├── yarn.lock ├── .gitignore ├── .circleci └── config.yml └── README.md /ethereum-contracts/.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity -------------------------------------------------------------------------------- /banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renproject/chaosdex/HEAD/banner.jpg -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renproject/chaosdex/HEAD/preview.png -------------------------------------------------------------------------------- /react-client/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /ethereum-contracts/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "solidity.packageDefaultDependenciesContractsDirectory": "" 3 | } -------------------------------------------------------------------------------- /react-client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renproject/chaosdex/HEAD/react-client/public/favicon.ico -------------------------------------------------------------------------------- /react-client/public/og:image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renproject/chaosdex/HEAD/react-client/public/og:image.png -------------------------------------------------------------------------------- /react-client/src/lib/className.ts: -------------------------------------------------------------------------------- 1 | export const className = (...args: string[]) => { 2 | return args.join(" "); 3 | }; 4 | -------------------------------------------------------------------------------- /react-client/src/lib/history.ts: -------------------------------------------------------------------------------- 1 | import { createBrowserHistory } from "history"; 2 | 3 | export const history = createBrowserHistory({ basename: process.env.PUBLIC_URL }); 4 | -------------------------------------------------------------------------------- /react-client/src/styles/images/icons/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /react-client/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line: ban-types no-any 2 | export const setIntervalAndRun = (handler: Function, timeout?: number | undefined, ...args: any[]): number => { 3 | handler(...args); 4 | return setInterval(handler, timeout, ...args); 5 | }; 6 | -------------------------------------------------------------------------------- /react-client/src/styles/images/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /ethereum-contracts/.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | copyPackages: ["darknode-sol", "openzeppelin-solidity"], // needed to import from node_modules 3 | testrpcOptions: "-d --accounts 10 --port 8555", 4 | skipFiles: [ 5 | "Migrations.sol", 6 | "test/TestToken.sol" 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /ethereum-contracts/contracts/Bindings.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.12; 2 | 3 | import "./DEX.sol"; 4 | import "./DEXAdapter.sol"; 5 | import "./DEXReserve.sol"; 6 | 7 | /// @notice Bindings imports all of the contracts for generating bindings. 8 | /* solium-disable-next-line no-empty-blocks */ 9 | contract Bindings {} 10 | -------------------------------------------------------------------------------- /react-client/src/styles/images/exit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /react-client/src/styles/images/exit-black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /react-client/src/styles/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | // #[a-f0-9][a-f0-9][a-f0-9] 2 | 3 | $border: rgba(145, 149, 160, 0.1); 4 | 5 | $background: #21262C; 6 | $mid-grey: #999; 7 | $foreground: #ccc; 8 | 9 | $grey-09: #555; 10 | $grey-10: #888; 11 | $grey-11: #999; 12 | $grey-12: #aaa; 13 | 14 | 15 | $green: #00a500; 16 | $light-red: #F16262; 17 | $red: #F04545; 18 | $orange: #F45532; 19 | $blue-bright: #006FE8; 20 | $blue-light: #00A8F5; 21 | // $background: #fcfcfd; -------------------------------------------------------------------------------- /react-client/src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import "~@renproject/fonts/fonts"; 2 | 3 | @import "~@renproject/react-components/src/styles/common"; 4 | 5 | @import 'scss/variables'; 6 | 7 | @import 'scss/addressInput'; 8 | @import 'scss/confirmTrade'; 9 | @import 'scss/exchange'; 10 | @import 'scss/header'; 11 | @import 'scss/history'; 12 | @import 'scss/liquidity'; 13 | @import 'scss/order'; 14 | @import 'scss/popup'; 15 | @import 'scss/stats'; 16 | @import 'scss/tutorial'; 17 | @import 'scss/_general'; -------------------------------------------------------------------------------- /react-client/src/lib/icons.tsx: -------------------------------------------------------------------------------- 1 | export { ReactComponent as DocsIcon } from "../styles/images/icons/docs-white.svg"; 2 | export { ReactComponent as StatsIcon } from "../styles/images/icons/stats.svg"; 3 | export { ReactComponent as FAQIcon } from "../styles/images/icons/faq-white.svg"; 4 | export { ReactComponent as HowItWorksIcon } from "../styles/images/icons/howitworks-white.svg"; 5 | export { ReactComponent as TutorialIcon } from "../styles/images/icons/tutorial-white.svg"; 6 | export { ReactComponent as Logo } from "../styles/images/logo.svg"; 7 | -------------------------------------------------------------------------------- /react-client/src/styles/images/previous.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /react-client/src/lib/debounce.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export const useDebounce = (value: T, delay: number) => { 4 | const [debouncedValue, setDebouncedValue] = useState(value); 5 | useEffect( 6 | () => { 7 | const handler = setTimeout(() => { 8 | setDebouncedValue(value); 9 | }, delay); 10 | return () => { 11 | clearTimeout(handler); 12 | }; 13 | }, 14 | [value] 15 | ); 16 | 17 | return debouncedValue; 18 | }; 19 | -------------------------------------------------------------------------------- /ethereum-contracts/patches/truffle-typings+1.0.8.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/truffle-typings/index.d.ts b/node_modules/truffle-typings/index.d.ts 2 | index eae5e74..5014462 100644 3 | --- a/node_modules/truffle-typings/index.d.ts 4 | +++ b/node_modules/truffle-typings/index.d.ts 5 | @@ -66,7 +66,7 @@ declare namespace Truffle { 6 | 7 | interface Contract extends ContractNew { 8 | deployed(): Promise; 9 | - at(address: string): T; 10 | + at(address: string): Promise; 11 | address: string; 12 | contractName: string; 13 | } 14 | -------------------------------------------------------------------------------- /react-client/src/styles/images/rp-flag-de.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | Flag of Germany 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /react-client/src/lib/environmentVariables.ts: -------------------------------------------------------------------------------- 1 | // User-configured 2 | export const SENTRY_DSN = process.env.REACT_APP_SENTRY_DSN; 3 | export const ETHEREUM_NODE = process.env.REACT_APP_ETHEREUM_NODE || `${"http"}://localhost:8545`; 4 | export const NETWORK = process.env.REACT_APP_NETWORK || "testnet"; 5 | 6 | // Not configured 7 | export const SOURCE_VERSION = process.env.REACT_APP_SOURCE_VERSION; 8 | export const ENVIRONMENT = ((process.env.NODE_ENV === "development") ? "development" : NETWORK) || "unknown"; 9 | export const IS_TESTNET = NETWORK !== "mainnet" && NETWORK !== "chaosnet"; 10 | -------------------------------------------------------------------------------- /react-client/src/styles/images/next.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /ethereum-contracts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "typeRoots": [ 4 | "types" 5 | ], 6 | "lib": [ 7 | "es2015" 8 | ], 9 | "types": [ 10 | "node", 11 | "mocha", 12 | "truffle-contracts", 13 | "chai" 14 | ], 15 | "module": "commonjs", 16 | "noImplicitAny": true, 17 | "noImplicitReturns": true, 18 | "removeComments": true, 19 | "preserveConstEnums": true, 20 | "sourceMap": true, 21 | "esModuleInterop": true 22 | } 23 | } -------------------------------------------------------------------------------- /react-client/src/lib/declarations/declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@ledgerhq/hw-transport-u2f"; 2 | declare module "@ledgerhq/web3-subprovider"; 3 | declare module "web3-provider-engine"; 4 | declare module "web3-provider-engine/subproviders/fetch"; 5 | declare module "web3-provider-engine/subproviders/nonce-tracker"; 6 | declare module "web3-provider-engine/util/create-payload"; 7 | declare module "ethereumjs-wallet"; 8 | declare module "ethereumjs-wallet/provider-engine"; 9 | declare module "use-persisted-state"; 10 | declare module "wallet-address-validator"; 11 | declare module "react-super-responsive-table"; -------------------------------------------------------------------------------- /ethereum-contracts/contracts/test/TestToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.12; 2 | 3 | import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; 4 | import "openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol"; 5 | 6 | contract TestToken is ERC20, ERC20Detailed { 7 | constructor(string memory _name, string memory _symbol, uint8 _decimals) public ERC20Detailed(_name, _symbol, _decimals) { 8 | _mint(msg.sender, 1000000000000000000000000000); 9 | } 10 | } 11 | 12 | contract RenToken is TestToken("Republic Token", "REN", 18) {} 13 | contract DaiToken is TestToken("MakerDAO", "DAI", 18) {} 14 | -------------------------------------------------------------------------------- /react-client/src/components/views/Popup.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export const Popup: React.StatelessComponent<{ 4 | noOverlay?: boolean; 5 | whiteX?: boolean; 6 | cancel?: () => void; 7 | }> = ({ noOverlay, whiteX, cancel, children }) => { 8 | return
9 |
10 | {cancel ?
: <>} 11 | {children} 12 |
13 | {noOverlay ? null :
} 14 |
; 15 | }; 16 | -------------------------------------------------------------------------------- /react-client/src/state/popupContainer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { List } from "immutable"; 4 | import { Container } from "unstated"; 5 | 6 | const initialState = { 7 | popup: List<() => React.ReactNode>(), 8 | }; 9 | 10 | export class PopupContainer extends Container { 11 | public state = initialState; 12 | 13 | public pushPopup = async (popup: () => React.ReactNode): Promise => { 14 | await this.setState({ popup: this.state.popup.push(popup) }); 15 | } 16 | 17 | public popPopup = async (): Promise => { 18 | await this.setState({ popup: this.state.popup.pop() }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ethereum-contracts/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.22 <0.6.0; 2 | 3 | import "darknode-sol/contracts/Shifter/ShifterRegistry.sol"; 4 | 5 | contract Migrations { 6 | address public owner; 7 | uint public last_completed_migration; 8 | 9 | constructor() public { 10 | owner = msg.sender; 11 | } 12 | 13 | modifier restricted() { 14 | if (msg.sender == owner) _; 15 | } 16 | 17 | function setCompleted(uint completed) public restricted { 18 | last_completed_migration = completed; 19 | } 20 | 21 | function upgrade(address new_address) public restricted { 22 | Migrations upgraded = Migrations(new_address); 23 | upgraded.setCompleted(last_completed_migration); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node / NPM / Yarn 2 | node_modules 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Build / test artifacts 8 | coverage 9 | build 10 | dist 11 | coverageEnv 12 | coverage.json 13 | .coveralls.yml 14 | 15 | # Configuration files 16 | *.env 17 | .env* 18 | 19 | # OS files 20 | .DS_Store 21 | 22 | # Solidity 23 | .node-xmlhttprequest-sync-* 24 | scTopics 25 | allFiredEvents 26 | in_complete_tests 27 | build/development 28 | local_* 29 | bindings.go 30 | ethereum-contracts/types/truffle-contracts 31 | ethereum-contracts/react-client 32 | 33 | # ABI location 34 | /react-client/src/contracts/development 35 | # Contract typings 36 | /react-client/src/lib/contracts 37 | 38 | react-client/package-lock.json 39 | react-client/.gitignore 40 | -------------------------------------------------------------------------------- /ethereum-contracts/patches/solidity-coverage+0.6.7.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/solidity-coverage/lib/app.js b/node_modules/solidity-coverage/lib/app.js 2 | index 6e08eda..30ee172 100644 3 | --- a/node_modules/solidity-coverage/lib/app.js 4 | +++ b/node_modules/solidity-coverage/lib/app.js 5 | @@ -121,7 +121,7 @@ class App { 6 | // Put the coverage network in the existing config 7 | if (!truffleConfig.networks) truffleConfig.networks = {}; 8 | truffleConfig.networks.coverage = coverageNetwork; 9 | - const configString = `module.exports = ${JSON.stringify(truffleConfig)}`; 10 | + const configString = `require("ts-node/register"); module.exports = ${JSON.stringify(truffleConfig)}`; 11 | fs.writeFileSync(`${this.coverageDir}/truffle-config.js`, configString); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /react-client/src/styles/images/icons/icon-next.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Triangle 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /react-client/src/styles/images/icons/icon-prev.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Triangle Copy 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ethereum-contracts/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:recommended" 4 | ], 5 | "linterOptions": { 6 | "exclude": [ 7 | "node_modules", 8 | ] 9 | }, 10 | "rules": { 11 | "quotemark": [ 12 | true, 13 | "double" 14 | ], 15 | "interface-name": false, 16 | "max-classes-per-file": false, 17 | "no-console": false, 18 | "no-empty-interface": false, 19 | "no-var-requires": false, 20 | "object-literal-sort-keys": false, 21 | "no-implicit-dependencies": false, 22 | "ordered-imports": true, 23 | "no-object-literal-type-assertion": true, 24 | "semicolon": true, 25 | "eofline": true, 26 | "no-non-null-assertion": true, 27 | "no-unused-expression": false, 28 | "one-variable-per-declaration": false, 29 | "space-before-function-paren": false 30 | } 31 | } -------------------------------------------------------------------------------- /react-client/src/styles/images/icons/howitworks.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /react-client/src/styles/images/icons/howitworks-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /react-client/src/styles/images/icons/icon-end.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 37 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /react-client/src/styles/images/arrow-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /react-client/src/styles/images/arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /react-client/src/styles/images/icons/tutorial.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /react-client/src/lib/estimatePrice.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from "bignumber.js"; 2 | 3 | import { Token } from "../state/generalTypes"; 4 | import { ReserveBalances } from "../state/uiContainer"; 5 | 6 | const feeInBIPs = 20; 7 | 8 | export const removeRenVMFee = (rcvAmount: BigNumber) => 9 | rcvAmount 10 | .times(10000 - feeInBIPs) 11 | .div(10000); 12 | 13 | export const recoverRenVMFee = (dstAmount: BigNumber) => 14 | dstAmount 15 | .times(10000) 16 | .div(10000 - feeInBIPs) 17 | .minus(dstAmount); 18 | 19 | export const estimatePrice = async (srcToken: Token, dstToken: Token, sendAmount: BigNumber, reserves: ReserveBalances | undefined): Promise => { 20 | if (!reserves) { 21 | return new BigNumber(0); 22 | } 23 | 24 | const srcAmount = reserves.get(srcToken); 25 | const dstAmount = reserves.get(dstToken); 26 | 27 | if (srcAmount === undefined || dstAmount === undefined) { 28 | console.debug("srcAmount or dstAmount undefined"); 29 | return new BigNumber(0); 30 | } 31 | 32 | const rcvAmount = dstAmount.minus((srcAmount.times(dstAmount).div(srcAmount.plus(sendAmount)))); 33 | return removeRenVMFee(rcvAmount); 34 | }; 35 | -------------------------------------------------------------------------------- /react-client/src/styles/images/out-tx.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | 5 | version: 2.1 6 | 7 | executors: 8 | default: 9 | docker: 10 | - image: circleci/node:10.16 11 | working_directory: ~/renvm-demo/ethereum-contracts 12 | 13 | commands: 14 | install_deps: 15 | description: "Install dependencies" 16 | steps: 17 | # Download and cache dependencies 18 | - restore_cache: 19 | name: Restore node_modules 20 | keys: 21 | - v2-dependencies-{{ checksum "yarn.lock" }} 22 | - run: 23 | name: Install Dependencies 24 | command: yarn install --network-concurrency 1 25 | - save_cache: 26 | name: Save node_modules 27 | paths: 28 | - node_modules 29 | key: v2-dependencies-{{ checksum "yarn.lock" }} 30 | 31 | jobs: 32 | build: 33 | executor: default 34 | steps: 35 | - checkout: 36 | path: ~/renvm-demo 37 | - install_deps 38 | - run: 39 | name: Run tests 40 | command: yarn run coverage # && yarn run coveralls 41 | 42 | workflows: 43 | build: 44 | jobs: 45 | - build 46 | -------------------------------------------------------------------------------- /react-client/src/styles/images/icons/icon-start.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 38 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /react-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "plugins": [{ 4 | "name": "typescript-tslint-plugin" 5 | }], 6 | "outDir": "build/dist", 7 | "module": "esnext", 8 | "target": "es5", 9 | "lib": [ 10 | "es2015", 11 | "es2017", 12 | "dom", 13 | "esnext" 14 | ], 15 | "sourceMap": true, 16 | "allowJs": true, 17 | "jsx": "preserve", 18 | "moduleResolution": "node", 19 | "rootDir": "src", 20 | "forceConsistentCasingInFileNames": true, 21 | "noImplicitReturns": true, 22 | "noImplicitThis": true, 23 | "noImplicitAny": true, 24 | "strictNullChecks": true, 25 | "suppressImplicitAnyIndexErrors": true, 26 | "strictPropertyInitialization": true, 27 | "skipLibCheck": true, 28 | "esModuleInterop": true, 29 | "strict": true, 30 | "resolveJsonModule": true, 31 | "isolatedModules": true, 32 | "noEmit": true, 33 | "alwaysStrict": true, 34 | "strictFunctionTypes": true, 35 | "allowSyntheticDefaultImports": true 36 | }, 37 | "exclude": [ 38 | "node_modules", 39 | "build", 40 | "scripts", 41 | "acceptance-tests", 42 | "webpack", 43 | "jest", 44 | "src/setupTests.ts" 45 | ], 46 | "include": [ 47 | "src" 48 | ] 49 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `💱 ChaosDEX` 2 | 3 | Trade BTC, ZEC, BCH ⟷ DAI trustlessly. 4 | 5 | Powered by RenVM and the [RenVM.js SDK](https://github.com/renproject/renvm-sdk-js). To get started using the SDK, read the [Developer Docs](https://docs.renproject.io/developers/) or the [Getting Started Tutorial](https://docs.renproject.io/developers/tutorial/getting-started). 6 | 7 | ![Preview](./preview.png) 8 | 9 | ## Run locally 10 | 11 | This will run against `testnet`. 12 | 13 | ```sh 14 | cd ethereum-contracts 15 | yarn install 16 | yarn run bindings:ts 17 | cd ../ 18 | ``` 19 | 20 | ```sh 21 | cd react-client 22 | yarn install 23 | NETWORK="testnet" yarn start 24 | ``` 25 | 26 | > If it throws `Can't resolve './build/Release/scrypt'`, run: 27 | > ```sh 28 | > rm -r node_modules/scrypt 29 | > ``` 30 | 31 | ## Deploying 32 | 33 | After running the above commands, `cd` into `react-client`. 34 | 35 | Copy into `.env`, and fill in the Ethereum Node (first line) and optionally the Sentry DSN: 36 | 37 | ```sh 38 | REACT_APP_ETHEREUM_NODE="" 39 | REACT_APP_ETHERSCAN="https://kovan.etherscan.io" 40 | 41 | REACT_APP_NETWORK="testnet" 42 | REACT_APP_SENTRY_DSN="" 43 | REACT_APP_ETHEREUM_NETWORK="Kovan" 44 | REACT_APP_ETHEREUM_NETWORK_ID=42 45 | ``` 46 | 47 | Deploy to Github Pages: 48 | 49 | ``` 50 | yarn deploy 51 | ``` 52 | -------------------------------------------------------------------------------- /react-client/src/styles/images/icons/tutorial-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /react-client/src/lib/conversion.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from "bignumber.js"; 2 | import { Currency } from "@renproject/react-components"; 3 | 4 | import { Token, TokenPrices, Tokens } from "../state/generalTypes"; 5 | import { InfoError } from "./errors"; 6 | 7 | const toSmallestUnit = (amount: BigNumber, token: Token): BigNumber => { 8 | const details = Tokens.get(token); 9 | if (!details) { 10 | throw new Error(`Could not get token details for: ${token}`); 11 | } 12 | return amount.div(new BigNumber(10).exponentiatedBy(new BigNumber(details.decimals))); 13 | }; 14 | 15 | export const toBitcoinValue = (amount: BigNumber, token: Token, tokenPrices: TokenPrices): BigNumber => { 16 | const asNaturalUnit = toSmallestUnit(amount, token); 17 | if (token === Token.BTC) { 18 | return asNaturalUnit; 19 | } 20 | 21 | const tokenPriceMap = tokenPrices.get(token); 22 | if (!tokenPriceMap) { 23 | return new BigNumber(0); 24 | } 25 | const toCurrency = Currency.BTC; 26 | const price = tokenPriceMap.get(toCurrency); 27 | if (!price) { 28 | throw new InfoError(`Could not get pricing information for ${toCurrency}`); 29 | } 30 | amount = asNaturalUnit.multipliedBy(price); 31 | 32 | if (!amount.isFinite()) { 33 | amount = new BigNumber(0); 34 | } 35 | return amount; 36 | }; 37 | -------------------------------------------------------------------------------- /react-client/src/styles/scss/_liquidity.scss: -------------------------------------------------------------------------------- 1 | .liquidity--options { 2 | padding: 0 40px; 3 | padding-bottom: 20px; 4 | 5 | display: flex; 6 | 7 | .liquidity--option+.liquidity--option { 8 | margin-left: 20px; 9 | } 10 | 11 | .liquidity--option { 12 | font-weight: 500; 13 | font-size: 14px; 14 | line-height: 16px; 15 | letter-spacing: 0.5px; 16 | 17 | /* identical to box height */ 18 | display: flex; 19 | align-items: center; 20 | 21 | color: #FFFFFF; 22 | opacity: 0.4; 23 | 24 | cursor: pointer; 25 | } 26 | 27 | .liquidity--option--selected { 28 | opacity: 1; 29 | } 30 | } 31 | 32 | .order--wrapper { 33 | .order--tabs--minus { 34 | color: $red; 35 | } 36 | } 37 | 38 | .liquidity-details { 39 | background: #1C2127; 40 | border: 1px solid rgba(145, 149, 160, 0.1); 41 | box-sizing: border-box; 42 | box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.3); 43 | border-radius: 12px; 44 | 45 | font-size: 12px; 46 | line-height: 29px; 47 | 48 | color: #FFFFFF; 49 | 50 | display: flex; 51 | flex-flow: column; 52 | margin: 0 40px; 53 | margin-top: 20px; 54 | padding: 10px; 55 | } 56 | 57 | .liquidity-detail { 58 | display: flex; 59 | justify-content: space-between; 60 | } -------------------------------------------------------------------------------- /ethereum-contracts/README.md: -------------------------------------------------------------------------------- 1 | # ChaosDEX Smart Contracts 2 | 3 | [![Build Status](https://circleci.com/gh/renproject/chaosdex.svg?style=svg)](https://circleci.com/gh/renproject/chaosdex) 4 | [![Coverage Status](https://coveralls.io/repos/github/renproject/chaosdex/badge.svg?branch=master)](https://coveralls.io/github/renproject/chaosdex?branch=master) 5 | 6 | ## Tests 7 | 8 | Install the dependencies. 9 | 10 | ``` 11 | yarn install 12 | ``` 13 | 14 | Run the `ganache-cli` or an alternate Ethereum test RPC server on port 8545. The `-d` flag will use a deterministic mnemonic for reproducibility. 15 | 16 | ```sh 17 | yarn exec ganache-cli -d 18 | ``` 19 | 20 | Run the Truffle test suite. 21 | 22 | ```sh 23 | yarn test 24 | ``` 25 | 26 | ## Coverage 27 | 28 | Install the dependencies. 29 | 30 | ``` 31 | yarn install 32 | ``` 33 | 34 | Run the Truffle test suite with coverage. 35 | 36 | ```sh 37 | yarn coverage 38 | ``` 39 | 40 | ## Deploying to Kovan 41 | 42 | Add a `.env`, filling in the mnemonic and Kovan ethereum node (e.g. Infura): 43 | 44 | ```sh 45 | MNEMONIC="..." 46 | KOVAN_ETHEREUM_NODE="..." 47 | ETHERSCAN_KEY="..." 48 | ``` 49 | 50 | Deploy to Kovan: 51 | 52 | ```sh 53 | NETWORK=testnet yarn run deploy 54 | ``` 55 | 56 | ## Verifying Contract Code 57 | 58 | ```sh 59 | NETWORK=testnet yarn run verify DEX DEXAdapter BTC_DAI_Reserve ZEC_DAI_Reserve 60 | ``` 61 | -------------------------------------------------------------------------------- /react-client/src/index.tsx: -------------------------------------------------------------------------------- 1 | // Import css first so that styles are consistent across dev and build 2 | import "./styles/index.scss"; 3 | 4 | import * as React from "react"; 5 | import * as ReactDOM from "react-dom"; 6 | 7 | import { Router } from "react-router-dom"; 8 | import { Provider } from "unstated"; 9 | 10 | import { App } from "./components/controllers/App"; 11 | import { IS_TESTNET } from "./lib/environmentVariables"; 12 | import { history } from "./lib/history"; 13 | import { initializeSentry } from "./sentry"; 14 | import { PersistentContainer } from "./state/persistentContainer"; 15 | import { PopupContainer } from "./state/popupContainer"; 16 | import { SDKContainer } from "./state/sdkContainer"; 17 | import { UIContainer } from "./state/uiContainer"; 18 | 19 | initializeSentry(); 20 | 21 | const persistentContainer = new PersistentContainer(); 22 | const popupContainer = new PopupContainer(); 23 | const uiContainer = new UIContainer(persistentContainer, popupContainer); 24 | const sdkContainer = new SDKContainer(persistentContainer); 25 | 26 | ReactDOM.render( 27 | 28 | 29 | 30 | 31 | , 32 | document.getElementById("root") as HTMLElement 33 | ); 34 | 35 | if (IS_TESTNET) { 36 | document.title = "TestDEX"; 37 | } 38 | -------------------------------------------------------------------------------- /ethereum-contracts/patches/openzeppelin-solidity+2.4.0.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/openzeppelin-solidity/contracts/cryptography/ECDSA.sol b/node_modules/openzeppelin-solidity/contracts/cryptography/ECDSA.sol 2 | index d85ce09..adb7ac8 100644 3 | --- a/node_modules/openzeppelin-solidity/contracts/cryptography/ECDSA.sol 4 | +++ b/node_modules/openzeppelin-solidity/contracts/cryptography/ECDSA.sol 5 | @@ -28,7 +28,7 @@ library ECDSA { 6 | function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { 7 | // Check the signature length 8 | if (signature.length != 65) { 9 | - return (address(0)); 10 | + revert("signature's length is invalid"); 11 | } 12 | 13 | // Divide the signature in r, s and v variables 14 | @@ -55,11 +55,11 @@ library ECDSA { 15 | // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept 16 | // these malleable signatures as well. 17 | if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { 18 | - return address(0); 19 | + revert("signature's s is in the wrong range"); 20 | } 21 | 22 | if (v != 27 && v != 28) { 23 | - return address(0); 24 | + revert("signature's v is in the wrong range"); 25 | } 26 | 27 | // If the signature is valid (and not malleable), return the signer address 28 | -------------------------------------------------------------------------------- /react-client/src/styles/images/icons/faq.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /react-client/src/styles/images/icons/faq-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ethereum-contracts/test/helper/testUtils.ts: -------------------------------------------------------------------------------- 1 | import * as chai from "chai"; 2 | import * as crypto from "crypto"; 3 | 4 | import chaiAsPromised from "chai-as-promised"; 5 | import chaiBigNumber from "chai-bignumber"; 6 | import BigNumber from "bignumber.js"; 7 | import BN from "bn.js"; 8 | 9 | // Import chai log helper 10 | import "./logs"; 11 | 12 | chai.use(chaiAsPromised); 13 | chai.use((chaiBigNumber)(BigNumber, BN)); 14 | chai.should(); 15 | 16 | export const ETHEREUM_TOKEN_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; 17 | 18 | export const NULL = "0x0000000000000000000000000000000000000000"; 19 | export const NULL20 = NULL; 20 | export const NULL32 = "0x0000000000000000000000000000000000000000000000000000000000000000"; 21 | 22 | export const randomBytes = (bytes: number): string => { 23 | return `0x${crypto.randomBytes(bytes).toString("hex")}`; 24 | }; 25 | 26 | export const advanceBlock = () => { 27 | return new Promise((resolve, reject) => { 28 | (web3.currentProvider.send as any)({ 29 | jsonrpc: '2.0', 30 | method: 'evm_mine', 31 | id: new Date().getTime(), 32 | }, ((err: Error, result: any) => { 33 | if (err) { return reject(err); } 34 | return resolve() 35 | }) as any) 36 | }) 37 | } 38 | 39 | export const advanceBlocks = async (blocks: number) => { 40 | for (let i = 0; i < blocks; i++) { 41 | await advanceBlock(); 42 | } 43 | } 44 | 45 | // Add a 0x prefix to a hex value, converting to a string first 46 | export const Ox = (hex: string | BN | Buffer) => { 47 | const hexString = typeof hex === "string" ? hex : hex.toString("hex"); 48 | return hexString.substring(0, 2) === "0x" ? hexString : `0x${hexString}`; 49 | }; -------------------------------------------------------------------------------- /react-client/src/state/connect.tsx: -------------------------------------------------------------------------------- 1 | // tslint:disable:no-any 2 | import * as React from "react"; 3 | 4 | // import { Loading } from "@renproject/react-components"; 5 | import { Container, Subscribe } from "unstated"; 6 | 7 | // import { PersistContainer } from "unstated-persist"; 8 | 9 | interface AnyConnectedProps { 10 | containers: Array>; 11 | } 12 | export interface ConnectedProps { 13 | containers: Containers; 14 | } 15 | 16 | type Omit = Pick>; 17 | 18 | type GetContainers = Props extends ConnectedProps ? 19 | Containers : never; 20 | type GetContainerClasses = Props extends ConnectedProps> ? 21 | Array ContainerInstance> : never; 22 | 23 | // const isBootstrapped = (container: Container | PersistContainer): boolean => { 24 | // return (container as any).persist === undefined || container.state._persist_version !== undefined; 25 | // } 26 | 27 | // Somewhat typesafe version of https://github.com/goncy/unstated-connect 28 | export const connect = (_containers: GetContainerClasses) => 29 | (Component: React.ComponentClass>> | React.StatelessComponent>>) => (props: Omit) => ( 30 | 31 | {(...containers) => 32 | // containers.every(isBootstrapped) ? 33 | >)} /> 34 | // : 35 | } 36 | 37 | ); 38 | -------------------------------------------------------------------------------- /ethereum-contracts/patches/typechain-target-web3-v1+1.0.2.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/typechain-target-web3-v1/dist/generation.js b/node_modules/typechain-target-web3-v1/dist/generation.js 2 | index f90f030..86b685e 100644 3 | --- a/node_modules/typechain-target-web3-v1/dist/generation.js 4 | +++ b/node_modules/typechain-target-web3-v1/dist/generation.js 5 | @@ -18,6 +18,7 @@ function codegen(contract) { 6 | export class ${contract.name} extends Contract { 7 | constructor(jsonInterface: any[], address?: string, options?: contractOptions); 8 | clone(): ${contract.name}; 9 | + address: string; 10 | methods: { 11 | ${codegenForFunctions(contract.functions)} 12 | }; 13 | diff --git a/node_modules/typechain-target-web3-v1/lib/generation.ts b/node_modules/typechain-target-web3-v1/lib/generation.ts 14 | index 0131187..e84d427 100644 15 | --- a/node_modules/typechain-target-web3-v1/lib/generation.ts 16 | +++ b/node_modules/typechain-target-web3-v1/lib/generation.ts 17 | @@ -1,12 +1,6 @@ 18 | import { 19 | - Contract, 20 | - AbiParameter, 21 | - FunctionDeclaration, 22 | - EventDeclaration, 23 | - AbiOutputParameter, 24 | - EvmType, 25 | - TupleType, 26 | - EvmOutputType, 27 | + AbiOutputParameter, AbiParameter, Contract, EventDeclaration, EvmOutputType, EvmType, 28 | + FunctionDeclaration, TupleType, 29 | } from "typechain"; 30 | import { Dictionary } from "ts-essentials"; 31 | import { values } from "lodash"; 32 | @@ -28,6 +22,7 @@ export function codegen(contract: Contract) { 33 | export class ${contract.name} extends Contract { 34 | constructor(jsonInterface: any[], address?: string, options?: contractOptions); 35 | clone(): ${contract.name}; 36 | + address: string; 37 | methods: { 38 | ${codegenForFunctions(contract.functions)} 39 | }; 40 | -------------------------------------------------------------------------------- /react-client/src/styles/images/qr.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /react-client/src/styles/images/icons/docs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /react-client/src/contracts/clean.js: -------------------------------------------------------------------------------- 1 | // tslint:disable: no-console 2 | 3 | /** 4 | * Only keeps "contractName", "abi", "sourcePath", "compiler", "networks", 5 | * "schemaVersion" and "updatedAt". 6 | */ 7 | 8 | const glob = require("glob"); 9 | const fs = require("fs"); 10 | 11 | const networks = ["testnet", "devnet", "localnet"]; 12 | 13 | const path = require("path"); 14 | const dirname = path.dirname(__filename); 15 | 16 | for (const network of networks) { 17 | const directory = path.join(dirname, `./${network}/*.json`); 18 | glob(directory, (globErr, files) => { // read the folder or folders if you want: example json/**/*.json 19 | if (globErr) { 20 | console.error(`error while reading the files in ${directory}`, globErr); 21 | } 22 | files.forEach((file) => { 23 | fs.readFile(file, "utf8", (readErr, data) => { // Read each file 24 | if (readErr) { 25 | console.error(`error while reading the contents of ${file}`, readErr); 26 | } 27 | var obj = JSON.parse(data); 28 | const newObj = { 29 | abi: obj.abi, 30 | compiler: obj.compiler, 31 | contractName: obj.contractName, 32 | networks: obj.networks, 33 | schemaVersion: obj.schemaVersion, 34 | sourcePath: obj.sourcePath, 35 | }; 36 | const newData = JSON.stringify(newObj, null, " "); 37 | 38 | if (data !== newData) { 39 | fs.writeFile(file, JSON.stringify(newObj, null, " "), (writeErr) => { 40 | if (writeErr) { 41 | return console.error(writeErr); 42 | } 43 | console.info(` Updated \x1b[33m${file}\x1b[0m.`); 44 | }); 45 | } 46 | }); 47 | }); 48 | }); 49 | } 50 | -------------------------------------------------------------------------------- /react-client/src/styles/images/icons/stats.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 29 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /react-client/src/lib/getWeb3.ts: -------------------------------------------------------------------------------- 1 | import Web3 from "web3"; 2 | import { HttpProvider } from "web3-providers"; 3 | 4 | import { InfoError } from "./errors"; 5 | 6 | interface InjectedEthereum extends HttpProvider { 7 | enable: () => Promise; 8 | send: (name: string, args?: X) => Promise; 9 | on: (event: string, callback?: () => void) => void; 10 | autoRefreshOnNetworkChange?: boolean; 11 | } 12 | 13 | declare global { 14 | interface Window { 15 | ethereum?: InjectedEthereum; 16 | web3?: Web3; 17 | } 18 | } 19 | 20 | export const getWeb3 = async () => new Promise(async (resolve, reject) => { 21 | // Modern dApp browsers... 22 | if (window.ethereum) { 23 | 24 | try { 25 | // See https://medium.com/metamask/no-longer-reloading-pages-on-network-change-fbf041942b44 26 | // We will want to support the network changing without reloading in 27 | // the future. 28 | window.ethereum.on("chainChanged", () => { 29 | document.location.reload(); 30 | }); 31 | window.ethereum.autoRefreshOnNetworkChange = false; 32 | } catch (error) { 33 | // Ignore 34 | } 35 | 36 | try { 37 | // See https://metamask.github.io/metamask-docs/API_Reference/Ethereum_Provider#ethereum.send(%E2%80%98eth_requestaccounts%E2%80%99) 38 | await window.ethereum.send("eth_requestAccounts"); 39 | resolve(new Web3(window.ethereum)); 40 | } catch (error) { 41 | try { 42 | // Request account access if needed 43 | await window.ethereum.enable(); 44 | resolve(new Web3(window.ethereum)); 45 | } catch (error) { 46 | reject(error); 47 | } 48 | } 49 | } else if (window.web3) { 50 | // Accounts always exposed 51 | resolve(new Web3(window.web3.currentProvider)); 52 | } else { 53 | // Non-dApp browsers... 54 | reject(new InfoError(`No Web3 detected.`)); 55 | } 56 | }); 57 | -------------------------------------------------------------------------------- /react-client/src/styles/images/logo-small.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /react-client/src/sentry.ts: -------------------------------------------------------------------------------- 1 | import * as Sentry from "@sentry/browser"; 2 | 3 | import { ExtraErrorData } from "@sentry/integrations"; 4 | 5 | import { ENVIRONMENT, SENTRY_DSN, SOURCE_VERSION } from "./lib/environmentVariables"; 6 | import { pageLoadedAt } from "./lib/errors"; 7 | 8 | export const initializeSentry = () => { 9 | 10 | // Initialize Sentry error logging 11 | Sentry.init({ 12 | // Used to define the project to log errors to 13 | dsn: SENTRY_DSN, 14 | 15 | // Used to separate production and staging errors 16 | environment: ENVIRONMENT, 17 | 18 | // Used to track errors across versions 19 | release: SOURCE_VERSION, 20 | 21 | ignoreErrors: [ 22 | "Network Error", 23 | "NetworkError", 24 | "Failed to fetch", 25 | "Network request failed", 26 | "Wrong response id", 27 | "Request failed or timed out", 28 | "Invalid JSON RPC response", 29 | "timeout of 0ms exceeded", 30 | "header not found", 31 | ], 32 | blacklistUrls: [ 33 | // Chrome extensions 34 | /extensions\//i, 35 | /^chrome:\/\//i, 36 | ], 37 | 38 | // Only throw errors generated from scripts at these URLs 39 | whitelistUrls: [ 40 | /.*republicprotocol.*/i, 41 | /.*renproject.*/i, 42 | 43 | // Local testing (localhost and IPv4 addresses) 44 | /.*localhost.*/i, 45 | /.*(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).*/ 46 | ], 47 | 48 | integrations: [new ExtraErrorData()], 49 | }); 50 | 51 | Sentry.configureScope(scope => { 52 | scope.setExtra("loggedIn", false); 53 | 54 | // We set this to false when logging to Sentry explicitly. 55 | scope.setExtra("caught", false); 56 | 57 | scope.setExtra("release", SOURCE_VERSION); 58 | 59 | scope.setExtra("pageLoadedAt", pageLoadedAt()); 60 | }); 61 | }; 62 | -------------------------------------------------------------------------------- /react-client/src/styles/scss/_confirmTrade.scss: -------------------------------------------------------------------------------- 1 | .swap-details { 2 | color: $grey-12; 3 | padding: 20px 0; 4 | 5 | hr { 6 | display: block; 7 | height: 1px; 8 | border: 0; 9 | border-top: 1px solid $foreground; 10 | margin: 1em 0; 11 | padding: 0; 12 | } 13 | } 14 | 15 | .swap-details--values>div { 16 | display: flex; 17 | justify-content: space-between; 18 | margin-bottom: 0; 19 | } 20 | 21 | .swap-details--values>div+div { 22 | margin-top: 20px; 23 | } 24 | 25 | .swap-details--values--left { 26 | font-weight: 500; 27 | } 28 | 29 | .swap-details--values--right { 30 | font-weight: 500; // used to be 600 31 | } 32 | 33 | .swap-details--icons { 34 | display: flex; 35 | justify-content: center; 36 | margin-bottom: 20px; 37 | margin-top: 20px; 38 | 39 | span { 40 | font-weight: 500; // used to be bold 41 | font-size: 14px; 42 | color: $background; 43 | } 44 | 45 | span+span { 46 | opacity: 0.8; 47 | font-weight: 500; 48 | } 49 | 50 | .swap-details--icons--arrow { 51 | display: flex; 52 | justify-content: center; 53 | align-items: center; 54 | width: initial; 55 | color: black; 56 | font-size: 20px; 57 | } 58 | 59 | >div { 60 | width: 100%; 61 | display: flex; 62 | flex-flow: column; 63 | align-items: center; 64 | } 65 | 66 | .swap-details--icon { 67 | width: 42px; 68 | height: 42px; 69 | max-width: 42px; 70 | margin-bottom: 10px; 71 | } 72 | } 73 | 74 | .swap-details hr { 75 | border-top: dashed 1px $background; 76 | } 77 | 78 | .swap-details-heading { 79 | width: 100%; 80 | color: $blue-bright; 81 | font-weight: 500; // used to be 600 82 | display: flex; 83 | justify-content: space-between; 84 | } 85 | 86 | .swap-details--rounded { 87 | border: 1px solid $background; 88 | box-shadow: 0px 1px 2px rgba(0, 26, 58, 0.1); 89 | border-radius: 12px; 90 | padding: 10px 20px; 91 | text-transform: uppercase; 92 | } -------------------------------------------------------------------------------- /react-client/src/styles/images/icons/docs-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /react-client/src/components/views/SelectMarketWrapper.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { SelectMarket } from "@renproject/react-components"; 4 | 5 | import { _catchInteractionErr_ } from "../../lib/errors"; 6 | import { getMarket } from "../../lib/market"; 7 | import { connect, ConnectedProps } from "../../state/connect"; 8 | import { Token, TokenDetails, Tokens } from "../../state/generalTypes"; 9 | import { UIContainer } from "../../state/uiContainer"; 10 | 11 | // tslint:disable: react-unused-props-and-state 12 | interface Props { 13 | top: boolean; 14 | thisToken: Token; 15 | otherToken: Token; 16 | locked?: boolean; 17 | except?: Token; 18 | } 19 | 20 | /** 21 | * SelectMarket allows the user to select a market from two token dropdowns 22 | */ 23 | export const SelectMarketWrapper = connect>([UIContainer])( 24 | ({ containers: [uiContainer], top, thisToken, otherToken, locked, except }) => { 25 | const handleChange = (token: Token): void => { 26 | if (top) { 27 | uiContainer.updateSrcToken(token).catch(error => _catchInteractionErr_(error, "Error in SelectMarketWrapper: updateSrcToken")); 28 | } else { 29 | uiContainer.updateDstToken(token).catch(error => _catchInteractionErr_(error, "Error in SelectMarketWrapper: updateDstToken")); 30 | } 31 | }; 32 | 33 | const newTokens = new Map(Tokens); 34 | const newTokenDetails = newTokens.get(thisToken); 35 | let lockedToken = new Map(); 36 | if (newTokenDetails) { 37 | lockedToken = lockedToken.set(thisToken, newTokenDetails); 38 | } 39 | if (except) { 40 | newTokens.delete(except); 41 | } 42 | return ; 53 | } 54 | ); 55 | -------------------------------------------------------------------------------- /react-client/src/styles/images/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 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 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /react-client/src/components/views/TokenBalance.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { Currency } from "@renproject/react-components"; 4 | import { BigNumber } from "bignumber.js"; 5 | 6 | import { Token, TokenPrices } from "../../state/generalTypes"; 7 | 8 | export const TokenBalance: React.SFC = (props) => { 9 | const { group, token, convertTo, tokenPrices, digits, toReadable, decimals } = props; 10 | 11 | let amount = new BigNumber(props.amount); 12 | 13 | if (toReadable) { 14 | amount = amount.div(new BigNumber(10).pow(decimals || 0)); 15 | } 16 | 17 | if (!amount.isFinite()) { 18 | amount = new BigNumber(0); 19 | } 20 | 21 | if (!convertTo) { 22 | return <>{amount.decimalPlaces(digits || 6).toFixed()}; 23 | } 24 | 25 | if (!tokenPrices) { 26 | return <>--; 27 | } 28 | 29 | const tokenPriceMap = tokenPrices.get(token); 30 | if (!tokenPriceMap) { 31 | return <>-; 32 | } 33 | 34 | const price = tokenPriceMap.get(convertTo); 35 | if (!price) { 36 | return ERR; 37 | } 38 | 39 | let defaultDigits; 40 | switch (convertTo) { 41 | case Currency.CNY: 42 | case Currency.JPY: 43 | case Currency.KRW: 44 | defaultDigits = 0; break; 45 | case Currency.BTC: 46 | case Currency.ETH: 47 | defaultDigits = 3; break; 48 | default: 49 | defaultDigits = 2; 50 | } 51 | defaultDigits = digits === undefined ? defaultDigits : digits; 52 | 53 | amount = amount.multipliedBy(price); 54 | 55 | if (!amount.isFinite()) { 56 | amount = new BigNumber(0); 57 | } 58 | 59 | if (group) { 60 | const moneyFormat: BigNumber.Format = { 61 | decimalSeparator: ".", 62 | groupSeparator: ",", 63 | groupSize: 3, 64 | }; 65 | return <>{amount.toFormat(defaultDigits, moneyFormat)}; 66 | } 67 | 68 | return <>{amount.toFixed(defaultDigits)}; 69 | }; 70 | 71 | export interface TokenAmountConversionOptions { 72 | token: Token; 73 | amount: string | BigNumber; 74 | convertTo?: Currency; 75 | digits?: number; 76 | 77 | toReadable?: boolean; 78 | decimals?: number; 79 | group?: boolean; 80 | 81 | tokenPrices?: TokenPrices; 82 | } 83 | -------------------------------------------------------------------------------- /ethereum-contracts/types/chai/index.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | 3 | /// 4 | 5 | declare module "chai-bignumber" { 6 | function chaiBigNumber(bignumber: any, BN?: any): (chai: any, utils: any) => void; 7 | 8 | namespace chaiBigNumber { 9 | } 10 | 11 | export = chaiBigNumber; 12 | } 13 | 14 | declare namespace Chai { 15 | type BigNumber = number | string | { toNumber: () => number }; 16 | 17 | // For BDD API 18 | interface Assertion extends LanguageChains, NumericComparison, TypeComparison { 19 | bignumber: BigNumberAssert; 20 | } 21 | 22 | // For Assert API 23 | interface Assert { 24 | bignumber: BigNumberAssert; 25 | } 26 | 27 | export interface BigNumberAssert { 28 | finite(actual?: BN, msg?: string): void; 29 | integer(actual?: BN, msg?: string): void; 30 | negative(actual?: BN, msg?: string): void; 31 | zero(actual?: BN, msg?: string): void; 32 | 33 | equal(actual?: BN, expected?: BN, msg?: string): void; 34 | equals(actual?: BN, expected?: BN, msg?: string): void; 35 | eq(actual?: BN, expected?: BN, msg?: string): void; 36 | 37 | greaterThan(actual?: BN, expected?: BN, msg?: string): void; 38 | above(actual?: BN, expected?: BN, msg?: string): void; 39 | gt(actual?: BN, expected?: BN, msg?: string): void; 40 | 41 | greaterThanOrEqualTo(actual?: BN, expected?: BN, msg?: string): void; 42 | least(actual?: BN, expected?: BN, msg?: string): void; 43 | gte(actual?: BN, expected?: BN, msg?: string): void; 44 | 45 | lessThan(actual?: BN, expected?: BN, msg?: string): void; 46 | below(actual?: BN, expected?: BN, msg?: string): void; 47 | lt(actual?: BN, expected?: BN, msg?: string): void; 48 | 49 | lessThanOrEqualTo(actual?: BN, expected?: BN, msg?: string): void; 50 | most(actual?: BN, expected?: BN, msg?: string): void; 51 | lte(actual?: BN, expected?: BN, msg?: string): void; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /react-client/src/components/ErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import * as Sentry from "@sentry/browser"; 2 | import * as React from "react"; 3 | 4 | import { _catchInteractionErr_ } from "../lib/errors"; 5 | 6 | const defaultState = { // Entries must be immutable 7 | error: null as null | Error, 8 | errorInfo: null as null | React.ErrorInfo, 9 | }; 10 | 11 | export class ErrorBoundary extends React.Component { 12 | constructor(props: Props) { 13 | super(props); 14 | this.state = defaultState; 15 | } 16 | 17 | public componentDidCatch = (error: Error, errorInfo: React.ErrorInfo) => { 18 | this.setState({ 19 | error, 20 | errorInfo, 21 | }); 22 | _catchInteractionErr_(error, { 23 | ...errorInfo, 24 | description: `Error caught in ErrorBoundary (${typeof this.props.id === "number" ? "#" : ""}${this.props.id})`, 25 | shownToUser: "As Error Boundary", 26 | }); 27 | } 28 | 29 | /** 30 | * The main render function. 31 | * @dev Should have minimal computation, loops and anonymous functions. 32 | */ 33 | public render(): React.ReactNode { 34 | const { errorInfo, error } = this.state; 35 | if (errorInfo) { 36 | // Error path 37 | return ( 38 |
39 |

Something went wrong.

40 |
41 | {error && error.toString()} 42 |
43 | {errorInfo.componentStack} 44 |
45 | {this.props.popup ?
46 | 47 | 48 |
: null 49 | } 50 |
51 | ); 52 | } 53 | // Normally, just render children 54 | return this.props.children; 55 | } 56 | 57 | private readonly reportFeedback = () => { 58 | Sentry.showReportDialog(); 59 | } 60 | } 61 | 62 | interface Props { 63 | id: number | string; 64 | 65 | /** 66 | * Popup specifies whether or not the Error Boundary is being rendered in 67 | * the popup controller. 68 | */ 69 | popup?: boolean; 70 | 71 | /** 72 | * If `popup` is true, then onCancel should also be provided. 73 | */ 74 | onCancel?(): void; 75 | } 76 | -------------------------------------------------------------------------------- /react-client/src/styles/images/rp-flag-uk.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | union-jack 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ethereum-contracts/migrations/networks.js: -------------------------------------------------------------------------------- 1 | const BN = require("bn.js"); 2 | const ren = require("@renproject/contracts"); 3 | 4 | const config = { 5 | VERSION: "1.0.0", 6 | MINIMUM_BOND: new BN(100000).mul(new BN(10).pow(new BN(18))), 7 | MINIMUM_POD_SIZE: 3, // 24 in production 8 | MINIMUM_EPOCH_INTERVAL: 2, // 14400 in production 9 | DARKNODE_PAYMENT_CYCLE_DURATION_SECONDS: 300, // 300 for testnet (5 minutes in seconds), 86400 in mainnet testing (1 day), 2628000 in production (1 month in seconds) 10 | mintAuthority: "0x723eb4380E03dF6a6f98Cc1338b00cfBE5E45218", // Darknode public key 11 | shiftInFee: 10, 12 | shiftOutFee: 10, 13 | zBTCMinShiftOutAmount: 10000, 14 | zZECMinShiftOutAmount: 10000, 15 | zBCHMinShiftOutAmount: 10000, 16 | dexFees: 10, 17 | renNetwork: { 18 | addresses: { 19 | tokens: { 20 | REN: "", 21 | DAI: "", 22 | }, 23 | ren: { 24 | DarknodeSlasher: "", 25 | DarknodeRegistry: "", 26 | DarknodeRegistryStore: "", 27 | DarknodePaymentStore: "", 28 | DarknodePayment: "", 29 | }, 30 | shifter: { 31 | BTCShifter: "", 32 | ZECShifter: "", 33 | ShifterRegistry: "", 34 | zZEC: "", 35 | zBTC: "", 36 | }, 37 | }, 38 | }, 39 | } 40 | 41 | module.exports = { 42 | mainnet: { 43 | renNetwork: ren.mainnet, 44 | DEX: "", 45 | DEXAdapter: "", 46 | config: { 47 | ...config, 48 | mintAuthority: "TODO", 49 | }, 50 | }, 51 | chaosnet: { 52 | renNetwork: ren.chaosnet, 53 | DEX: "0xf65d91333B1d4d3887016b17741aD602d7768594", 54 | DEXAdapter: "0x9992e9341e496bE5bC8F424DFC1f78A7388D3A58", 55 | BTC_DAI_Reserve: '0x2c4Ce444252FBeB762d789D6457D2BD530E292f6', 56 | ZEC_DAI_Reserve: '0xa08b74DaA6ea1ca4397D1e0C14C517f535A7839c', 57 | config: { 58 | ...config, 59 | mintAuthority: ren.chaosnet.renVM.mintAuthority, 60 | }, 61 | }, 62 | testnet: { 63 | renNetwork: ren.testnet, 64 | DEX: "", 65 | DEXAdapter: "", 66 | BTC_DAI_Reserve: "", 67 | ZEC_DAI_Reserve: "", 68 | config: { 69 | ...config, 70 | mintAuthority: ren.testnet.renVM.mintAuthority, 71 | }, 72 | }, 73 | devnet: { 74 | renNetwork: ren.devnet, 75 | DEX: "", 76 | DEXAdapter: "", 77 | config: { 78 | ...config, 79 | mintAuthority: ren.devnet.renVM.mintAuthority, 80 | }, 81 | }, 82 | config, 83 | } -------------------------------------------------------------------------------- /react-client/src/styles/scss/_exchange.scss: -------------------------------------------------------------------------------- 1 | .exchange { 2 | $padding-top: 150px; 3 | min-height: calc(100vh - 100px - #{$padding-top}); 4 | padding-top: $padding-top; 5 | 6 | @media(max-width: $max-md) { 7 | padding: 0 20px; 8 | } 9 | 10 | @media(min-width: $min-lg) { 11 | width: 430px; 12 | margin: 0 auto; 13 | } 14 | } 15 | 16 | .exchange-inner { 17 | display: flex; 18 | align-items: center; 19 | width: 100%; 20 | padding: 0; 21 | 22 | @media(max-width: $max-md) { 23 | flex-flow: column; 24 | 25 | >* { 26 | width: 100%; 27 | margin-bottom: 30px; 28 | } 29 | } 30 | } 31 | 32 | 33 | .exchange--center { 34 | flex-grow: 1; 35 | 36 | @media(max-width: $max-md) { 37 | order: -1; 38 | padding: 0 20px; 39 | } 40 | } 41 | 42 | .connect-button { 43 | border: 1.6px solid $blue-bright !important; 44 | font-weight: 500 !important; 45 | color: #21262C !important; 46 | font-weight: 500 !important; 47 | font-size: 14px !important; 48 | 49 | background: #E0E3EB !important; 50 | box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.3) !important; 51 | border-radius: 12px !important; 52 | border: none !important; 53 | } 54 | 55 | // TABS 56 | 57 | .exchange--tabs { 58 | background: #1C2127; 59 | border: 1px solid rgba(145, 149, 160, 0.1); 60 | box-sizing: border-box; 61 | box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.5); 62 | border-radius: 20px; 63 | margin: 0 -20px; 64 | } 65 | 66 | .exchange--tab { 67 | background: none; 68 | border: none; 69 | font-size: 14px; 70 | line-height: 16px; 71 | text-align: center; 72 | 73 | color: #9195A0; 74 | opacity: 0.4; 75 | width: 50%; 76 | transition: all 200ms; 77 | border: 0px solid rgba(145, 149, 160, 0.1); 78 | border-radius: 20px; 79 | } 80 | 81 | .exchange--tab--selected { 82 | background: #282C35; 83 | border: 1px solid rgba(145, 149, 160, 0.1); 84 | border-radius: 20px; 85 | box-sizing: border-box; 86 | box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.1); 87 | color: #E0E3EB; 88 | opacity: 1; 89 | } 90 | 91 | .unaudited { 92 | margin: 0 -20px; 93 | 94 | background: rgba(249, 59, 84, 0.2); 95 | border: 1px solid #F93B54; 96 | box-sizing: border-box; 97 | border-radius: 16px; 98 | 99 | margin-bottom: 40px; 100 | 101 | font-weight: normal; 102 | font-size: 12px; 103 | line-height: 29px; 104 | letter-spacing: 0.5px; 105 | 106 | text-align: center; 107 | 108 | color: #E0E3EB; 109 | 110 | svg { 111 | width: 12px; 112 | height: 12px; 113 | } 114 | 115 | 116 | } -------------------------------------------------------------------------------- /ethereum-contracts/contracts/ChaosnetChallenges/SimplePuzzle.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.12; 2 | 3 | import "./Puzzle.sol"; 4 | 5 | import "darknode-sol/contracts/Shifter/ShifterRegistry.sol"; 6 | 7 | contract SimplePuzzle is Puzzle { 8 | /// @param _registry The Shifter registry contract address 9 | /// @param _tokenSymbol The token symbol for the reward and the shifting 10 | /// @param _secretHash The secret hash 11 | /* solium-disable-next-line no-empty-blocks */ 12 | constructor( 13 | ShifterRegistry _registry, 14 | string memory _tokenSymbol, 15 | bytes memory _secretHash, 16 | uint256 _maxGasPrice 17 | ) public Puzzle( 18 | _registry, 19 | _tokenSymbol, 20 | _secretHash, 21 | _maxGasPrice 22 | ) {} 23 | 24 | /// @notice Allows someone to try and claim the reward by submitting the secret. 25 | /// @param _rewardAddress The address that should receive the reward if the secret is correct. 26 | /// @param _secret The secret. 27 | function claimReward(bytes memory _rewardAddress, bytes memory _secret) public onlyNotFrontRunning { 28 | require(!rewardClaimed, "reward already claimed"); 29 | require(validateSecret(_secret), "invalid secret"); 30 | rewardClaimed = true; 31 | uint256 amount = rewardAmount(); 32 | // Shift out the funds to the specified address 33 | registry.getShifterBySymbol(tokenSymbol).shiftOut(_rewardAddress, amount); 34 | 35 | emit LogRewardClaimed(_rewardAddress, _secret, amount); 36 | } 37 | } 38 | 39 | contract UnknownPuzzle is SimplePuzzle { 40 | constructor( 41 | ShifterRegistry _registry, 42 | bytes memory _secretHash, 43 | uint256 _maxGasPrice 44 | ) public SimplePuzzle( 45 | _registry, 46 | "zBTC", 47 | _secretHash, 48 | _maxGasPrice 49 | ) {} 50 | } 51 | 52 | contract BTCPuzzle is SimplePuzzle { 53 | constructor( 54 | ShifterRegistry _registry, 55 | bytes memory _secretHash, 56 | uint256 _maxGasPrice 57 | ) public SimplePuzzle( 58 | _registry, 59 | "zBTC", 60 | _secretHash, 61 | _maxGasPrice 62 | ) {} 63 | } 64 | 65 | contract BCHPuzzle is SimplePuzzle { 66 | constructor( 67 | ShifterRegistry _registry, 68 | bytes memory _secretHash, 69 | uint256 _maxGasPrice 70 | ) public SimplePuzzle( 71 | _registry, 72 | "zBCH", 73 | _secretHash, 74 | _maxGasPrice 75 | ) {} 76 | } 77 | 78 | contract ZECPuzzle is SimplePuzzle { 79 | constructor( 80 | ShifterRegistry _registry, 81 | bytes memory _secretHash, 82 | uint256 _maxGasPrice 83 | ) public SimplePuzzle( 84 | _registry, 85 | "zZEC", 86 | _secretHash, 87 | _maxGasPrice 88 | ) {} 89 | } 90 | -------------------------------------------------------------------------------- /react-client/src/components/views/exchange-forms/OrderForm.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { Loading } from "@renproject/react-components"; 4 | 5 | import { _catchBackgroundErr_ } from "../../../lib/errors"; 6 | import { getMarket } from "../../../lib/market"; 7 | import { connect, ConnectedProps } from "../../../state/connect"; 8 | import { CommitmentType } from "../../../state/persistentContainer"; 9 | import { UIContainer } from "../../../state/uiContainer"; 10 | import { OrderFormInputs } from "./OrderFormInputs"; 11 | 12 | interface Props { 13 | handleLogin: () => void; 14 | } 15 | 16 | /** 17 | * NewOrder is a visual component for allowing users to open new orders 18 | */ 19 | export const OrderForm = connect>([UIContainer])( 20 | ({ handleLogin, containers: [uiContainer] }) => { 21 | 22 | const openOrder = React.useCallback(async () => { 23 | await uiContainer.updateCommitmentType(CommitmentType.Trade); 24 | uiContainer.setSubmitting(true).catch(error => _catchBackgroundErr_(error, "Error in OrderForm: setSubmitting")); 25 | }, [uiContainer]); 26 | 27 | const { orderInputs, address, submitting } = uiContainer.state; 28 | 29 | const orderInput = orderInputs; 30 | const market = getMarket(orderInput.srcToken, orderInput.dstToken); 31 | 32 | const marketPrice = 0; 33 | 34 | const loggedIn = address !== null; 35 | const sufficientBalance = uiContainer.sufficientBalance(); 36 | const validVolume = uiContainer.validVolume(); 37 | const disabled = !loggedIn || !sufficientBalance || !validVolume; 38 | 39 | let button; 40 | if (!market) { 41 | button = ; 44 | } else if (!loggedIn) { 45 | button = ; 51 | } else { 52 | button = ; 64 | } 65 | 66 | return
67 | 68 |
{button}
69 |
; 70 | } 71 | ); 72 | -------------------------------------------------------------------------------- /react-client/src/components/views/exchange-forms/LiquidityForm.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { Loading } from "@renproject/react-components"; 4 | 5 | import { _catchBackgroundErr_ } from "../../../lib/errors"; 6 | import { getMarket } from "../../../lib/market"; 7 | import { connect, ConnectedProps } from "../../../state/connect"; 8 | import { CommitmentType } from "../../../state/persistentContainer"; 9 | import { LiquidityTabs, UIContainer } from "../../../state/uiContainer"; 10 | import { LiquidityFormInputs } from "./LiquidityFormInputs"; 11 | 12 | interface Props { 13 | handleLogin: () => void; 14 | } 15 | 16 | export const LiquidityForm = connect>([UIContainer])( 17 | ({ handleLogin, containers: [uiContainer] }) => { 18 | 19 | const { liquidityTab, orderInputs, address, submitting } = uiContainer.state; 20 | 21 | const openOrder = React.useCallback(async () => { 22 | await uiContainer.updateCommitmentType(liquidityTab === LiquidityTabs.Add ? CommitmentType.AddLiquidity : CommitmentType.RemoveLiquidity); 23 | uiContainer.setSubmitting(true).catch(error => _catchBackgroundErr_(error, "Error in LiquidityForm: openOrder")); 24 | }, [uiContainer, liquidityTab]); 25 | 26 | const market = getMarket(orderInputs.srcToken, orderInputs.dstToken); 27 | 28 | const marketPrice = 0; 29 | 30 | const loggedIn = address !== null; 31 | const sufficientBalance = uiContainer.sufficientBalance(); 32 | const validVolume = uiContainer.validVolume(); 33 | const disabled = !loggedIn || !sufficientBalance || !validVolume; 34 | 35 | let button; 36 | if (!market) { 37 | button = ; 40 | } else if (!loggedIn) { 41 | button = ; 47 | } else { 48 | button = ; 60 | } 61 | 62 | return
63 | 64 |
{button}
65 |
; 66 | } 67 | ); 68 | -------------------------------------------------------------------------------- /react-client/src/lib/market.ts: -------------------------------------------------------------------------------- 1 | import { Currency } from "@renproject/react-components"; 2 | import { Map, OrderedMap } from "immutable"; 3 | 4 | import { Token, TokenPrices } from "../state/generalTypes"; 5 | 6 | const CoinGeckoIDs = Map() 7 | .set(Token.DAI, "dai") 8 | .set(Token.BTC, "bitcoin") 9 | .set(Token.BCH, "bitcoin-cash") 10 | .set(Token.ETH, "ethereum") 11 | .set(Token.ZEC, "zcash"); 12 | 13 | /** 14 | * Retrieves the current pricepoint for two currencies. 15 | * @param fstCode The first currency. 16 | * @param sndCode The second currency. 17 | * @returns An array containing the price with respect to the currencies, and the 24 hour percent change. 18 | */ 19 | const fetchDetails = async (geckoID: string) => { 20 | try { 21 | const url = `https://api.coingecko.com/api/v3/coins/${geckoID}?localization=false&tickers=false&market_data=true&community_data=false&developer_data=false&sparkline=false`; 22 | const response = await fetch(url); 23 | return response.json(); 24 | } catch (error) { 25 | throw new Error(`Failed to fetch prices for ${geckoID}`); 26 | } 27 | }; 28 | 29 | export const getTokenPricesInCurrencies = async (): Promise => 30 | /*await*/ CoinGeckoIDs 31 | .map(coinGeckoID => fetchDetails(coinGeckoID)) 32 | .reduce(async (pricesPromise, detailsPromise, token) => { 33 | const prices = await pricesPromise; 34 | try { 35 | const data = await detailsPromise; 36 | const price = Map(data.market_data.current_price); 37 | return prices.set(token, price); 38 | } catch (error) { 39 | return prices; 40 | } 41 | }, Promise.resolve(Map>())); 42 | 43 | export enum MarketPair { 44 | DAI_BTC = "DAI/BTC", 45 | DAI_ZEC = "DAI/ZEC", 46 | DAI_BCH = "DAI/BCH", 47 | // ZEC_BTC = "ZEC/BTC", 48 | } 49 | 50 | interface MarketDetails { 51 | symbol: MarketPair; 52 | quote: Token; 53 | base: Token; 54 | } 55 | 56 | const MarketPairs = OrderedMap() 57 | // BTC pairs 58 | .set(MarketPair.DAI_BTC, { symbol: MarketPair.DAI_BTC, quote: Token.BTC, base: Token.DAI }) 59 | .set(MarketPair.DAI_ZEC, { symbol: MarketPair.DAI_ZEC, quote: Token.ZEC, base: Token.DAI }) 60 | .set(MarketPair.DAI_BCH, { symbol: MarketPair.DAI_BCH, quote: Token.BCH, base: Token.DAI }) 61 | // .set(MarketPair.ZEC_BTC, { symbol: MarketPair.ZEC_BTC, quote: Token.BTC, base: Token.ZEC }) 62 | ; 63 | 64 | export const getMarket = (left: Token, right: Token): MarketPair | undefined => { 65 | return ( 66 | MarketPairs.findKey((marketDetails: MarketDetails) => marketDetails.base === left && marketDetails.quote === right) || 67 | MarketPairs.findKey((marketDetails: MarketDetails) => marketDetails.base === right && marketDetails.quote === left) || 68 | undefined 69 | ); 70 | }; 71 | -------------------------------------------------------------------------------- /react-client/src/components/views/order-popup/TokenAllowance.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { Loading } from "@renproject/react-components"; 4 | 5 | import { _catchInteractionErr_, safeJSONStringify } from "../../../lib/errors"; 6 | import { Token } from "../../../state/generalTypes"; 7 | import { Commitment } from "../../../state/persistentContainer"; 8 | import { network } from "../../../state/sdkContainer"; 9 | import { Popup } from "../Popup"; 10 | 11 | export const TokenAllowance: React.StatelessComponent<{ 12 | token: Token, 13 | amount: string, 14 | commitment: Commitment | null, 15 | orderID: string; 16 | submit: (orderID: string) => Promise, 17 | hide?: () => void, 18 | }> = ({ token, amount, commitment, orderID, submit, hide }) => { 19 | const [submitting, setSubmitting] = React.useState(false); 20 | const [error, setError] = React.useState(null as Error | null); 21 | const [failedTransaction, setFailedTransaction] = React.useState(null as string | null); 22 | 23 | const onSubmit = () => { 24 | setError(null); 25 | setSubmitting(true); 26 | submit(orderID).catch(err => { 27 | setSubmitting(false); 28 | 29 | // Ignore user denying error in MetaMask. 30 | if (String(err.message || err).match(/User denied transaction signature/)) { 31 | return; 32 | } 33 | 34 | _catchInteractionErr_(err, "Error in TokenAllowance: submit"); 35 | const match = String(err.message || err).match(/"transactionHash": "(0x[a-fA-F0-9]{64})"/); 36 | if (match && match.length >= 2) { 37 | setFailedTransaction(match[1]); 38 | err = new Error("Transaction reverted."); 39 | } 40 | setError(err); 41 | }); 42 | }; 43 | return 44 |
45 |
46 |

Transfer Approval

47 |
48 | Please approve the transfer of {amount} {token.toUpperCase()}. 49 |
50 |
51 |
52 | {error ? 53 | Error submitting to Ethereum: {error.message || safeJSONStringify(error)} 54 | {failedTransaction ? <> 55 |
56 | See the Transaction Stack Trace for more details. 57 | : null} 58 |
: null} 59 |
60 | 61 |
62 |
63 |
64 |
; 65 | }; 66 | -------------------------------------------------------------------------------- /react-client/src/components/views/tutorial-popup/Tutorial.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import createPersistedState from "use-persisted-state"; 4 | 5 | import { ReactComponent as Docs } from "../../../styles/images/icons/docs.svg"; 6 | import { ReactComponent as FAQ } from "../../../styles/images/icons/faq.svg"; 7 | import { ReactComponent as HowItWorks } from "../../../styles/images/icons/howitworks.svg"; 8 | import { Popup } from "../Popup"; 9 | import { BUILDWITHRENVM_LINK, FAQ_LINK, READTHEDOCS_LINK, tutorialPages } from "./TutorialPages"; 10 | 11 | const usePageState = createPersistedState("tutorial-page"); 12 | 13 | /** 14 | * Shows a tutorial popup with multiple pages. The pages are defined in 15 | * [[TutorialPages]]. 16 | */ 17 | export const Tutorial: React.StatelessComponent<{ 18 | cancel: () => void; 19 | }> = ({ cancel }) => { 20 | 21 | // `page` represents the index of the current page being shown, out of the 22 | // pages in `tutorialPages`. 23 | const [page, setPage] = usePageState(0); 24 | 25 | const nextPage = React.useCallback(async () => { 26 | if (page < tutorialPages.length - 1) { 27 | setPage(page + 1); 28 | } else { 29 | cancel(); 30 | } 31 | }, [page, setPage, cancel]); 32 | 33 | const previousPage = React.useCallback(async () => { 34 | if (page > 0) { 35 | setPage(page - 1); 36 | } 37 | }, [page, setPage]); 38 | 39 | if (page < 0 || page >= tutorialPages.length) { 40 | setPage(0); 41 | return <>; 42 | } 43 | 44 | return 45 |
46 |
47 |
48 |
Setup Guide
49 |
    50 | {tutorialPages.map(({ name }, index) => 51 | // tslint:disable-next-line: react-this-binding-issue jsx-no-lambda 52 |
  • setPage(index)} role="tab" key={name} className={`${page >= index ? "checked" : ""} ${page === index ? "selected" : ""}`}>{name}
  • 53 | )} 54 |
55 |
56 |
57 |
58 | 63 |
64 |
65 |
66 | {React.createElement(tutorialPages[page].node, { nextPage, previousPage })} 67 |
68 |
69 |
; 70 | }; 71 | -------------------------------------------------------------------------------- /ethereum-contracts/contracts/ChaosnetChallenges/ShiftInPuzzle.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.12; 2 | 3 | import "./Puzzle.sol"; 4 | 5 | import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 6 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 7 | import "darknode-sol/contracts/Shifter/ShifterRegistry.sol"; 8 | import "darknode-sol/contracts/libraries/Compare.sol"; 9 | 10 | contract ShiftInPuzzle is Puzzle { 11 | using SafeMath for uint256; 12 | 13 | /// @param _registry The Shifter registry contract address 14 | /// @param _tokenSymbol The token symbol for the reward and the shifting 15 | /// @param _secretHash The secret hash 16 | /* solium-disable-next-line no-empty-blocks */ 17 | constructor( 18 | ShifterRegistry _registry, 19 | string memory _tokenSymbol, 20 | bytes memory _secretHash, 21 | uint256 _maxGasPrice 22 | ) public Puzzle( 23 | _registry, 24 | _tokenSymbol, 25 | _secretHash, 26 | _maxGasPrice 27 | ) {} 28 | 29 | /// @notice Allows someone to try and claim the reward by submitting the secret. 30 | /// @param _rewardAddress The address that should receive the shiftedOut tokens and the potential reward. 31 | /// @param _secret The secret. 32 | /// @param _amount The amount of token provided to the Darknodes in Sats. 33 | /// @param _nHash The hash of the nonce returned by the Darknodes. 34 | /// @param _sig The signature returned by the Darknodes. 35 | function claimReward( 36 | // Payload 37 | bytes memory _rewardAddress, 38 | bytes memory _secret, 39 | // Required 40 | uint256 _amount, 41 | bytes32 _nHash, 42 | bytes memory _sig 43 | ) public onlyNotFrontRunning { 44 | require(_amount > 0, "amount must be greater than 0"); 45 | uint256 prizeAmount = rewardAmount(); 46 | 47 | // Construct the payload hash and verify the signature to ensure the Darknodes have 48 | // received the token. 49 | bytes32 pHash = hashPayload(_rewardAddress, _secret); 50 | uint256 transferAmount = registry.getShifterBySymbol(tokenSymbol).shiftIn(pHash, _amount, _nHash, _sig); 51 | 52 | // If the secret is correct, give the reward 53 | if (validateSecret(_secret) && !rewardClaimed) { 54 | rewardClaimed = true; 55 | transferAmount = transferAmount.add(prizeAmount); 56 | emit LogRewardClaimed(_rewardAddress, _secret, prizeAmount); 57 | } 58 | 59 | // Shift out the funds to the specified address 60 | registry.getShifterBySymbol(tokenSymbol).shiftOut(_rewardAddress, transferAmount); 61 | } 62 | 63 | /// @notice Get the hash payload 64 | /// 65 | /// @param _rewardAddress The address that should receive the shiftedOut tokens and the potential reward. 66 | /// @param _secret The secret. 67 | function hashPayload( 68 | bytes memory _rewardAddress, 69 | bytes memory _secret 70 | ) public pure returns (bytes32) { 71 | return keccak256(abi.encode(_rewardAddress, _secret)); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /ethereum-contracts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shifter-sol", 3 | "version": "1.0.0", 4 | "config": { 5 | "truffleBuildPath": "../react-client/src/contracts" 6 | }, 7 | "scripts": { 8 | "generate": "truffle compile && yarn run typings --target truffle", 9 | "typings": "echo $npm_package_config_truffleBuildPath'/**/*.json' && typechain $npm_package_config_truffleBuildPath'/**/*.json'", 10 | "coverage": "yarn run generate && solidity-coverage", 11 | "coveralls": "cat ./coverage/lcov.info | coveralls", 12 | "test": "yarn run generate && truffle test", 13 | "bindings:ts": "truffle compile && yarn run typings --target web3-v1 --outDir '../react-client/src/lib/contracts'", 14 | "bindings:go": "solc darknode-sol=`pwd`/node_modules/darknode-sol openzeppelin-solidity=`pwd`/node_modules/openzeppelin-solidity --combined-json bin,abi,userdoc,devdoc,metadata $(find contracts -type f -name '*.sol') | abigen -pkg bindings --out bindings.go --abi -", 15 | "deploy": "truffle migrate --network $NETWORK --all", 16 | "deploy-skipDryRun": "yarn deploy --skipDryRun", 17 | "verify": "truffle run verify --network $NETWORK", 18 | "clean-build": "grep -R -l 'networks\": {}' $npm_package_config_truffleBuildPath --exclude-dir=development | xargs rm; node $npm_package_config_truffleBuildPath/clean.js", 19 | "prepare": "patch-package" 20 | }, 21 | "dependencies": { 22 | "darknode-sol": "https://github.com/renproject/darknode-sol#42f1a180938ee3a81eb84291281a68675f2ef164", 23 | "openzeppelin-solidity": "2.4.0" 24 | }, 25 | "devDependencies": { 26 | "@renproject/contracts": "0.3.25", 27 | "@types/chai": "^4.2.5", 28 | "@types/chai-as-promised": "^7.1.2", 29 | "@types/elliptic": "^6.4.10", 30 | "@types/mocha": "^5.2.7", 31 | "@types/node": "^12.12.11", 32 | "any-promise": "^1.3.0", 33 | "bignumber.js": "^9.0.0", 34 | "bn.js": "^5.0.0", 35 | "chai": "^4.2.0", 36 | "chai-as-promised": "^7.1.1", 37 | "chai-bignumber": "github:ren-forks/chai-bignumber.git#afa6f46dcbef0b7e622dc27b9b3354fc67afafbc", 38 | "coveralls": "^3.0.8", 39 | "dotenv": "^8.2.0", 40 | "elliptic": "^6.5.1", 41 | "eth-gas-reporter": "^0.2.12", 42 | "ethereumjs-util": "^6.2.0", 43 | "ganache-cli": "^6.7.0", 44 | "patch-package": "^6.2.0", 45 | "postinstall-postinstall": "^2.0.0", 46 | "solc": "0.5.12", 47 | "solidity-coverage": "0.6.7", 48 | "truffle": "^5.1.0", 49 | "truffle-hdwallet-provider": "^1.0.17", 50 | "truffle-plugin-verify": "^0.3.5", 51 | "truffle-typings": "^1.0.8", 52 | "ts-generator": "^0.0.8", 53 | "ts-node": "^8.5.2", 54 | "tslint": "^5.20.1", 55 | "typechain": "^1.0.3", 56 | "typechain-target-truffle": "^1.0.2", 57 | "typechain-target-web3-v1": "^1.0.2", 58 | "typescript": "^3.7.2" 59 | }, 60 | "resolutions": { 61 | "solc": "0.5.12", 62 | "scrypt": "https://github.com/ren-forks/node-scrypt" 63 | }, 64 | "author": "Ren", 65 | "license": "GPL-3.0-or-later" 66 | } 67 | -------------------------------------------------------------------------------- /react-client/src/components/views/LoggedOutPopup.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { Blocky, Loading } from "@renproject/react-components"; 4 | 5 | import { _catchInteractionErr_ } from "../../lib/errors"; 6 | import { connect, ConnectedProps } from "../../state/connect"; 7 | import { SDKContainer } from "../../state/sdkContainer"; 8 | import { UIContainer } from "../../state/uiContainer"; 9 | import { Popup } from "./Popup"; 10 | 11 | interface Props { 12 | oldAccount: string; 13 | } 14 | 15 | export const LoggedOutPopup = connect>([UIContainer, SDKContainer])( 16 | ({ oldAccount, containers: [uiContainer, sdkContainer] }) => { 17 | const [accounts, setAccounts] = React.useState(null as string[] | null); 18 | 19 | const { web3, networkID } = uiContainer.state; 20 | 21 | const onLogin = React.useCallback((address: string) => { 22 | if (!web3) { 23 | return; 24 | } 25 | uiContainer.connect(web3, address, networkID).catch(error => _catchInteractionErr_(error, "Error in LoggedOutPopup: uiContainer.connect")); 26 | sdkContainer.connect(web3, address, networkID).catch(error => _catchInteractionErr_(error, "Error in LoggedOutPopup: sdkContainer.connect")); 27 | }, [uiContainer, sdkContainer, web3, networkID]); 28 | 29 | const close = React.useCallback(() => { 30 | uiContainer.setLoggedOut().catch(error => _catchInteractionErr_(error, "Error in LoggedOutPopup: setLoggedOut")); 31 | }, [uiContainer]); 32 | 33 | React.useEffect(() => { 34 | (async () => { 35 | if (web3) { 36 | const newAccounts = await web3.eth.getAccounts(); 37 | setAccounts(newAccounts); 38 | } 39 | })().catch(error => _catchInteractionErr_(error, "Error in LoggedOutPopup: getAccounts")); 40 | }, [networkID, web3]); 41 | 42 | return 43 |
44 |

Logged out

45 |

The address {oldAccount.slice(0, 12)}{oldAccount.slice(12, -6)}{oldAccount.slice(-6, -1)} is no longer selected in your Web3 wallet.

46 | {accounts ? 47 | accounts.length > 0 ? 48 | <> 49 |

Select one of the accounts below to continue trading:

50 |
51 | {accounts.map(account => { 52 | const onClick = () => onLogin(account); 53 | return ; 56 | })} 57 |
58 | : 59 |

Log in to continue trading.

60 | : 61 | } 62 |
63 |
; 64 | } 65 | ); 66 | -------------------------------------------------------------------------------- /react-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chaosdex", 3 | "version": "1.0.0", 4 | "private": true, 5 | "homepage": "https://chaosdex.herokuapp.com", 6 | "scripts": { 7 | "lint:unused-exports": "ts-unused-exports ./tsconfig.json", 8 | "lint:outdated-imports": "ncu", 9 | "lint:ts": "tslint --project ./ -t stylish --quiet", 10 | "lint:extra": "$npm_execpath run --silent lint:unused-exports; $npm_execpath run --silent lint:outdated-imports", 11 | "lint:all": "$npm_execpath run --silent lint:ts && $npm_execpath run --silent lint:extra", 12 | "lint": "$npm_execpath run --silent lint:ts", 13 | "unused-exports": "ts-unused-exports ./tsconfig.json src/**/*.ts src/**/*.tsx", 14 | "start": "react-scripts start", 15 | "build": "NODE_OPTIONS=--max_old_space_size=4096 REACT_APP_SOURCE_VERSION=$SOURCE_VERSION react-scripts build", 16 | "test": "react-scripts test", 17 | "eject": "react-scripts eject", 18 | "predeploy": "$npm_execpath run build", 19 | "deploy": "gh-pages -d build" 20 | }, 21 | "dependencies": { 22 | "@renproject/fonts": "^1.0.1", 23 | "@renproject/react-components": "1.0.37", 24 | "@renproject/ren": "^0.2.3", 25 | "@sentry/browser": "^5.10.2", 26 | "@sentry/core": "^5.10.2", 27 | "@sentry/integrations": "^5.10.2", 28 | "@types/bchaddrjs": "^0.4.0", 29 | "@types/bn.js": "^4.11.5", 30 | "@types/node": "^13.1.0", 31 | "@types/qrcode.react": "^1.0.0", 32 | "@types/qs": "^6.9.0", 33 | "@types/react": "^16.9.17", 34 | "@types/react-circular-progressbar": "^1.1.0", 35 | "@types/react-copy-to-clipboard": "^4.3.0", 36 | "@types/react-dom": "^16.9.4", 37 | "@types/react-router-dom": "^5.1.3", 38 | "@types/recharts": "^1.8.4", 39 | "axios": "^0.19.0", 40 | "bchaddrjs": "^0.4.4", 41 | "bignumber.js": "^9.0.0", 42 | "bn.js": "^5.1.0", 43 | "bootstrap": "^4.4.1", 44 | "history": "^4.10.1", 45 | "immutable": "^4.0.0-rc.12", 46 | "localforage": "^1.7.3", 47 | "moment": "^2.24.0", 48 | "qrcode.react": "^1.0.0", 49 | "qs": "^6.9.1", 50 | "react": "^16.12.0", 51 | "react-bootstrap": "^1.0.0-beta.9", 52 | "react-circular-progressbar": "^2.0.3", 53 | "react-copy-to-clipboard": "^5.0.2", 54 | "react-dom": "^16.12.0", 55 | "react-router-dom": "^5.1.2", 56 | "react-super-responsive-table": "^5.1.1", 57 | "recharts": "^1.8.5", 58 | "svgo": "1.3.2", 59 | "unstated": "^2.1.1", 60 | "unstated-persist": "^0.0.4", 61 | "use-persisted-state": "^0.3.0", 62 | "wallet-address-validator": "^0.2.4", 63 | "web3": "^2.0.0-alpha.1" 64 | }, 65 | "devDependencies": { 66 | "gh-pages": "^2.1.1", 67 | "ncu": "^0.2.1", 68 | "node-sass": "^4.13.0", 69 | "npm-check-updates": "^4.0.1", 70 | "react-scripts": "^3.3.0", 71 | "ts-unused-exports": "^5.2.0", 72 | "tslint": "^5.20.1", 73 | "tslint-microsoft-contrib": "^6.2.0", 74 | "tslint-react": "^4.1.0", 75 | "typescript": "^3.7.4" 76 | }, 77 | "browserslist": [ 78 | ">0.2%", 79 | "not dead", 80 | "not ie <= 11", 81 | "not op_mini all" 82 | ] 83 | } 84 | -------------------------------------------------------------------------------- /react-client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 29 | ChaosDEX 30 | 31 | 33 | 34 | 35 | 36 | 39 |
40 | 41 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /ethereum-contracts/truffle.js: -------------------------------------------------------------------------------- 1 | require("ts-node/register"); 2 | require("dotenv").config(); 3 | 4 | const path = require("path"); 5 | // @ts-ignore 6 | const packageJSON = require("./package.json"); 7 | 8 | const HDWalletProvider = require("truffle-hdwallet-provider"); 9 | 10 | const { execSync } = require("child_process") 11 | 12 | const GWEI = 1000000000; 13 | const commitHash = execSync("git describe --always --long").toString().trim(); 14 | 15 | if ((process.env.NETWORK || "").match(/devnet|testnet|chaosnet|mainnet/) && process.env.INFURA_KEY === undefined) { 16 | throw new Error("Must set INFURA_KEY"); 17 | } 18 | 19 | 20 | module.exports = { 21 | networks: { 22 | devnet: { 23 | // @ts-ignore 24 | provider: () => new HDWalletProvider(process.env.MNEMONIC_KOVAN, `https://kovan.infura.io/v3/${process.env.INFURA_KEY}`), 25 | network_id: 42, 26 | gas: 6721975, 27 | gasPrice: 10 * GWEI, 28 | }, 29 | testnet: { 30 | // @ts-ignore 31 | provider: () => new HDWalletProvider(process.env.MNEMONIC_KOVAN, `https://kovan.infura.io/v3/${process.env.INFURA_KEY}`), 32 | network_id: 42, 33 | gas: 6721975, 34 | gasPrice: 10 * GWEI, 35 | }, 36 | chaosnet: { 37 | // @ts-ignore 38 | provider: () => new HDWalletProvider(process.env.MNEMONIC_MAINNET, `https://mainnet.infura.io/v3/${process.env.INFURA_KEY}`), 39 | network_id: 1, 40 | gas: 6721975, 41 | gasPrice: 10 * GWEI, 42 | }, 43 | development: { 44 | host: "localhost", 45 | port: 8545, 46 | network_id: "*", 47 | }, 48 | coverage: { 49 | host: "localhost", 50 | network_id: "*", 51 | port: 8555, // <-- If you change this, also set the port option in .solcover.js. 52 | gas: 0xfffffffffff, // <-- Use this high gas value 53 | gasPrice: 0x01 // <-- Use this low gas price 54 | }, 55 | develop: { 56 | port: 8545 57 | }, 58 | }, 59 | mocha: { 60 | // // Use with `npm run test`, not with `npm run coverage` 61 | // reporter: 'eth-gas-reporter', 62 | // reporterOptions: { 63 | // currency: 'USD', 64 | // gasPrice: 21 65 | // }, 66 | enableTimeouts: false, 67 | useColors: true, 68 | bail: false, 69 | }, 70 | compilers: { 71 | solc: { 72 | version: "0.5.12", 73 | settings: { 74 | evmVersion: "petersburg", 75 | optimizer: { 76 | enabled: true, 77 | runs: 200, 78 | } 79 | } 80 | } 81 | }, 82 | plugins: [ 83 | "truffle-plugin-verify" 84 | ], 85 | api_keys: { 86 | etherscan: process.env.ETHERSCAN_KEY, 87 | }, 88 | verify: { 89 | preamble: ` 90 | Deployed by Ren Project, https://renproject.io 91 | 92 | Commit hash: ${commitHash} 93 | Repository: https://github.com/renproject/darknode-sol 94 | Issues: https://github.com/renproject/darknode-sol/issues 95 | 96 | Licenses 97 | openzeppelin-solidity: (MIT) https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/LICENSE 98 | darknode-sol: (GNU GPL V3) https://github.com/renproject/darknode-sol/blob/master/LICENSE 99 | ` 100 | }, 101 | contracts_build_directory: path.join(__dirname, packageJSON.config.truffleBuildPath, process.env.NETWORK || "development"), 102 | }; 103 | -------------------------------------------------------------------------------- /ethereum-contracts/contracts/ChaosnetChallenges/Puzzle.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.12; 2 | 3 | import "darknode-sol/contracts/Shifter/ShifterRegistry.sol"; 4 | import "darknode-sol/contracts/libraries/Compare.sol"; 5 | 6 | import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 7 | import "openzeppelin-solidity/contracts/math/SafeMath.sol"; 8 | import "openzeppelin-solidity/contracts/token/ERC20/SafeERC20.sol"; 9 | import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; 10 | 11 | contract Puzzle is Ownable { 12 | using SafeMath for uint256; 13 | using SafeERC20 for ERC20; 14 | 15 | bytes public secretHash; // The hash of the secret 16 | string public tokenSymbol; // The symbol of the reward token 17 | bool public rewardClaimed; // Whether the puzzle has been solved or not 18 | 19 | uint256 public maxGasPrice; // Max tx gas price to avoid front running 20 | 21 | ShifterRegistry public registry; 22 | 23 | modifier onlyNotFrontRunning() { 24 | require(tx.gasprice <= maxGasPrice, "gas price is too high"); 25 | _; 26 | } 27 | 28 | event LogRewardClaimed(bytes _rewardAddress, bytes _secret, uint256 _rewardAmount); 29 | 30 | /// @param _registry The Shifter registry contract address 31 | /// @param _tokenSymbol The token symbol for the reward and the shifting 32 | /// @param _secretHash The secret hash 33 | constructor( 34 | ShifterRegistry _registry, 35 | string memory _tokenSymbol, 36 | bytes memory _secretHash, 37 | uint256 _maxGasPrice 38 | ) public { 39 | registry = _registry; 40 | tokenSymbol = _tokenSymbol; 41 | secretHash = _secretHash; 42 | maxGasPrice = _maxGasPrice; 43 | } 44 | 45 | /// @notice The amount of reward for solving the puzzle 46 | function rewardAmount() public view returns (uint256) { 47 | ERC20 token = ERC20(registry.getTokenBySymbol(tokenSymbol)); 48 | return token.balanceOf(address(this)); 49 | } 50 | 51 | /// @notice Funds the contract with claimable rewards 52 | /// @param _amount The amount of token provided to the Darknodes in Sats. 53 | /// @param _nHash The hash of the nonce returned by the Darknodes. 54 | /// @param _sig The signature returned by the Darknodes. 55 | function fund( 56 | // Required 57 | uint256 _amount, 58 | bytes32 _nHash, 59 | bytes memory _sig 60 | ) public { 61 | require(_amount > 0, "amount must be greater than 0"); 62 | registry.getShifterBySymbol(tokenSymbol).shiftIn(0x0, _amount, _nHash, _sig); 63 | } 64 | 65 | /// @notice Transfers tokens from the contract to reduce the amount of reward. 66 | /// 67 | /// @param _tokenAddress The address of the ERC20 token 68 | /// @param _amount The amount to transfer 69 | /// @param _transferTo The destination address to send ERC20 tokens to 70 | function transfer(address _tokenAddress, uint256 _amount, address _transferTo) external onlyOwner { 71 | ERC20(_tokenAddress).transfer(_transferTo, _amount); 72 | } 73 | 74 | /// @notice Validate that the secret is correct. Use this function to 75 | /// validate your answer if you think you've got it before 76 | /// submitting a swap. You could also use this to brute-force 77 | /// the answer too if you want. 78 | /// 79 | /// @param _secret The secret. 80 | function validateSecret(bytes memory _secret) public view returns (bool) { 81 | bytes memory h = abi.encodePacked(sha256(_secretMessage(_secret))); 82 | return Compare.bytesEqual(h, secretHash); 83 | } 84 | 85 | function _secretMessage(bytes memory _secret) internal pure returns (bytes memory) { 86 | return abi.encodePacked( 87 | "Secret(", string(_secret), 88 | ")" 89 | ); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /ethereum-contracts/migrations/2_dex.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | const zBTC = artifacts.require("zBTC"); 4 | const zZEC = artifacts.require("zZEC"); 5 | const zBCH = artifacts.require("zBCH"); 6 | const ShifterRegistry = artifacts.require("ShifterRegistry"); 7 | 8 | const BTC_DAI_Reserve = artifacts.require("BTC_DAI_Reserve"); 9 | const ZEC_DAI_Reserve = artifacts.require("ZEC_DAI_Reserve"); 10 | const BCH_DAI_Reserve = artifacts.require("BCH_DAI_Reserve"); 11 | const DEXAdapter = artifacts.require("DEXAdapter"); 12 | const DEX = artifacts.require("DEX"); 13 | const DaiToken = artifacts.require("DaiToken"); 14 | 15 | const networks = require("./networks.js"); 16 | 17 | module.exports = async function (deployer, network, accounts) { 18 | deployer.logger.log(`Deploying to ${network}...`); 19 | 20 | network = network.replace("-fork", ""); 21 | 22 | const addresses = networks[network] || {}; 23 | const config = networks[network] ? networks[network].config : networks.config; 24 | 25 | const renNetwork = addresses.renNetwork || networks.config.renNetwork; 26 | 27 | DEX.address = addresses.DEX || ""; 28 | DEXAdapter.address = addresses.DEXAdapter || ""; 29 | DaiToken.address = renNetwork.addresses.tokens.DAI.address || ""; 30 | 31 | if (!DaiToken.address) { 32 | await deployer.deploy(DaiToken) 33 | } 34 | 35 | if (!DEX.address) { 36 | await deployer.deploy( 37 | DEX, 38 | DaiToken.address, 39 | ); 40 | } 41 | const dex = await DEX.at(DEX.address); 42 | 43 | if (!DEXAdapter.address) { 44 | await deployer.deploy( 45 | DEXAdapter, 46 | DEX.address, 47 | ShifterRegistry.address, 48 | ); 49 | } 50 | 51 | deployer.logger.log("Deploying reserves..."); 52 | 53 | const deployReserve = async (quoteToken, baseToken, Reserve) => { 54 | await deployer.deploy(Reserve, baseToken.address, quoteToken.address, config.dexFees); 55 | const res = await Reserve.at(Reserve.address); 56 | await dex.registerReserve(quoteToken.address, Reserve.address); 57 | // const dai = await rightToken.at(rightToken.address); 58 | // await dai.transfer(Reserve.address, "100000000000000000000"); 59 | } 60 | 61 | BTC_DAI_Reserve.address = await dex.reserves(zBTC.address); 62 | if (BTC_DAI_Reserve.address === "0x0000000000000000000000000000000000000000") { 63 | await deployReserve(zBTC, DaiToken, BTC_DAI_Reserve); 64 | deployer.logger.log(`[${"BTC"}, ${"DAI"}]: ${BTC_DAI_Reserve.address}`); 65 | } else { 66 | deployer.logger.log(`\nUsing existing reserve for [${"BTC"}, ${"DAI"}]: ${BTC_DAI_Reserve.address}\n`); 67 | } 68 | 69 | ZEC_DAI_Reserve.address = await dex.reserves(zZEC.address); 70 | if (ZEC_DAI_Reserve.address === "0x0000000000000000000000000000000000000000") { 71 | await deployReserve(zZEC, DaiToken, ZEC_DAI_Reserve); 72 | deployer.logger.log(`[${"ZEC"}, ${"DAI"}]: ${ZEC_DAI_Reserve.address}`); 73 | } else { 74 | deployer.logger.log(`\nUsing existing reserve for [${"ZEC"}, ${"DAI"}]: ${ZEC_DAI_Reserve.address}\n`); 75 | } 76 | 77 | BCH_DAI_Reserve.address = await dex.reserves(zBCH.address); 78 | if (BCH_DAI_Reserve.address === "0x0000000000000000000000000000000000000000") { 79 | await deployReserve(zBCH, DaiToken, BCH_DAI_Reserve); 80 | deployer.logger.log(`[${"BCH"}, ${"DAI"}]: ${BCH_DAI_Reserve.address}`); 81 | } else { 82 | deployer.logger.log(`\nUsing existing reserve for [${"BCH"}, ${"DAI"}]: ${BCH_DAI_Reserve.address}\n`); 83 | } 84 | 85 | // await web3.eth.sendTransaction({ to: "", from: accounts[0], value: web3.utils.toWei("1", "ether") }); 86 | 87 | /** LOG *******************************************************************/ 88 | 89 | deployer.logger.log({ 90 | DEX: DEX.address, 91 | DEXAdapter: DEXAdapter.address, 92 | BTC_DAI_Reserve: BTC_DAI_Reserve.address, 93 | ZEC_DAI_Reserve: ZEC_DAI_Reserve.address, 94 | BCH_DAI_Reserve: BCH_DAI_Reserve.address, 95 | }); 96 | } -------------------------------------------------------------------------------- /react-client/src/components/views/order-popup/DepositReceived.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { Loading, TokenIcon } from "@renproject/react-components"; 4 | import { TxStatus } from "@renproject/ren"; 5 | 6 | import { _catchInteractionErr_, safeJSONStringify } from "../../../lib/errors"; 7 | import { Token } from "../../../state/generalTypes"; 8 | import { Popup } from "../Popup"; 9 | 10 | const renderTxStatus = (status: TxStatus | null) => { 11 | switch (status) { 12 | case null: 13 | return "Submitting"; 14 | case TxStatus.TxStatusNil: 15 | return "Submitting"; 16 | case TxStatus.TxStatusConfirming: 17 | return "Waiting for confirmations"; 18 | case TxStatus.TxStatusPending: 19 | return "Executing"; 20 | case TxStatus.TxStatusExecuting: 21 | return "Executing"; 22 | case TxStatus.TxStatusDone: 23 | return "Done"; 24 | case TxStatus.TxStatusReverted: 25 | return "Reverted"; 26 | } 27 | }; 28 | 29 | export const DepositReceived: React.StatelessComponent<{ 30 | token?: Token; 31 | messageID: string | null; 32 | renVMStatus: TxStatus | null; 33 | orderID: string; 34 | submitDeposit?: (orderID: string, resubmit?: boolean) => Promise; 35 | hide?: () => void; 36 | }> = ({ token, renVMStatus, messageID, orderID, submitDeposit, hide }) => { 37 | const [submitted, setSubmitted] = React.useState(false); 38 | const [error, setError] = React.useState(null as Error | null); 39 | 40 | const onClick = React.useCallback(async () => { 41 | setError(null); 42 | setSubmitted(true); 43 | if (submitDeposit) { 44 | try { 45 | await submitDeposit(orderID); 46 | } catch (error) { 47 | setSubmitted(false); 48 | setError(error); 49 | _catchInteractionErr_(error, "Error in DepositReceived: submitDeposit"); 50 | } 51 | } 52 | }, [orderID, submitDeposit]); 53 | 54 | // useEffect replaces `componentDidMount` and `componentDidUpdate`. 55 | // To limit it to running once, we use the initialized hook. 56 | const [initialized, setInitialized] = React.useState(false); 57 | React.useEffect(() => { 58 | if (!initialized) { 59 | setInitialized(true); 60 | if (messageID) { 61 | onClick().catch(console.error); 62 | } 63 | } 64 | }, [initialized, messageID, onClick]); 65 | 66 | // const onRetry = async () => { 67 | // setError(null); 68 | // setSubmitted(true); 69 | // if (submitDeposit) { 70 | // try { 71 | // await submitDeposit(orderID, true); 72 | // } catch (error) { 73 | // setSubmitted(false); 74 | // setError(error); 75 | // _catchInteractionErr_(error); 76 | // } 77 | // } 78 | // }; 79 | 80 | const waiting = (submitDeposit === undefined) || submitted; 81 | 82 | return 83 |
84 |
85 | {token ? : null} 86 |

{waiting ? <>Submitting to RenVM : <>Submit to RenVM}

87 | {waiting ? : null} 88 | {error ? Unable to submit to RenVM: {error.message || safeJSONStringify(error)} : null} 89 | {waiting ?
90 | <> 91 |

Submitting order to RenVM...
This can take a few minutes.

92 |

Status: {{renderTxStatus(renVMStatus)}. || }

93 | 94 |
:
95 | 96 |
97 | } 98 |
99 |
100 |
; 101 | }; 102 | -------------------------------------------------------------------------------- /react-client/src/state/generalTypes.ts: -------------------------------------------------------------------------------- 1 | import { Currency } from "@renproject/react-components"; 2 | import RenJS, { NetworkDetails } from "@renproject/ren"; 3 | import { isMainnetAddress, isTestnetAddress } from "bchaddrjs"; 4 | import { Map } from "immutable"; 5 | import { validate } from "wallet-address-validator"; 6 | import Web3 from "web3"; 7 | import { AbiItem } from "web3-utils"; 8 | 9 | import { syncGetDEXAdapterAddress, syncGetDEXAddress } from "../lib/contractAddresses"; 10 | import { DEX } from "../lib/contracts/DEX"; 11 | import { DEXAdapter } from "../lib/contracts/DEXAdapter"; 12 | import { DEXReserve } from "../lib/contracts/DEXReserve"; 13 | import { ERC20Detailed } from "../lib/contracts/ERC20Detailed"; 14 | 15 | export enum Token { 16 | DAI = "DAI", 17 | BTC = "BTC", 18 | ETH = "ETH", 19 | ZEC = "ZEC", 20 | BCH = "BCH", 21 | } 22 | 23 | export const renderToken = (token: Token): string => token === Token.DAI ? "SAI" : token; 24 | 25 | const btcValidator = (address: string, isTestnet: boolean) => validate(address, "btc", isTestnet ? "testnet" : "prod"); 26 | const zecValidator = (address: string, isTestnet: boolean) => validate(address, "zec", isTestnet ? "testnet" : "prod"); 27 | const bchValidator = (address: string, isTestnet: boolean) => { 28 | try { 29 | return isTestnet ? isTestnetAddress(address) : isMainnetAddress(address); 30 | } catch (error) { 31 | return false; 32 | } 33 | }; 34 | const ethValidator = (address: string, isTestnet: boolean) => validate(address, "eth", isTestnet ? "testnet" : "prod"); 35 | 36 | export const Tokens = Map() 37 | .set(Token.DAI, { symbol: Token.DAI, name: "Sai", decimals: 18, priority: 100, chain: RenJS.Chains.Ethereum, validator: ethValidator }) 38 | .set(Token.BTC, { symbol: Token.BTC, name: "Bitcoin", decimals: 8, priority: 200, chain: RenJS.Chains.Bitcoin, validator: btcValidator }) 39 | // .set(Token.ETH, { symbol: Token.ETH, name: "Ethereum", decimals: 18, priority: 1024, chain: RenJS.Chains.Ethereum, validator: ethValidator }) 40 | .set(Token.ZEC, { symbol: Token.ZEC, name: "Zcash", decimals: 8, priority: 201, chain: RenJS.Chains.Zcash, validator: zecValidator }) 41 | .set(Token.BCH, { symbol: Token.BCH, name: "Bitcoin Cash", decimals: 8, priority: 202, chain: RenJS.Chains.BitcoinCash, validator: bchValidator }) 42 | ; 43 | 44 | export const isEthereumBased = (token: Token) => { 45 | const details = Tokens.get(token); 46 | if (!details) { 47 | return false; 48 | } 49 | return details.chain === RenJS.Chains.Ethereum; 50 | }; 51 | 52 | export const isERC20 = (token: Token) => isEthereumBased(token) && token !== Token.ETH; 53 | 54 | export interface TokenDetails { 55 | name: string; 56 | symbol: Token; 57 | decimals: number; 58 | priority: number; 59 | chain: RenJS["Chains"]["Ethereum"] | RenJS["Chains"]["Bitcoin"] | RenJS["Chains"]["Zcash"] | RenJS["Chains"]["BitcoinCash"]; 60 | validator: (address: string, isTestnet: boolean) => boolean; 61 | } 62 | 63 | export type TokenPrices = Map>; 64 | 65 | // tslint:disable: non-literal-require 66 | const network = process.env.REACT_APP_NETWORK || "testnet"; 67 | const DEXABI = require(`../contracts/${network}/DEX.json`).abi; 68 | const DEXAdapterABI = require(`../contracts/${network}/DEXAdapter.json`).abi; 69 | const DEXReserveABI = require(`../contracts/${network}/BTC_DAI_Reserve.json`).abi; 70 | 71 | export const NULL_BYTES32 = "0x0000000000000000000000000000000000000000000000000000000000000000"; 72 | export const NULL_BYTES = "0x0000000000000000000000000000000000000000"; 73 | 74 | /// Initialize Web3 and contracts 75 | export const getExchange = (web3: Web3, networkID: number): DEX => 76 | new web3.eth.Contract(DEXABI as AbiItem[], syncGetDEXAddress(networkID)); 77 | export const getERC20 = (web3: Web3, networkDetails: NetworkDetails, tokenAddress: string): ERC20Detailed => 78 | new (web3.eth.Contract)(networkDetails.contracts.addresses.erc.ERC20.abi, tokenAddress); 79 | export const getAdapter = (web3: Web3, networkID: number): DEXAdapter => 80 | new (web3.eth.Contract)(DEXAdapterABI as AbiItem[], syncGetDEXAdapterAddress(networkID)); 81 | export const getReserve = (web3: Web3, _networkID: number, tokenAddress: string): DEXReserve => 82 | new (web3.eth.Contract)(DEXReserveABI as AbiItem[], tokenAddress); // syncGetDEXReserveAddress(networkID, token)); 83 | -------------------------------------------------------------------------------- /react-client/src/components/controllers/Exchange.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { Loading } from "@renproject/react-components"; 4 | 5 | import { className } from "../../lib/className"; 6 | import { _catchInteractionErr_ } from "../../lib/errors"; 7 | import { connect, ConnectedProps } from "../../state/connect"; 8 | import { Token } from "../../state/generalTypes"; 9 | import { ExchangeTabs, UIContainer } from "../../state/uiContainer"; 10 | import { ReactComponent as Warning } from "../../styles/images/warning.svg"; 11 | import { ErrorBoundary } from "../ErrorBoundary"; 12 | import { LiquidityForm } from "../views/exchange-forms/LiquidityForm"; 13 | import { OrderForm } from "../views/exchange-forms/OrderForm"; 14 | import { OrderHistory } from "../views/OrderHistory"; 15 | import { OpeningOrder } from "./OpeningOrder"; 16 | import { PromptDetails } from "./PromptDetails"; 17 | 18 | interface Props { 19 | handleLogin: () => void; 20 | } 21 | 22 | /** 23 | * Exchange is the main token-swapping page. 24 | */ 25 | export const Exchange = connect>([UIContainer])( 26 | ({ handleLogin, containers: [uiContainer] }) => { 27 | 28 | const { currentOrderID, submitting } = uiContainer.state; 29 | 30 | const onSwapTab = React.useCallback(async () => { 31 | (async () => { 32 | await uiContainer.resetReceiveValue(); 33 | await uiContainer.setExchangeTab(ExchangeTabs.Swap); 34 | })().catch(error => _catchInteractionErr_(error, "Error in Exchange: onSwapTab")); 35 | }, [uiContainer]); 36 | const onLiquidityTab = React.useCallback(() => { 37 | const { orderInputs: { srcToken, dstToken } } = uiContainer.state; 38 | 39 | // If src token is DAI, set it to the dst token - unless it's also DAI 40 | const newSrcToken = srcToken === Token.DAI ? (dstToken === Token.DAI ? Token.BTC : dstToken) : srcToken; 41 | uiContainer.resetReceiveValue().then(() => { 42 | uiContainer.updateBothTokens(newSrcToken, Token.DAI).catch(error => _catchInteractionErr_(error, "Error in Exchange: updateBothTokens")); 43 | uiContainer.setExchangeTab(ExchangeTabs.Liquidity).catch(error => _catchInteractionErr_(error, "Error in Exchange: setExchangeTab")); 44 | }).catch(error => _catchInteractionErr_(error, "Error in Exchange: resetReceiveValue")); 45 | }, [uiContainer]); 46 | 47 | const cancel = React.useCallback(async () => { 48 | await uiContainer.setSubmitting(false); 49 | }, [uiContainer]); 50 | 51 | const { exchangeTab } = uiContainer.state; 52 | 53 | return
54 |
55 |
56 | }> 57 |
58 | ️ Chaosnet is unaudited, please proceed with caution. 59 |
60 |
61 | 62 | 63 |
64 | {exchangeTab === ExchangeTabs.Swap ? 65 | : 66 | 67 | } 68 | 69 | {submitting ? 70 | : 71 | <> 72 | } 73 | {currentOrderID ? 74 | : 75 | <> 76 | } 77 |
78 |
79 |
80 |
; 81 | } 82 | ); 83 | -------------------------------------------------------------------------------- /ethereum-contracts/contracts/DEX.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.12; 2 | 3 | import "darknode-sol/contracts/libraries/Claimable.sol"; 4 | import "./DEXReserve.sol"; 5 | import "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol"; 6 | import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 7 | 8 | /// @title DEX 9 | /// @notice The DEX contract stores the reserves for each token pair and 10 | /// provides functions for interacting with them: 11 | /// 1) the view-function `calculateReceiveAmount` for calculating how much 12 | /// the user will receive in exchange for their tokens 13 | /// 2) the function `trade` for executing a swap. If one of the tokens is the 14 | /// base token, this will only talk to one reserve. If neither of the 15 | /// tokens are, then the trade will settle across two reserves. 16 | /// 17 | /// The DEX is ownable, allowing a DEX operator to register new reserves. 18 | /// Once a reserve has been registered, it can't be updated. 19 | contract DEX is Claimable { 20 | mapping (address=>DEXReserve) public reserves; 21 | address public baseToken; 22 | 23 | event LogTrade(address _src, address _dst, uint256 _sendAmount, uint256 _recvAmount); 24 | 25 | /// @param _baseToken The reserves must all have a common base token. 26 | constructor(address _baseToken) public { 27 | baseToken = _baseToken; 28 | } 29 | 30 | /// @notice Allow the owner to recover funds accidentally sent to the 31 | /// contract. To withdraw ETH, the token should be set to `0x0`. 32 | function recoverTokens(address _token) external onlyOwner { 33 | if (_token == address(0x0)) { 34 | msg.sender.transfer(address(this).balance); 35 | } else { 36 | ERC20(_token).transfer(msg.sender, ERC20(_token).balanceOf(address(this))); 37 | } 38 | } 39 | 40 | /// @notice The DEX operator is able to register new reserves. 41 | /// @param _erc20 The token that can be traded against the base token. 42 | /// @param _reserve The address of the reserve contract. It must follow the 43 | /// DEXReserve interface. 44 | function registerReserve(address _erc20, DEXReserve _reserve) external onlyOwner { 45 | require(reserves[_erc20] == DEXReserve(0x0), "token reserve already registered"); 46 | reserves[_erc20] = _reserve; 47 | } 48 | 49 | /// @notice The main trade function to execute swaps. 50 | /// @param _to The address at which the DST tokens should be sent to. 51 | /// @param _src The address of the token being spent. 52 | /// @param _dst The address of the token being received. 53 | /// @param _sendAmount The amount of the source token being traded. 54 | function trade(address _to, address _src, address _dst, uint256 _sendAmount) public returns (uint256) { 55 | uint256 recvAmount; 56 | if (_src == baseToken) { 57 | require(reserves[_dst] != DEXReserve(0x0), "unsupported token"); 58 | recvAmount = reserves[_dst].buy(_to, msg.sender, _sendAmount); 59 | } else if (_dst == baseToken) { 60 | require(reserves[_src] != DEXReserve(0x0), "unsupported token"); 61 | recvAmount = reserves[_src].sell(_to, msg.sender, _sendAmount); 62 | } else { 63 | require(reserves[_src] != DEXReserve(0x0) && reserves[_dst] != DEXReserve(0x0), "unsupported token"); 64 | uint256 intermediteAmount = reserves[_src].sell(address(this), msg.sender, _sendAmount); 65 | ERC20(baseToken).approve(address(reserves[_dst]), intermediteAmount); 66 | recvAmount = reserves[_dst].buy(_to, address(this), intermediteAmount); 67 | } 68 | emit LogTrade(_src, _dst, _sendAmount, recvAmount); 69 | return recvAmount; 70 | } 71 | 72 | /// @notice A read-only function to estimate the amount of DST tokens the 73 | /// trader would receive for the send amount. 74 | /// @param _src The address of the token being spent. 75 | /// @param _dst The address of the token being received. 76 | /// @param _sendAmount The amount of the source token being traded. 77 | function calculateReceiveAmount(address _src, address _dst, uint256 _sendAmount) public view returns (uint256) { 78 | if (_src == baseToken) { 79 | return reserves[_dst].calculateBuyRcvAmt(_sendAmount); 80 | } 81 | if (_dst == baseToken) { 82 | return reserves[_src].calculateSellRcvAmt(_sendAmount); 83 | } 84 | return reserves[_dst].calculateBuyRcvAmt(reserves[_src].calculateSellRcvAmt(_sendAmount)); 85 | } 86 | } -------------------------------------------------------------------------------- /react-client/src/styles/scss/_history.scss: -------------------------------------------------------------------------------- 1 | .history { 2 | margin: 0 -20px; 3 | margin-top: 25px; 4 | } 5 | 6 | .history .history--banner { 7 | border-bottom: 1px solid transparentize(#E0E3EB, 1 - 0.1); 8 | padding-left: 20px; 9 | margin-bottom: 20px; 10 | text-align: center; 11 | padding-bottom: 5px; 12 | 13 | 14 | 15 | span { 16 | color: $blue-bright; 17 | font-size: 12; 18 | font-weight: 500; 19 | font-weight: 500; 20 | font-size: 12px; 21 | line-height: 14px; 22 | color: #9195A0; 23 | } 24 | } 25 | 26 | .history .swap--history--entry { 27 | display: flex; 28 | justify-content: space-between; 29 | align-items: center; 30 | padding: 10px; 31 | border-radius: 22px; 32 | border: 1px solid $background; 33 | background: #282C35; 34 | box-shadow: 0px 1px 4px rgba(0, 26, 58, 0.05); 35 | border-radius: 22px; 36 | 37 | font-size: 12; 38 | font-weight: 500; 39 | margin-bottom: 10px; 40 | text-decoration: none; 41 | 42 | .token--info { 43 | display: flex; 44 | align-items: center; 45 | } 46 | 47 | .received--text { 48 | color: $grey-10; 49 | margin-left: 10px; 50 | margin-right: 7px; 51 | } 52 | 53 | .token--amount { 54 | color: $grey-12; 55 | } 56 | 57 | .swap--time { 58 | margin-right: 7px; 59 | color: $grey-11; 60 | } 61 | } 62 | 63 | .history--txs { 64 | display: flex; 65 | justify-content: center; 66 | align-items: center; 67 | } 68 | 69 | .history--txs>a { 70 | border: 0.5px solid $background; 71 | border-radius: 100px; 72 | width: 25px; 73 | height: 25px; 74 | margin-top: -5px; 75 | margin-bottom: -5px; 76 | 77 | text-decoration: none; 78 | display: flex; 79 | justify-content: center; 80 | align-items: center; 81 | 82 | svg { 83 | height: 7px; 84 | } 85 | 86 | .loading:after { 87 | border: 1px solid; 88 | border-color: $grey-09 transparent $grey-09 transparent; 89 | } 90 | 91 | &.tx-in { 92 | transform: rotate(-45deg); 93 | 94 | svg path { 95 | fill: $grey-09; 96 | } 97 | 98 | border-color: $grey-09; 99 | 100 | margin-right: 7px; 101 | } 102 | 103 | &.tx-out { 104 | transform: rotate(45deg); 105 | 106 | svg path { 107 | fill: $blue-bright; 108 | } 109 | 110 | border-color: $blue-bright; 111 | } 112 | } 113 | 114 | .history--pages { 115 | display: flex; 116 | justify-content: space-between; 117 | align-items: center; 118 | } 119 | 120 | .history--pages button { 121 | background: $background; 122 | border: 1px solid $foreground; 123 | border-radius: 100px; 124 | min-width: unset; 125 | width: 35px; 126 | 127 | &:disabled { 128 | opacity: 0.2; 129 | } 130 | 131 | svg { 132 | width: 10px; 133 | fill: $foreground; 134 | } 135 | } 136 | 137 | .history--page-count { 138 | text-transform: uppercase; 139 | color: $grey-09; 140 | font-size: 10px; 141 | } 142 | 143 | .tx-pending { 144 | border: none; 145 | } 146 | 147 | .tx-pending--solid { 148 | border-radius: 100px; 149 | width: 25px; 150 | height: 25px; 151 | margin-top: -5px; 152 | margin-bottom: -5px; 153 | 154 | text-decoration: none; 155 | display: flex; 156 | justify-content: center; 157 | align-items: center; 158 | margin-right: 5px; 159 | } 160 | 161 | .tx-pending:after { 162 | content: ' '; 163 | // display: block; 164 | position: absolute; 165 | width: 18px; 166 | height: 18px; 167 | margin: 0 auto; 168 | border-radius: 50%; 169 | border: 1px solid; 170 | animation: tx-pending 1.2s linear infinite; 171 | // border-color: $grey-09 transparent $grey-09 transparent; 172 | border-color: $blue-bright transparent $blue-bright transparent; 173 | } 174 | 175 | @keyframes tx-pending { 176 | 0% { 177 | transform: rotate(0deg); 178 | } 179 | 180 | 100% { 181 | transform: rotate(360deg); 182 | } 183 | } 184 | 185 | .swap--progress { 186 | @extend .token--icon; 187 | height: 20px; 188 | width: 20px !important; 189 | } 190 | 191 | .button--plain { 192 | background: none; 193 | border: none; 194 | color: $blue-bright; 195 | padding: 0; 196 | margin: 0; 197 | height: auto; 198 | } 199 | 200 | .continue--shift { 201 | margin-left: 5px; 202 | } -------------------------------------------------------------------------------- /react-client/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:recommended", 4 | "tslint-react", 5 | "tslint-microsoft-contrib/latest" 6 | ], 7 | "rulesDirectory": [ 8 | "node_modules/tslint-microsoft-contrib" 9 | ], 10 | "linterOptions": { 11 | "exclude": [ 12 | "config/**/*.js", 13 | "node_modules/**/*.ts", 14 | "src/lib/external/*.js" 15 | ] 16 | }, 17 | "rules": { 18 | // Disabled 19 | "interface-name": false, 20 | "jsx-boolean-value": false, 21 | "max-classes-per-file": false, 22 | "no-empty-interface": false, 23 | "no-var-requires": false, 24 | "object-literal-sort-keys": false, 25 | "no-implicit-dependencies": false, 26 | "space-before-function-paren": false, 27 | "arrow-parens": false, 28 | "jsx-wrap-multiline": false, 29 | "one-variable-per-declaration": false, 30 | "jsx-no-multiline-js": false, 31 | "import-name": false, 32 | "no-relative-imports": false, 33 | "no-null-keyword": false, 34 | "possible-timing-attack": false, 35 | "no-submodule-imports": false, 36 | "no-backbone-get-set-outside-model": false, 37 | "prefer-type-cast": false, 38 | "use-simple-attributes": false, 39 | "newline-before-return": false, 40 | "export-name": false, 41 | "binary-expression-operand-order": false, 42 | "strict-boolean-expressions": false, 43 | "newline-per-chained-call": false, 44 | "prefer-array-literal": false, 45 | "match-default-export-name": false, 46 | "function-name": false, 47 | "no-void-expression": false, 48 | // "no-return-await": false, 49 | // "ter-indent": false, 50 | // Warnings 51 | "no-suspicious-comment": { 52 | "severity": "warning" 53 | }, 54 | "no-console": { 55 | "severity": "warning" 56 | }, 57 | "strict-type-predicates": { 58 | "severity": "warning" 59 | }, 60 | // Enabled 61 | "ordered-imports": true, 62 | "no-object-literal-type-assertion": true, 63 | "semicolon": true, 64 | "eofline": true, 65 | "no-non-null-assertion": true, 66 | "no-floating-promises": true, 67 | "typedef": true, 68 | "no-any": true, 69 | "no-angle-bracket-type-assertion": true, 70 | "no-use-before-declare": true, 71 | "promise-function-async": true, 72 | "prefer-readonly": true, 73 | "await-promise": true, 74 | "react-this-binding-issue": true, 75 | "no-string-based-set-timeout": true, 76 | "no-unnecessary-type-assertion": true, 77 | "no-unnecessary-local-variable": false, 78 | "only-arrow-functions": true, 79 | // Configured 80 | "no-unused-variable": [ 81 | true, 82 | { 83 | "ignore-pattern": "^_" 84 | } 85 | ], 86 | "quotemark": [ 87 | true, 88 | "double", 89 | "jsx-double" 90 | ], 91 | "indent": [ 92 | true, 93 | "spaces", 94 | 4 95 | ], 96 | "variable-name": [ 97 | true, 98 | "ban-keywords", 99 | "check-format", 100 | "allow-leading-underscore", 101 | "allow-trailing-underscore", 102 | "allow-pascal-case" 103 | ], 104 | "no-import-side-effect": [ 105 | true, 106 | { 107 | "ignore-module": "(\\.html|\\.css|\\.scss)$" 108 | } 109 | ], 110 | "trailing-comma": [ 111 | true, 112 | { 113 | "multiline": true, 114 | "esSpecCompliant": true 115 | } 116 | ], 117 | "increment-decrement": [ 118 | true, 119 | "allow-post" 120 | ], 121 | "align": [ 122 | true, 123 | "parameters", 124 | "statements" 125 | ], 126 | "array-type": [ 127 | true, 128 | "array-simple" 129 | ], 130 | // Temporarily disabled - some work is required to get the project to 131 | // abide by these 132 | "no-unsafe-any": false, 133 | "max-line-length": false, 134 | "max-func-body-length": false, 135 | "react-a11y-input-elements": false, 136 | "react-a11y-role-has-required-aria-props": false, 137 | "completed-docs": false 138 | } 139 | } -------------------------------------------------------------------------------- /react-client/src/components/views/order-popup/SubmitToEthereum.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { InfoLabel, Loading } from "@renproject/react-components"; 4 | 5 | import { _catchInteractionErr_, safeJSONStringify } from "../../../lib/errors"; 6 | import { renderToken, Token } from "../../../state/generalTypes"; 7 | import { 8 | CommitmentType, ShiftInEvent, ShiftOutEvent, Tx, 9 | } from "../../../state/persistentContainer"; 10 | import { network } from "../../../state/sdkContainer"; 11 | import { Popup } from "../Popup"; 12 | 13 | export const SubmitToEthereum: React.StatelessComponent<{ 14 | token: Token, 15 | orderID: string, 16 | txHash: Tx | null, 17 | order: ShiftInEvent | ShiftOutEvent, 18 | submit: (orderID: string, retry?: boolean) => Promise, 19 | hide?: () => void, 20 | }> = ({ token, orderID, txHash, order, submit, hide }) => { 21 | const [submitting, setSubmitting] = React.useState(false); 22 | const [error, setError] = React.useState(null as Error | null); 23 | const [failedTransaction, setFailedTransaction] = React.useState(null as string | null); 24 | 25 | const onSubmit = React.useCallback(async () => { 26 | setError(null); 27 | setFailedTransaction(null); 28 | setSubmitting(true); 29 | try { 30 | await submit(orderID, error !== null); 31 | } catch (error) { 32 | setSubmitting(false); 33 | let shownError = error; 34 | 35 | // Ignore user denying error in MetaMask. 36 | if (String(shownError.message || shownError).match(/User denied transaction signature/)) { 37 | return; 38 | } 39 | 40 | _catchInteractionErr_(shownError, "Error in SubmitToEthereum: submit"); 41 | const match = String(shownError.message || shownError).match(/"transactionHash": "(0x[a-fA-F0-9]{64})"/); 42 | if (match && match.length >= 2) { 43 | setFailedTransaction(match[1]); 44 | shownError = new Error("Transaction reverted."); 45 | } 46 | setError(shownError); 47 | } 48 | }, [orderID, submit, error]); 49 | 50 | // useEffect replaces `componentDidMount` and `componentDidUpdate`. 51 | // To limit it to running once, we use the initialized hook. 52 | const [initialized, setInitialized] = React.useState(false); 53 | React.useEffect(() => { 54 | if (!initialized) { 55 | setInitialized(true); 56 | if (txHash) { 57 | onSubmit().catch(console.error); 58 | } 59 | } 60 | }, [initialized, txHash, onSubmit]); 61 | 62 | return 63 |
64 |
65 |

Submit {order && order.commitment && order.commitment.type === CommitmentType.Trade ? "swap " : ""}to Ethereum

66 |
67 | Submit {order && order.commitment && order.commitment.type === CommitmentType.Trade ? <>swap to Ethereum to receive {renderToken(token).toUpperCase()} : <>to Ethereum}.{txHash ? Tx Hash: {txHash.hash} : <>} 68 |
69 |
70 |
71 | {error ? 72 | Error submitting to Ethereum: {error.message || safeJSONStringify(error)} 73 | {failedTransaction ? <> 74 |
75 | See the Transaction Status for more details. 76 |
77 | Some possible issues: 78 |
    79 |
  • nonce hash already spent: your trade has already gone through in another transaction
  • 80 |
  • amount is less than the minimum shiftOut amount: the amount being shifted out is less than the tx fees
  • 81 |
  • SafeERC20: low-level call failed: you have insufficient balance
  • 82 |
83 | : null} 84 |
: null} 85 |
86 | 87 |
88 |
89 |
90 |
; 91 | }; 92 | -------------------------------------------------------------------------------- /react-client/src/components/controllers/PromptDetails.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { InfoLabel } from "@renproject/react-components"; 4 | 5 | import { IS_TESTNET } from "../../lib/environmentVariables"; 6 | import { _catchInteractionErr_ } from "../../lib/errors"; 7 | import { connect, ConnectedProps } from "../../state/connect"; 8 | import { renderToken, Token } from "../../state/generalTypes"; 9 | import { CommitmentType } from "../../state/persistentContainer"; 10 | import { UIContainer } from "../../state/uiContainer"; 11 | import { AskForAddress } from "../views/order-popup/AskForAddress"; 12 | import { ConfirmTradeDetails } from "../views/order-popup/ConfirmTradeDetails"; 13 | import { BTC_FAUCET_LINK, TAZ_FAUCET_LINK } from "../views/tutorial-popup/TutorialPages"; 14 | 15 | interface Props { 16 | cancel: () => void; 17 | } 18 | 19 | /** 20 | * PromptDetails is a visual component for allowing users to open new orders 21 | */ 22 | export const PromptDetails = connect>([UIContainer])( 23 | ({ containers: [uiContainer], cancel }) => { 24 | 25 | const { 26 | toAddress, confirmedOrderInputs, confirmedTrade, 27 | address, commitmentType, preferredCurrency, tokenPrices 28 | } = uiContainer.state; 29 | 30 | const onRefundAddress = React.useCallback(async (refundAddress: string) => { 31 | await uiContainer.updateRefundAddress(refundAddress).catch(error => _catchInteractionErr_(error, "Error in PromptDetails: updateRefundAddress")); 32 | await uiContainer.commitOrder(); 33 | }, [uiContainer]); 34 | 35 | const onCancel = () => { 36 | uiContainer.resetTrade().catch(error => _catchInteractionErr_(error, "Error in PromptDetails: resetTrade")); 37 | cancel(); 38 | }; 39 | 40 | // The confirmed order inputs should always be available 41 | if (!confirmedOrderInputs) { 42 | return <>; 43 | } 44 | 45 | // Ask the user to confirm the details before continuing 46 | if (!confirmedTrade) { 47 | return ; 55 | } 56 | 57 | // Ask the user to provide an address for receiving `dstToken` 58 | if (toAddress === null) { 59 | return 63 | Enter the {renderToken(confirmedOrderInputs.dstToken)} public address you want to receive your tokens to. 64 | {confirmedOrderInputs.dstToken === Token.BTC && IS_TESTNET ? Hint: If you don't have a Testnet BTC wallet, use the faucet's return address. : <>} 65 | : <>Enter your Ethereum address to receive Liquidity tokens.} 66 | onAddress={uiContainer.updateToAddress} 67 | cancel={onCancel} 68 | defaultAddress={address || ""} 69 | />; 70 | } 71 | 72 | // Ask the user to provide an address for refunding `srcToken` to in 73 | // case the trade doesn't go through 74 | return 78 | Enter your {renderToken(confirmedOrderInputs.srcToken)} refund address in case the {commitmentType === CommitmentType.Trade ? "trade" : "transaction"} doesn't go through. 79 | {confirmedOrderInputs.srcToken === Token.BTC && IS_TESTNET ? Hint: If you don't have a Testnet BTC wallet, use the faucet's return address. : <>} 80 | {confirmedOrderInputs.srcToken === Token.ZEC && IS_TESTNET ? Hint: If you don't have a Testnet ZEC wallet, use the faucet's return address. : <>} 81 | } 82 | onAddress={onRefundAddress} 83 | cancel={onCancel} 84 | defaultAddress={address || ""} 85 | />; 86 | } 87 | ); 88 | -------------------------------------------------------------------------------- /react-client/src/components/views/tutorial-popup/TutorialPages.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { IS_TESTNET } from "../../../lib/environmentVariables"; 4 | import { renderToken, Token } from "../../../state/generalTypes"; 5 | import { ReactComponent as Logo } from "../../../styles/images/logo.svg"; 6 | import { ReactComponent as MetaMask } from "../../../styles/images/metamask.svg"; 7 | 8 | export const BUILDWITHRENVM_LINK = "https://renproject.io/developers"; 9 | export const READTHEDOCS_LINK = "https://docs.renproject.io/ren/"; // https://docs.renproject.io/developers 10 | export const FAQ_LINK = "https://docs.renproject.io/chaosnet/chaosdex"; 11 | export const KOVAN_FAUCET_LINK = "https://github.com/kovan-testnet/faucet"; 12 | export const BTC_FAUCET_LINK = "https://bitcoinfaucet.uo1.net/"; 13 | export const TAZ_FAUCET_LINK = "https://faucet.zcash.garethtdavies.com/"; 14 | export const METAMASK_LINK = "https://metamask.io/"; 15 | export const INTEROP_LINK = "https://docs.renproject.io/ren/renvm/universal-interop"; 16 | 17 | type TutorialPage = React.StatelessComponent<{ 18 | nextPage: () => void; 19 | previousPage: () => void; 20 | }>; 21 | 22 | // Custom `div`s to make it easier to read the page content. 23 | const Page = (props: React.DetailedHTMLProps, HTMLDivElement>) =>
{props.children}
; 24 | const Body = (props: React.DetailedHTMLProps, HTMLDivElement>) =>
{props.children}
; 25 | const Buttons = (props: React.DetailedHTMLProps, HTMLDivElement>) =>
{props.children}
; 26 | 27 | const Welcome: TutorialPage = ({ nextPage }) => { 28 | return 29 | 30 | 31 |

Welcome to the RenVM {IS_TESTNET ? "Demo" : "ChaosDEX"}

32 |

Ren is an open protocol that enables the permissionless and private transfer of value between any blockchain

33 | 34 | 35 | {/* Counter-weight for the Next button */} 36 | 37 | 38 |
; 39 | }; 40 | 41 | const Overview: TutorialPage = ({ nextPage, previousPage }) => { 42 | return 43 | 44 |

Overview

45 |

The RenVM {IS_TESTNET ? "Demo" : "ChaosDEX"} is a DEX built by the Ren team to showcase interoperability facilitated by RenVM.

46 |

Users can exchange {renderToken(Token.DAI)}, {renderToken(Token.BTC)}, {renderToken(Token.ZEC)} & {renderToken(Token.BCH)} in a completely trustless, decentralized, and permissionless manner.

47 | 48 | 49 | 50 | 51 | 52 |
; 53 | }; 54 | 55 | const GettingStarted: TutorialPage = ({ nextPage, previousPage }) => { 56 | return 57 | 58 |

Let's get started

59 |

Before you can use the demo, you'll need to:

60 |
    61 |
  1. Install MetaMask. MetaMask is a browser extension that allows you to interact with Ethereum apps.
  2. 62 |
  3. Get some Testnet BTC from a Testnet BTC Faucet. If you don't have a Testnet Bitcoin wallet, skip this step until later.
  4. 63 |
64 | {/*

make sure you have a Bitcoin wallet that supports the Bitcoin Testnet and some Testnet Bitcoin. You’ll also need a MetaMask wallet and some Testnet Ether.

65 |

For more details on where to acquire these, head over to the FAQ.

66 |

If you want to find out more about how the demo works, jump into the technical docs.

*/} 67 | 68 | 69 | 70 | 71 | 72 |
; 73 | }; 74 | 75 | export const tutorialPages: Array<{ name: string, node: TutorialPage }> = IS_TESTNET ? 76 | [ 77 | { name: "Welcome", node: Welcome, }, 78 | { name: "Overview", node: Overview, }, 79 | { name: "Getting started", node: GettingStarted } 80 | ] : [ 81 | { name: "Welcome", node: Welcome, }, 82 | { name: "Overview", node: Overview, }, 83 | ]; 84 | -------------------------------------------------------------------------------- /react-client/src/state/persistentContainer.tsx: -------------------------------------------------------------------------------- 1 | import RenJS, { TxStatus } from "@renproject/ren"; 2 | import localForage from "localforage"; 3 | import { PersistContainer } from "unstated-persist"; 4 | 5 | // import { Chain } from "@renproject/ren"; 6 | // import { Token } from "./generalTypes"; 7 | import { OrderInputs } from "./uiContainer"; 8 | 9 | export interface OrderCommitment { 10 | type: CommitmentType.Trade; 11 | srcToken: string; 12 | dstToken: string; 13 | minDestinationAmount: number; 14 | srcAmount: number; 15 | toAddress: string; 16 | refundBlockNumber: number; 17 | refundAddress: string; 18 | } 19 | 20 | export interface AddLiquidityCommitment { 21 | type: CommitmentType.AddLiquidity; 22 | liquidityProvider: string; 23 | maxDAIAmount: string; 24 | token: string; 25 | amount: number; 26 | refundBlockNumber: number; 27 | refundAddress: string; 28 | } 29 | 30 | export interface RemoveLiquidityCommitment { 31 | type: CommitmentType.RemoveLiquidity; 32 | token: string; 33 | liquidity: number; 34 | nativeAddress: string; 35 | } 36 | 37 | export type Commitment = OrderCommitment | AddLiquidityCommitment | RemoveLiquidityCommitment; 38 | 39 | export enum CommitmentType { 40 | Trade, 41 | AddLiquidity, 42 | RemoveLiquidity 43 | } 44 | 45 | export interface Tx { 46 | hash: string; 47 | chain: RenJS["Chains"]["Ethereum"] | RenJS["Chains"]["Bitcoin"] | RenJS["Chains"]["Zcash"] | RenJS["Chains"]["BitcoinCash"]; 48 | } 49 | 50 | export enum ShiftInStatus { 51 | Committed = "shiftIn_committed", 52 | Deposited = "shiftIn_deposited", 53 | SubmittedToRenVM = "shiftIn_submittedToRenVM", 54 | ReturnedFromRenVM = "shiftIn_returnedFromRenVM", 55 | SubmittedToEthereum = "shiftIn_submittedToEthereum", 56 | ConfirmedOnEthereum = "shiftIn_confirmedOnEthereum", 57 | RefundedOnEthereum = "shiftIn_refundedOnEthereum", 58 | } 59 | 60 | export enum ShiftOutStatus { 61 | Committed = "shiftOut_committed", 62 | SubmittedToEthereum = "shiftOut_submittedToEthereum", 63 | ConfirmedOnEthereum = "shiftOut_confirmedOnEthereum", 64 | SubmittedToRenVM = "shiftOut_submittedToRenVM", 65 | ReturnedFromRenVM = "shiftOut_returnedFromRenVM", 66 | RefundedOnEthereum = "shiftOut_refundedOnEthereum", 67 | } 68 | 69 | export interface HistoryEventCommon { 70 | id: string; 71 | time: number; // Seconds since Unix epoch 72 | inTx: Tx | null; 73 | outTx: Tx | null; 74 | receivedAmount: string | null; 75 | orderInputs: OrderInputs; 76 | commitment: Commitment; 77 | messageID: string | null; 78 | nonce: string; 79 | renVMStatus: TxStatus | null; 80 | } 81 | 82 | export interface ShiftInEvent extends HistoryEventCommon { 83 | shiftIn: true; 84 | status: ShiftInStatus; 85 | commitment: Commitment; 86 | } 87 | 88 | export interface ShiftOutEvent extends HistoryEventCommon { 89 | shiftIn: false; 90 | status: ShiftOutStatus; 91 | } 92 | 93 | export type HistoryEvent = ShiftInEvent | ShiftOutEvent; 94 | 95 | const initialState = { 96 | // tslint:disable-next-line: no-object-literal-type-assertion 97 | historyItems: { 98 | // [1]: { 99 | // time: 0, // Seconds since Unix epoch 100 | // outTx: { 101 | // hash: "1234", 102 | // chain: Chain.Ethereum, 103 | // }, 104 | // receivedAmount: "1", 105 | // orderInputs: { 106 | // srcToken: Token.BTC, 107 | // dstToken: Token.ETH, 108 | // srcAmount: "1", 109 | // dstAmount: "1", 110 | // }, 111 | // } 112 | } as { 113 | [key: string]: HistoryEvent, 114 | }, 115 | // _persist_version: undefined as undefined | number, 116 | }; 117 | 118 | // @ts-ignore 119 | export class PersistentContainer extends PersistContainer { 120 | public state = initialState; 121 | 122 | public persist = { 123 | key: "ren-order-history-v2", 124 | version: 1, 125 | storage: localForage, 126 | }; 127 | 128 | public updateHistoryItem = async (key: string, item: Partial) => { 129 | await this.setState({ 130 | historyItems: { ...this.state.historyItems, [key]: { ...this.state.historyItems[key], ...item } }, 131 | // tslint:disable-next-line: no-any 132 | _persist_version: (this.state as any)._persist_version || 1, 133 | // tslint:disable-next-line: no-any 134 | } as any); 135 | } 136 | 137 | public removeHistoryItem = async (key: string) => { 138 | await this.setState({ 139 | historyItems: { ...this.state.historyItems, [key]: undefined }, 140 | // tslint:disable-next-line: no-any 141 | _persist_version: (this.state as any)._persist_version || 1, 142 | // tslint:disable-next-line: no-any 143 | } as any); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /react-client/src/components/views/order-popup/AskForAddress.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { Loading, TokenIcon } from "@renproject/react-components"; 4 | import RenJS from "@renproject/ren"; 5 | 6 | import { IS_TESTNET } from "../../../lib/environmentVariables"; 7 | import { _catchInteractionErr_, safeJSONStringify } from "../../../lib/errors"; 8 | import { renderToken, Token, Tokens } from "../../../state/generalTypes"; 9 | import { ReactComponent as MetaMask } from "../../../styles/images/metamask.svg"; 10 | import { Popup } from "../Popup"; 11 | 12 | export const AskForAddress: React.StatelessComponent<{ 13 | token: Token, 14 | message: React.ReactNode, 15 | defaultAddress: string, 16 | onAddress(address: string): Promise; 17 | cancel(): void; 18 | }> = ({ token, message, defaultAddress, onAddress, cancel }) => { 19 | // tslint:disable-next-line: prefer-const 20 | let [address, updateAddress] = React.useState(""); 21 | const [error, updateError] = React.useState(null as string | null); 22 | const [submitting, updateSubmitting] = React.useState(false); 23 | const inputRef = React.useRef() as React.MutableRefObject; 24 | const [checkingSkip, setCheckingSkip] = React.useState(true); 25 | 26 | const tokenDetails = Tokens.get(token); 27 | 28 | const submit = async (event?: React.FormEvent) => { 29 | if (event) { event.preventDefault(); } 30 | if (!error && tokenDetails && !tokenDetails.validator(address, IS_TESTNET)) { 31 | updateError(`Invalid ${tokenDetails.chain.toUpperCase()} address`); 32 | return; 33 | } 34 | try { 35 | updateSubmitting(true); 36 | await onAddress(address); 37 | } catch (error) { 38 | updateError(String(error.message || safeJSONStringify(error))); 39 | updateSubmitting(false); 40 | } 41 | }; 42 | 43 | const useDefaultAddress = () => { 44 | address = defaultAddress; 45 | updateAddress(defaultAddress); 46 | const current = inputRef.current; 47 | if (current) { 48 | current.focus(); 49 | } 50 | }; 51 | 52 | const onChange = (event: React.FormEvent): void => { 53 | updateError(null); 54 | updateAddress((event.target as HTMLInputElement).value); 55 | }; 56 | 57 | React.useEffect(() => { 58 | (async () => { 59 | try { 60 | if (tokenDetails && tokenDetails.chain === RenJS.Chains.Ethereum) { 61 | address = defaultAddress; 62 | updateAddress(defaultAddress); 63 | await submit(); 64 | } 65 | } catch (err) { 66 | _catchInteractionErr_(err, "Error in AskForAddress > submit"); 67 | } 68 | setCheckingSkip(false); 69 | })().catch((err => _catchInteractionErr_(err, "Error in AskForAddress > useEffect"))); 70 | }, [tokenDetails]); 71 | 72 | return 73 |
74 | {checkingSkip ? : <>} 75 |
76 | 77 |

{renderToken(token)} address

78 |
79 | {message} 80 |
81 |
82 |
83 | 93 | 94 | {tokenDetails && tokenDetails.chain === RenJS.Chains.Ethereum ? 95 | : 96 | null 97 | } 98 |
99 | {error ?
{error}
: null} 100 |
101 | 102 |
103 |
104 |
105 |
106 |
; 107 | }; 108 | -------------------------------------------------------------------------------- /react-client/src/lib/contractAddresses.ts: -------------------------------------------------------------------------------- 1 | import { AbiInput, toChecksumAddress } from "web3-utils"; 2 | 3 | import { Token } from "../state/generalTypes"; 4 | 5 | const network = process.env.REACT_APP_NETWORK || "testnet"; 6 | 7 | // tslint:disable: non-literal-require 8 | export const syncGetTokenAddress = (networkID: number, token: Token): string => { 9 | // eslint-disable-next-line 10 | switch (token) { 11 | case Token.DAI: 12 | const deployedDaiNetworks = require(`../contracts/${network}/DaiToken.json`).networks; 13 | return deployedDaiNetworks[networkID].address; 14 | case Token.ETH: 15 | return "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; 16 | case Token.BTC: 17 | const deployedBTCNetworks = require(`../contracts/${network}/zBTC.json`).networks; 18 | return deployedBTCNetworks[networkID].address; 19 | case Token.ZEC: 20 | const deployedZECNetworks = require(`../contracts/${network}/zZEC.json`).networks; 21 | return deployedZECNetworks[networkID].address; 22 | case Token.BCH: 23 | const deployedBCHNetworks = require(`../contracts/${network}/zBCH.json`).networks; 24 | return deployedBCHNetworks[networkID].address; 25 | } 26 | }; 27 | 28 | const tokensFromAddresses = {}; 29 | 30 | export const syncGetTokenFromAddress = (networkID: number, address: string): Token => { 31 | // eslint-disable-next-line 32 | 33 | // tslint:disable: no-string-literal 34 | if (toChecksumAddress(address) === "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE") { return Token.ETH; } 35 | tokensFromAddresses["DAI"] = tokensFromAddresses["DAI"] || require(`../contracts/${network}/DaiToken.json`).networks[networkID].address; 36 | if (toChecksumAddress(address) === toChecksumAddress(tokensFromAddresses["DAI"])) { return Token.DAI; } 37 | tokensFromAddresses["BTC"] = tokensFromAddresses["BTC"] || require(`../contracts/${network}/zBTC.json`).networks[networkID].address; 38 | if (toChecksumAddress(address) === toChecksumAddress(tokensFromAddresses["BTC"])) { return Token.BTC; } 39 | tokensFromAddresses["ZEC"] = tokensFromAddresses["ZEC"] || require(`../contracts/${network}/zZEC.json`).networks[networkID].address; 40 | if (toChecksumAddress(address) === toChecksumAddress(tokensFromAddresses["ZEC"])) { return Token.ZEC; } 41 | tokensFromAddresses["BCH"] = tokensFromAddresses["BCH"] || require(`../contracts/${network}/zBCH.json`).networks[networkID].address; 42 | if (toChecksumAddress(address) === toChecksumAddress(tokensFromAddresses["BCH"])) { return Token.BCH; } 43 | // tslint:enable: no-string-literal 44 | 45 | throw new Error(`Unknown token at address ${address} on network ${networkID}`); 46 | }; 47 | 48 | // tslint:disable: non-literal-require 49 | export const syncGetDEXReserveAddress = (networkID: number, token: Token): string => { 50 | // eslint-disable-next-line 51 | switch (token) { 52 | case Token.BTC: 53 | const deployedBTCReserveNetworks = require(`../contracts/${network}/BTC_DAI_Reserve.json`).networks; 54 | return deployedBTCReserveNetworks[networkID].address; 55 | case Token.ZEC: 56 | const deployedZECReserveNetworks = require(`../contracts/${network}/ZEC_DAI_Reserve.json`).networks; 57 | return deployedZECReserveNetworks[networkID].address; 58 | case Token.BCH: 59 | const deployedBCHReserveNetworks = require(`../contracts/${network}/BCH_DAI_Reserve.json`).networks; 60 | return deployedBCHReserveNetworks[networkID].address; 61 | } 62 | return ""; 63 | }; 64 | 65 | export const syncGetDEXAddress = (networkID: number): string => { 66 | const renExNetworks = require(`../contracts/${network}/DEX.json`).networks; 67 | return renExNetworks[networkID].address; 68 | }; 69 | 70 | export const syncGetDEXTradeLog = (): AbiInput[] => { 71 | const abi = require(`../contracts/${network}/DEX.json`).abi; 72 | for (const logAbi of abi) { 73 | if (logAbi.type === "event" && logAbi.name === "LogTrade") { 74 | return logAbi.inputs; 75 | } 76 | } 77 | return []; 78 | }; 79 | 80 | export const syncGetTransfer = (): AbiInput[] => { 81 | const abi = require(`../contracts/${network}/DaiToken.json`).abi; 82 | for (const logAbi of abi) { 83 | if (logAbi.type === "event" && logAbi.name === "Transfer") { 84 | return logAbi.inputs; 85 | } 86 | } 87 | return []; 88 | }; 89 | 90 | export const syncGetDEXAdapterAddress = (networkID: number): string => { 91 | const renExNetworks = require(`../contracts/${network}/DEXAdapter.json`).networks; 92 | return renExNetworks[networkID].address; 93 | }; 94 | 95 | export const getTokenDecimals = (token: Token): number => { 96 | switch (token) { 97 | case Token.DAI: 98 | return 18; 99 | case Token.ETH: 100 | return 18; 101 | case Token.BTC: 102 | return 8; 103 | case Token.ZEC: 104 | return 8; 105 | case Token.BCH: 106 | return 8; 107 | } 108 | }; 109 | -------------------------------------------------------------------------------- /ethereum-contracts/test/helper/logs.ts: -------------------------------------------------------------------------------- 1 | import * as chai from "chai"; 2 | 3 | import BigNumber from "bignumber.js"; 4 | import BN from "bn.js"; 5 | 6 | interface Log { 7 | event: string; 8 | args: object; 9 | } 10 | 11 | interface TransactionReceipt { 12 | tx: string; 13 | receipt: { 14 | transactionHash: string, 15 | transactionIndex: number, 16 | blockHash: string, 17 | blockNumber: number, 18 | gasUsed: number, 19 | cumulativeGasUsed: number, 20 | contractAddress: null, 21 | logs: any[], 22 | status: boolean, 23 | logsBloom: string, 24 | }; 25 | logs: Array<{ 26 | logIndex: number, 27 | transactionIndex: number, 28 | transactionHash: string, 29 | blockHash: string, 30 | blockNumber: number, 31 | address: string, 32 | type: string, 33 | id: string, 34 | event: string, 35 | args: object, 36 | }>; 37 | } 38 | 39 | export const log = (event: string, args: object) => ({ 40 | event, 41 | args, 42 | }); 43 | 44 | // Chai helper for comparing logs 45 | // tslint:disable:only-arrow-functions 46 | chai.use(function (newChai: any, utils: any): void { 47 | const property = "emit"; 48 | newChai.Assertion.addProperty(property, function () { 49 | utils.flag(this, property, true); 50 | }); 51 | 52 | const override = function (fn: any) { 53 | // tslint:disable-next-line:variable-name 54 | return function (_super: any) { 55 | return function (value: any, ...args: any[]) { 56 | if (utils.flag(this, property)) { 57 | const expected = value; 58 | const actual = getLogsFromTx(this._obj); 59 | fn.apply(this, [expected, actual]); 60 | } else { 61 | _super.apply(this, [value, ...args]); 62 | } 63 | }; 64 | }; 65 | }; 66 | 67 | const events = override(function (expected: Log[], actual: Log[]) { 68 | this.assert( 69 | compareArrayOfLogs(expected, actual), 70 | "expected logs #{act} to equal #{exp}", 71 | "expected logs #{act} to be different from #{exp}", 72 | logsToString(expected), 73 | logsToString(actual), 74 | ); 75 | 76 | for (let i = 0; i < expected.length; i++) { 77 | const expectedLog = expected[i]; 78 | const actualLog = actual[i]; 79 | for (const arg in expectedLog.args) { 80 | // skip if the property is from prototype 81 | if (!expectedLog.args.hasOwnProperty(arg)) { continue; } 82 | 83 | const expectedArg = (expectedLog.args as any)[arg]; 84 | const actualArg = (actualLog.args as any)[arg]; 85 | 86 | let sameValues: boolean; 87 | if (BN.isBN(expectedArg) || expectedArg.isBigNumber) { 88 | sameValues = (new BigNumber(expectedArg).eq(new BigNumber(actualArg))); 89 | } else { 90 | sameValues = (expectedArg === (actualLog.args as any)[arg]); 91 | } 92 | 93 | this.assert( 94 | sameValues, 95 | `expected ${arg} to be #{exp} instead of #{act} in log ${expectedLog.event}`, 96 | `expected ${arg} to be different from #{exp} in log ${expectedLog.event}`, 97 | (expectedLog.args as any)[arg], 98 | (actualLog.args as any)[arg], 99 | ); 100 | } 101 | } 102 | }); 103 | newChai.Assertion.overwriteMethod("logs", events); 104 | }); 105 | 106 | // Pretty-print logs 107 | const logsToString = (logs: Log[]): string => { 108 | return `[${logs.map((logItem: Log) => logItem.event).join(", ")}]`; 109 | }; 110 | // const logToString = (logItem: Log): string => { 111 | // return `${logItem.event} ${JSON.stringify(logItem.args)}`; 112 | // }; 113 | 114 | // Compare logs 115 | const compareArrayOfLogs = (expected: Log[], actual: Log[]): boolean => { 116 | if (expected.length !== actual.length) { return false; } 117 | 118 | for (let i = 0; i < expected.length; i++) { 119 | const expectedLog = expected[i]; 120 | const actualLog = actual[i]; 121 | 122 | if (expectedLog.event !== actualLog.event) { return false; } 123 | } 124 | 125 | return true; 126 | }; 127 | 128 | // Extract logs from transaction receipt in correct format.s 129 | export const getLogsFromTx = (tx: TransactionReceipt): Log[] => { 130 | return tx.logs.map((logItem) => { 131 | const args = {}; 132 | for (const arg in logItem.args) { 133 | // skip if the property is from prototype 134 | if (!logItem.args.hasOwnProperty(arg)) { continue; } 135 | 136 | if (isNaN(parseInt(arg, 10)) && arg !== "__length__") { 137 | (args as any)[arg] = (logItem.args as any)[arg]; 138 | } 139 | } 140 | return { 141 | event: logItem.event, 142 | args, 143 | }; 144 | }); 145 | }; 146 | -------------------------------------------------------------------------------- /react-client/src/styles/scss/_stats.scss: -------------------------------------------------------------------------------- 1 | .stats { 2 | color: white; 3 | 4 | h2 { 5 | font-size: 40px; 6 | font-weight: bold; 7 | } 8 | 9 | .trade--history { 10 | padding-bottom: 100px; 11 | 12 | thead th { 13 | font-size: 13px; 14 | color: #f9f9f9; 15 | opacity: 0.6; 16 | text-transform: uppercase; 17 | font-weight: bold; 18 | } 19 | 20 | tr { 21 | font-size: 16px; 22 | } 23 | 24 | a { 25 | color: #006fe8; 26 | } 27 | } 28 | 29 | // @media(min-width: $min-lg) { 30 | max-width: 1400px; 31 | margin: 0 auto; 32 | 33 | padding: 0 30px; 34 | // } 35 | 36 | .stats--rows { 37 | display: flex; 38 | flex-wrap: wrap; 39 | margin-bottom: 30px; 40 | width: 100%; 41 | } 42 | 43 | .trade--history { 44 | .stats--rows .odd { 45 | background-color: #1d2227; 46 | } 47 | 48 | .trade--history--block { 49 | padding-top: 30px; 50 | & + .trade--history--block { 51 | border-top: 1px solid rgba(151, 151, 151, 0.37); 52 | } 53 | } 54 | } 55 | 56 | small { 57 | padding-bottom: 20px; 58 | display: block; 59 | font-size: 16px; 60 | } 61 | 62 | .stat { 63 | width: 100%; 64 | font-size: 16px; 65 | box-sizing: border-box !important; 66 | // padding: 20px !important; 67 | display: flex !important; 68 | justify-content: center !important; 69 | align-items: center !important; 70 | margin-bottom: 20px; 71 | 72 | svg { 73 | margin-left: 10px; 74 | margin-right: 5px; 75 | } 76 | 77 | a[target="_blank"]::after { 78 | content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAANElEQVQYlWNgwAH+owFMARyKGdAl8ZqITxGMj6EQlzNQFOJUhK4QLx+XZyhTSAgwMDAwAADI1rNNfaEUNQAAAABJRU5ErkJggg==); 79 | margin: 0 3px 0 5px; 80 | } 81 | 82 | &.even { 83 | background-color: #1d2227; 84 | } 85 | } 86 | 87 | .graph-stat { 88 | display: flex; 89 | flex-flow: column; 90 | 91 | .graph-stat--loading { 92 | display: flex; 93 | align-items: center; 94 | 95 | .loading { 96 | margin-left: 10px; 97 | } 98 | } 99 | } 100 | 101 | .stat--group { 102 | width: calc(33% - 10px); 103 | margin: 5px; 104 | 105 | @media (max-width: $max-lg) { 106 | width: calc(50% - 10px); 107 | } 108 | 109 | @media (max-width: $max-sm) { 110 | width: calc(100% - 10px); 111 | } 112 | 113 | .stat--group--title { 114 | font-size: 20px; 115 | margin-bottom: 5px; 116 | 117 | svg { 118 | height: 30px; 119 | width: 30px; 120 | max-width: 30px; 121 | } 122 | } 123 | 124 | .stat--content { 125 | height: 300px; 126 | width: 300px; 127 | margin-bottom: 40px; 128 | display: flex; 129 | flex-direction: column; 130 | justify-content: space-between; 131 | } 132 | 133 | .stat--footer { 134 | h2 { 135 | font-size: 58px; 136 | font-weight: bold; 137 | } 138 | 139 | h3 { 140 | font-size: 24px; 141 | font-weight: 200; 142 | } 143 | } 144 | } 145 | 146 | .stats--pagination { 147 | display: flex; 148 | align-items: center; 149 | justify-content: flex-end; 150 | 151 | .page--number { 152 | margin-right: 20px; 153 | } 154 | .page--change { 155 | button { 156 | margin: 0 2px; 157 | min-width: 30px; 158 | width: 30px; 159 | height: 30px; 160 | border: 1px solid #006fe8; 161 | background: none; 162 | color: white; 163 | border-radius: 4px; 164 | 165 | &:disabled { 166 | opacity: 0.5; 167 | } 168 | } 169 | } 170 | } 171 | } 172 | 173 | .stats--title { 174 | display: flex; 175 | justify-content: space-between; 176 | } 177 | 178 | .custom-tooltip { 179 | display: flex; 180 | flex-flow: column; 181 | background: transparentize(#282c35, 0.5); 182 | border: 1px solid rgba(145, 149, 160, 0.1); 183 | box-sizing: border-box; 184 | box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.2); 185 | padding: 20px; 186 | } 187 | 188 | .trade--distribution { 189 | .trade--distribution--entry { 190 | display: flex; 191 | justify-content: space-between; 192 | } 193 | } 194 | 195 | .stats--rows table.responsiveTable { 196 | colgroup { 197 | @media (max-width: $min-md) { 198 | display: none; 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /ethereum-contracts/migrations/3_puzzles.js: -------------------------------------------------------------------------------- 1 | /// 2 | const hashjs = require('hash.js'); 3 | 4 | const zBTC = artifacts.require("zBTC"); 5 | const zZEC = artifacts.require("zZEC"); 6 | const zBCH = artifacts.require("zBCH"); 7 | const ShifterRegistry = artifacts.require("ShifterRegistry"); 8 | 9 | const BTC_DAI_Reserve = artifacts.require("BTC_DAI_Reserve"); 10 | const ZEC_DAI_Reserve = artifacts.require("ZEC_DAI_Reserve"); 11 | const BCH_DAI_Reserve = artifacts.require("BCH_DAI_Reserve"); 12 | const DEXAdapter = artifacts.require("DEXAdapter"); 13 | const DEX = artifacts.require("DEX"); 14 | const DaiToken = artifacts.require("DaiToken"); 15 | 16 | const BTCPuzzle = artifacts.require("BTCPuzzle"); 17 | 18 | const networks = require("./networks.js"); 19 | 20 | const generateSecretMessage = (secret) => { 21 | return `Secret(${secret})`; 22 | }; 23 | 24 | module.exports = async function (deployer, network, accounts) { 25 | deployer.logger.log(`Deploying to ${network}...`); 26 | 27 | network = network.replace("-fork", ""); 28 | 29 | const addresses = networks[network] || {}; 30 | const config = networks[network] ? networks[network].config : networks.config; 31 | 32 | const renNetwork = addresses.renNetwork || networks.config.renNetwork; 33 | 34 | /* 35 | DEX.address = addresses.DEX || ""; 36 | DEXAdapter.address = addresses.DEXAdapter || ""; 37 | DaiToken.address = renNetwork.addresses.tokens.DAI.address || ""; 38 | 39 | if (!DaiToken.address) { 40 | await deployer.deploy(DaiToken) 41 | } 42 | 43 | if (!DEX.address) { 44 | await deployer.deploy( 45 | DEX, 46 | DaiToken.address, 47 | ); 48 | } 49 | const dex = await DEX.at(DEX.address); 50 | 51 | if (!DEXAdapter.address) { 52 | await deployer.deploy( 53 | DEXAdapter, 54 | DEX.address, 55 | ShifterRegistry.address, 56 | ); 57 | } 58 | 59 | deployer.logger.log("Deploying reserves..."); 60 | 61 | const deployReserve = async (quoteToken, baseToken, Reserve) => { 62 | await deployer.deploy(Reserve, baseToken.address, quoteToken.address, config.dexFees); 63 | const res = await Reserve.at(Reserve.address); 64 | await dex.registerReserve(quoteToken.address, Reserve.address); 65 | // const dai = await rightToken.at(rightToken.address); 66 | // await dai.transfer(Reserve.address, "100000000000000000000"); 67 | } 68 | 69 | BTC_DAI_Reserve.address = await dex.reserves(zBTC.address); 70 | if (BTC_DAI_Reserve.address === "0x0000000000000000000000000000000000000000") { 71 | await deployReserve(zBTC, DaiToken, BTC_DAI_Reserve); 72 | deployer.logger.log(`[${"BTC"}, ${"DAI"}]: ${BTC_DAI_Reserve.address}`); 73 | } else { 74 | deployer.logger.log(`\nUsing existing reserve for [${"BTC"}, ${"DAI"}]: ${BTC_DAI_Reserve.address}\n`); 75 | } 76 | 77 | ZEC_DAI_Reserve.address = await dex.reserves(zZEC.address); 78 | if (ZEC_DAI_Reserve.address === "0x0000000000000000000000000000000000000000") { 79 | await deployReserve(zZEC, DaiToken, ZEC_DAI_Reserve); 80 | deployer.logger.log(`[${"ZEC"}, ${"DAI"}]: ${ZEC_DAI_Reserve.address}`); 81 | } else { 82 | deployer.logger.log(`\nUsing existing reserve for [${"ZEC"}, ${"DAI"}]: ${ZEC_DAI_Reserve.address}\n`); 83 | } 84 | 85 | BCH_DAI_Reserve.address = await dex.reserves(zBCH.address); 86 | if (BCH_DAI_Reserve.address === "0x0000000000000000000000000000000000000000") { 87 | await deployReserve(zBCH, DaiToken, BCH_DAI_Reserve); 88 | deployer.logger.log(`[${"BCH"}, ${"DAI"}]: ${BCH_DAI_Reserve.address}`); 89 | } else { 90 | deployer.logger.log(`\nUsing existing reserve for [${"BCH"}, ${"DAI"}]: ${BCH_DAI_Reserve.address}\n`); 91 | } 92 | 93 | // await web3.eth.sendTransaction({ to: "", from: accounts[0], value: web3.utils.toWei("1", "ether") }); 94 | */ 95 | 96 | /** PUZZLES **************************************************************/ 97 | 98 | const maxGasPrice = web3.utils.toWei("21", "gwei"); 99 | const secret = "helloWorld"; 100 | const msg = generateSecretMessage(secret); 101 | const hash = hashjs.sha256().update(msg).digest("hex"); 102 | deployer.logger.log(`secret hash is: 0x${hash}`); 103 | await deployer.deploy( 104 | BTCPuzzle, 105 | ShifterRegistry.address, 106 | `0x${hash}`, 107 | maxGasPrice, 108 | ); 109 | const puz = await BTCPuzzle.at(BTCPuzzle.address); 110 | const validSecret = await puz.validateSecret(web3.utils.fromAscii(secret)); 111 | deployer.logger.log(`valid?: ${validSecret}`); 112 | if (validSecret) { 113 | deployer.logger.log("secret is valid"); 114 | } else { 115 | deployer.logger.log("secret is not valid"); 116 | } 117 | 118 | /** LOG *******************************************************************/ 119 | 120 | /* 121 | deployer.logger.log({ 122 | DEX: DEX.address, 123 | DEXAdapter: DEXAdapter.address, 124 | BTC_DAI_Reserve: BTC_DAI_Reserve.address, 125 | ZEC_DAI_Reserve: ZEC_DAI_Reserve.address, 126 | BCH_DAI_Reserve: BCH_DAI_Reserve.address, 127 | }); 128 | */ 129 | } -------------------------------------------------------------------------------- /react-client/src/styles/scss/_tutorial.scss: -------------------------------------------------------------------------------- 1 | .tutorial { 2 | display: flex; 3 | min-height: 465px; 4 | 5 | ul { 6 | list-style-type: none; 7 | padding: 0; 8 | } 9 | 10 | .tutorial--left { 11 | @extend .no-mobile; 12 | width: 190px; 13 | box-shadow: 0px 1px 4px rgba(0, 27, 58, 0.2); 14 | display: flex; 15 | flex-flow: column; 16 | justify-content: space-between; 17 | text-align: left; 18 | 19 | .tutorial--left--top { 20 | 21 | .tutorial--left--setup { 22 | font-style: normal; 23 | font-weight: bold; 24 | font-size: 12px; 25 | line-height: 14px; 26 | color: $grey-11; 27 | padding: 20px 20px; 28 | text-transform: uppercase; 29 | } 30 | 31 | li { 32 | cursor: pointer; 33 | padding: 20px 20px; 34 | font-style: normal; 35 | font-weight: 500; 36 | font-size: 14px; 37 | line-height: 16px; 38 | color: $blue-bright; 39 | border-top: 1px solid rgba(0, 0, 0, 0); 40 | border-bottom: 1px solid rgba(0, 0, 0, 0); 41 | 42 | &.selected { 43 | box-shadow: 0px 1px 2px rgba(0, 27, 58, 0.0509804); 44 | border-top: 1px solid $background; 45 | border-bottom: 1px solid $background; 46 | background: $background; 47 | } 48 | 49 | &:before { 50 | content: ""; 51 | border-radius: 100%; 52 | border: 2px solid $blue-bright; 53 | color: $blue-bright; 54 | width: 16px; 55 | height: 16px; 56 | display: inline-block; 57 | margin-right: 10px; 58 | margin-bottom: -3px; 59 | } 60 | 61 | &.checked:before { 62 | background: url(../images/icons/check.svg) no-repeat center center; 63 | } 64 | } 65 | } 66 | 67 | .tutorial--left--bottom { 68 | padding: 0 25px; 69 | 70 | li { 71 | svg { 72 | margin-right: 10px; 73 | } 74 | 75 | a { 76 | font-style: normal; 77 | font-weight: 500; 78 | font-size: 14px; 79 | line-height: 30px; 80 | 81 | color: $grey-11; 82 | } 83 | } 84 | } 85 | } 86 | 87 | .tutorial--page { 88 | height: 100%; 89 | width: 400px; 90 | 91 | display: flex; 92 | flex-flow: column; 93 | justify-content: space-between; 94 | 95 | @media(max-width: $min-lg) { 96 | width: 300px; 97 | } 98 | 99 | .tutorial--page--body { 100 | 101 | padding: 30px 50px; 102 | padding-bottom: 0; 103 | 104 | @media(max-width: $min-lg) { 105 | padding: 30px 20px; 106 | padding-bottom: 0; 107 | } 108 | 109 | 110 | h2 { 111 | margin-top: 30px; 112 | font-weight: bold; 113 | font-size: 18px; 114 | line-height: 24px; 115 | color: $grey-12; 116 | 117 | } 118 | 119 | p { 120 | font-weight: normal; 121 | font-size: 14px; 122 | line-height: 21px; 123 | color: $grey-11; 124 | } 125 | 126 | a { 127 | font-weight: normal; 128 | font-size: 14px; 129 | line-height: 21px; 130 | color: $blue-bright; 131 | 132 | } 133 | 134 | ol { 135 | padding-left: 15px; 136 | } 137 | 138 | li { 139 | font-weight: normal; 140 | font-size: 14px; 141 | line-height: 21px; 142 | color: $grey-11; 143 | // text-align: left; 144 | 145 | svg { 146 | width: 25px; 147 | } 148 | } 149 | 150 | li+li { 151 | margin-top: 20px; 152 | } 153 | 154 | svg { 155 | width: 200px; 156 | } 157 | 158 | >* { 159 | margin: 20px 0; 160 | } 161 | } 162 | 163 | .tutorial--page--buttons { 164 | display: flex; 165 | justify-content: space-between; 166 | padding: 30px; 167 | padding-top: 0; 168 | 169 | .button { 170 | height: 40px; 171 | width: 140px; 172 | border-radius: 4px; 173 | background: linear-gradient(182.32deg, $blue-light -201.77%, $blue-bright 93.76%); 174 | font-weight: 500; 175 | font-size: 14px; 176 | } 177 | 178 | .button--white { 179 | border: 1.6px solid $blue-bright; 180 | background: $background; 181 | } 182 | } 183 | 184 | } 185 | } -------------------------------------------------------------------------------- /react-client/src/styles/scss/_header.scss: -------------------------------------------------------------------------------- 1 | .navbar { 2 | height: 100px; 3 | padding: 10px; 4 | 5 | * { 6 | z-index: 15; 7 | } 8 | 9 | .header--logo { 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | 14 | &:hover { 15 | text-decoration: none !important; 16 | } 17 | 18 | h1 { 19 | margin: 0; 20 | font-size: 25px; 21 | font-weight: 200; 22 | } 23 | 24 | svg { 25 | height: 40px; 26 | min-height: 40px; 27 | margin-right: 20px; 28 | 29 | .ren-logo { 30 | fill: $foreground; 31 | } 32 | } 33 | } 34 | 35 | .header--account--type { 36 | display: inline; 37 | 38 | } 39 | 40 | .header--account--address { 41 | display: flex; 42 | align-items: center; 43 | 44 | .blocky { 45 | height: 18px; 46 | width: 18px; 47 | border-radius: 100%; 48 | margin-right: 10px; 49 | } 50 | 51 | .blocky--outer { 52 | height: 18px; 53 | } 54 | 55 | 56 | } 57 | 58 | .nav-dropdown--account { 59 | >a::after { 60 | content: none !important; 61 | } 62 | } 63 | 64 | .header--account--type::before { 65 | content: ''; 66 | display: inline-block; 67 | width: 7px; 68 | height: 7px; 69 | margin-right: 10px; 70 | -moz-border-radius: 3.5px; 71 | -webkit-border-radius: 3.5px; 72 | border-radius: 3.5px; 73 | background-color: $orange; 74 | display: none !important; 75 | } 76 | 77 | .header--account--connected .header--account--type::before { 78 | width: 0; 79 | } 80 | 81 | .nav--divider { 82 | border: 0.5px solid $background; 83 | } 84 | 85 | .nav--button { 86 | background: $background; 87 | box-sizing: border-box; 88 | border-radius: 4px; 89 | margin: 0 20px; 90 | padding-right: 0rem !important; 91 | padding-left: 0rem !important; 92 | 93 | @media(min-width: $min-lg) { 94 | &.nav--button-border { 95 | background: #282C35; 96 | border: 1px solid rgba(145, 149, 160, 0.1); 97 | box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.1); 98 | border-radius: 4px; 99 | 100 | padding-right: 1rem !important; 101 | padding-left: 1rem !important; 102 | } 103 | } 104 | 105 | svg { 106 | margin-right: 8px; 107 | } 108 | } 109 | 110 | .nav-item { 111 | border: 1px solid $border !important; 112 | box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.2); 113 | } 114 | 115 | .nav--bubble { 116 | background: $background; 117 | border: 1px solid $foreground; 118 | box-sizing: border-box; 119 | border-radius: 16px; 120 | margin: 0 10px; 121 | padding: 0 10px; 122 | display: flex; 123 | align-items: center; 124 | justify-content: center 125 | } 126 | 127 | .nav-link { 128 | color: $foreground !important; 129 | } 130 | 131 | @media(max-width: $max-md) { 132 | .nav--bubble { 133 | border: none; 134 | border-radius: none; 135 | } 136 | 137 | .navbar-collapse { 138 | background: $background; 139 | border-bottom: 1px solid $background; 140 | box-shadow: 0px 1px 2px rgba(0, 26, 58, 0.0509804); 141 | padding: 20px 0; 142 | } 143 | } 144 | 145 | .dropdown-menu>* { 146 | padding: 12px 16px; 147 | cursor: pointer; 148 | 149 | font-size: 12px; 150 | font-weight: 200; // originally 400 151 | line-height: normal; 152 | text-align: left; 153 | color: $grey-12; 154 | 155 | display: flex; 156 | align-items: center; 157 | 158 | border: 1px solid white; 159 | margin: 0 10px; 160 | width: calc(100% - 20px); 161 | 162 | &:hover { 163 | border: 1px solid $background; 164 | border-radius: 6px; 165 | background-color: inherit; 166 | } 167 | 168 | &:active { 169 | color: $foreground; 170 | } 171 | 172 | &.header--dropdown--selected { 173 | border: 1px solid $background; 174 | border-radius: 6px; 175 | background-color: inherit; 176 | } 177 | } 178 | 179 | .dropdown-item { 180 | color: $background; 181 | 182 | &:active { 183 | color: $blue-bright; 184 | } 185 | } 186 | } 187 | 188 | .navbar-toggler { 189 | border: 0; 190 | 191 | .navbar-toggler-icon { 192 | background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgb(200, 200, 200)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") !important; 193 | } 194 | } 195 | 196 | .ren-logo-small { 197 | display: none; 198 | } 199 | 200 | @media(max-width: $max-xs) { 201 | .ren-logo-small { 202 | display: initial; 203 | } 204 | 205 | .ren-logo-big { 206 | display: none; 207 | } 208 | } -------------------------------------------------------------------------------- /react-client/src/contracts/devnet/DEX.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [ 3 | { 4 | "constant": true, 5 | "inputs": [ 6 | { 7 | "name": "", 8 | "type": "bytes32" 9 | } 10 | ], 11 | "name": "reserves", 12 | "outputs": [ 13 | { 14 | "name": "", 15 | "type": "address" 16 | } 17 | ], 18 | "payable": false, 19 | "stateMutability": "view", 20 | "type": "function" 21 | }, 22 | { 23 | "constant": true, 24 | "inputs": [], 25 | "name": "feeinBIPs", 26 | "outputs": [ 27 | { 28 | "name": "", 29 | "type": "uint256" 30 | } 31 | ], 32 | "payable": false, 33 | "stateMutability": "view", 34 | "type": "function" 35 | }, 36 | { 37 | "constant": true, 38 | "inputs": [], 39 | "name": "ethereum", 40 | "outputs": [ 41 | { 42 | "name": "", 43 | "type": "address" 44 | } 45 | ], 46 | "payable": false, 47 | "stateMutability": "view", 48 | "type": "function" 49 | }, 50 | { 51 | "inputs": [ 52 | { 53 | "name": "_feeinBIPs", 54 | "type": "uint256" 55 | } 56 | ], 57 | "payable": false, 58 | "stateMutability": "nonpayable", 59 | "type": "constructor" 60 | }, 61 | { 62 | "anonymous": false, 63 | "inputs": [ 64 | { 65 | "indexed": false, 66 | "name": "_src", 67 | "type": "address" 68 | }, 69 | { 70 | "indexed": false, 71 | "name": "_dst", 72 | "type": "address" 73 | }, 74 | { 75 | "indexed": false, 76 | "name": "_sendAmount", 77 | "type": "uint256" 78 | }, 79 | { 80 | "indexed": false, 81 | "name": "_recvAmount", 82 | "type": "uint256" 83 | } 84 | ], 85 | "name": "LogTrade", 86 | "type": "event" 87 | }, 88 | { 89 | "constant": false, 90 | "inputs": [ 91 | { 92 | "name": "_to", 93 | "type": "address" 94 | }, 95 | { 96 | "name": "_src", 97 | "type": "address" 98 | }, 99 | { 100 | "name": "_dst", 101 | "type": "address" 102 | }, 103 | { 104 | "name": "_sendAmount", 105 | "type": "uint256" 106 | } 107 | ], 108 | "name": "trade", 109 | "outputs": [ 110 | { 111 | "name": "", 112 | "type": "uint256" 113 | } 114 | ], 115 | "payable": true, 116 | "stateMutability": "payable", 117 | "type": "function" 118 | }, 119 | { 120 | "constant": false, 121 | "inputs": [ 122 | { 123 | "name": "_a", 124 | "type": "address" 125 | }, 126 | { 127 | "name": "_b", 128 | "type": "address" 129 | }, 130 | { 131 | "name": "_reserve", 132 | "type": "address" 133 | } 134 | ], 135 | "name": "registerReserve", 136 | "outputs": [], 137 | "payable": false, 138 | "stateMutability": "nonpayable", 139 | "type": "function" 140 | }, 141 | { 142 | "constant": true, 143 | "inputs": [ 144 | { 145 | "name": "_src", 146 | "type": "address" 147 | }, 148 | { 149 | "name": "_dst", 150 | "type": "address" 151 | }, 152 | { 153 | "name": "_sendAmount", 154 | "type": "uint256" 155 | } 156 | ], 157 | "name": "calculateReceiveAmount", 158 | "outputs": [ 159 | { 160 | "name": "", 161 | "type": "uint256" 162 | } 163 | ], 164 | "payable": false, 165 | "stateMutability": "view", 166 | "type": "function" 167 | }, 168 | { 169 | "constant": true, 170 | "inputs": [ 171 | { 172 | "name": "_a", 173 | "type": "address" 174 | }, 175 | { 176 | "name": "_b", 177 | "type": "address" 178 | } 179 | ], 180 | "name": "reserve", 181 | "outputs": [ 182 | { 183 | "name": "", 184 | "type": "address" 185 | } 186 | ], 187 | "payable": false, 188 | "stateMutability": "view", 189 | "type": "function" 190 | }, 191 | { 192 | "constant": true, 193 | "inputs": [ 194 | { 195 | "name": "_a", 196 | "type": "address" 197 | }, 198 | { 199 | "name": "_b", 200 | "type": "address" 201 | } 202 | ], 203 | "name": "tokenPairID", 204 | "outputs": [ 205 | { 206 | "name": "", 207 | "type": "bytes32" 208 | } 209 | ], 210 | "payable": false, 211 | "stateMutability": "pure", 212 | "type": "function" 213 | } 214 | ], 215 | "compiler": { 216 | "name": "solc", 217 | "version": "0.5.8+commit.23d335f2.Emscripten.clang" 218 | }, 219 | "contractName": "DEX", 220 | "networks": { 221 | "42": { 222 | "events": {}, 223 | "links": {}, 224 | "address": "0x641aBd6CC3E5CbDDAf2586A906d7F694C4d1ee2E", 225 | "transactionHash": "0x552d6bee96bf67aba2056b93172561539f0531084cc74a6d0443220d0f900ca2" 226 | } 227 | }, 228 | "schemaVersion": "3.0.11", 229 | "sourcePath": "dex-demo/ethereum-contracts/contracts/DEX.sol" 230 | } --------------------------------------------------------------------------------