├── .gitignore ├── src ├── lib │ ├── bfx │ │ ├── stableCoins.js │ │ ├── getUSDPrice.js │ │ ├── getCandleOpen.js │ │ ├── calculateVolume.js │ │ └── getCandle.js │ ├── parse │ │ ├── response │ │ │ ├── orders.js │ │ │ ├── cancel_order.js │ │ │ ├── submit_order.js │ │ │ └── release_tokens.js │ │ └── error.js │ ├── bind_api.js │ └── error │ │ └── reasons.js ├── api │ ├── account │ │ ├── balance.js │ │ ├── token_balance.js │ │ ├── unlock.js │ │ └── select.js │ ├── sign │ │ ├── request.js │ │ ├── cancel_order.js │ │ ├── sign.js │ │ └── order.js │ ├── get_order.js │ ├── get_config.js │ ├── contract │ │ ├── locked.js │ │ ├── deposit_lock.js │ │ ├── is_approved.js │ │ ├── approve.js │ │ ├── lock.js │ │ ├── unlock.js │ │ ├── create_order.js │ │ └── abi │ │ │ ├── locker.abi.js │ │ │ └── token.abi.js │ ├── submit_buy_order.js │ ├── submit_sell_order.js │ ├── eth │ │ ├── call.js │ │ ├── get_network.js │ │ └── send.js │ ├── cancel_order.js │ ├── get_orders.js │ ├── get_orders_hist.js │ ├── get_fee_rate.js │ ├── release_tokens.js │ └── submit_order.js ├── config.js └── efx.js ├── examples ├── browser_with_web3.html ├── browser.html ├── node_unlock_eth.js ├── node_release_tokens.js ├── node_buy_eth.js ├── node_sell_eth.js ├── node_get_orders.js ├── node_cancel_orders.js ├── node_lock_eth_usd.js ├── node_get_fee_rate.js ├── node_sell_eth_infura.js ├── node_balance_and_locked.js ├── node_spam_get_orders.js ├── node_spam_lock_eth_and_usd.js ├── node.js └── node_spam_submit_orders.js ├── test ├── fixtures │ ├── contracts │ │ ├── deployed.js │ │ ├── deploy.js │ │ ├── WrapperLockUsd.sol │ │ ├── WrapperLockEth.sol │ │ ├── ZRXToken.sol │ │ └── ZRXToken.sol.json │ └── nock │ │ ├── feeRate.js │ │ └── get_conf.js ├── helpers │ ├── ecRecover.js │ ├── instance.js │ ├── checksum_address.js │ └── compile.js ├── eth.js ├── deploy.js ├── account.js ├── index.js ├── signing.js ├── blockchain-api.js └── http-api.js ├── .env ├── .travis.yml ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | package-lock.json 4 | yarn-error.log 5 | test/contracts/*.json 6 | yarn.lock 7 | -------------------------------------------------------------------------------- /src/lib/bfx/stableCoins.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | USD: 1, 3 | DAI: 1, 4 | UDC: 1, 5 | TSD: 1, 6 | UST: 1, 7 | PAX: 1 8 | } -------------------------------------------------------------------------------- /src/api/account/balance.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns ETH balance 3 | */ 4 | module.exports = (efx) => { 5 | return efx.web3.eth.getBalance(efx.get('account')) 6 | } 7 | -------------------------------------------------------------------------------- /src/api/sign/request.js: -------------------------------------------------------------------------------- 1 | module.exports = (efx, contents) => { 2 | return { 3 | // headers: new window.Headers(), 4 | // mode: 'cors', 5 | // cache: 'default' 6 | json: JSON.stringify({contents}) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/api/get_order.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * A simple alias that specify an order when calling efx.getOrders method 4 | */ 5 | module.exports = async (efx, id, nonce, signature) => { 6 | return efx.getOrders(null, id, nonce, signature) 7 | } 8 | -------------------------------------------------------------------------------- /examples/browser_with_web3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /test/fixtures/contracts/deployed.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Leverages require.js loading cache in order to share an object 3 | * among multiple files on the same process. 4 | 5 | * NOTE: 6 | * - this is ugly 7 | * - quick hack to make tests run 8 | */ 9 | module.exports = {} 10 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # replace with your own infura key, this key isn't guaranteed to work forever 2 | INFURA_URL=https://mainnet.infura.io/v3/af1d9fbeceb44e76be1bb93738f00d19 3 | # generated using https://vanity-eth.tk 4 | PRIVATE_KEY=0x49e4d1e2aa7d026188251392dd2d335c176d846d8a894a8092c835f3b345e2ad -------------------------------------------------------------------------------- /examples/browser.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | -------------------------------------------------------------------------------- /src/api/sign/cancel_order.js: -------------------------------------------------------------------------------- 1 | const utils = require('ethereumjs-util') 2 | 3 | module.exports = (efx, orderId) => { 4 | //orderId = utils.sha3(orderId.toString(16)) 5 | 6 | //const toSign = utils.bufferToHex(orderId).slice(2) 7 | 8 | return efx.sign(orderId.toString(16)) 9 | } 10 | -------------------------------------------------------------------------------- /src/api/account/token_balance.js: -------------------------------------------------------------------------------- 1 | module.exports = (efx, token) => { 2 | const currency = efx.config['0x'].tokenRegistry[token] 3 | const action = 'balanceOf' 4 | const args = [ efx.get('account') ] 5 | 6 | return efx.eth.call(efx.contract.abi.token, currency.wrapperAddress, action, args) 7 | } 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | 5 | sudo: false 6 | 7 | install: 8 | - npm install 9 | - npm install -g ganache-cli 10 | 11 | before_script: 12 | - ganache-cli > ganache-cli.log & 13 | - sleep 5 14 | 15 | script: 16 | - npm run test 17 | 18 | cache: 19 | yarn: true 20 | -------------------------------------------------------------------------------- /src/api/account/unlock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Unlocks an account for given duration 3 | **/ 4 | module.exports = (efx, password, duration = 60) => { 5 | const {web3} = efx 6 | 7 | // TODO: can we improve this somehow? 8 | return web3.eth.personal.unlockAccount( 9 | efx.get('account'), 10 | password 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /src/api/get_config.js: -------------------------------------------------------------------------------- 1 | const { post } = require('request-promise') 2 | 3 | module.exports = async (efx) => { 4 | const url = efx.config.api + '/r/get/conf' 5 | 6 | const exchangeConf = await post(url, { json: {} }) 7 | 8 | efx.config = Object.assign({}, efx.config, exchangeConf) 9 | 10 | return exchangeConf 11 | }; 12 | -------------------------------------------------------------------------------- /src/api/contract/locked.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the amount you have locked for given token 3 | */ 4 | module.exports = (efx, token) => { 5 | const currency = efx.config['0x'].tokenRegistry[token] 6 | const action = 'balanceOf' 7 | const args = [ efx.get('account') ] 8 | 9 | return efx.eth.call(efx.contract.abi.locker, currency.wrapperAddress, action, args) 10 | } 11 | -------------------------------------------------------------------------------- /src/lib/parse/response/orders.js: -------------------------------------------------------------------------------- 1 | const parseError = require('../error') 2 | 3 | module.exports = async (request) => { 4 | try { 5 | const response = await request 6 | 7 | return response 8 | } catch (error) { 9 | 10 | // if it's not a HTTP response error, 11 | // throw the error 12 | if(!error.response) { 13 | throw error 14 | } 15 | 16 | return parseError(error.response.body) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/lib/parse/response/cancel_order.js: -------------------------------------------------------------------------------- 1 | const parseError = require('../error') 2 | 3 | module.exports = async (request) => { 4 | try { 5 | const response = await request 6 | 7 | return response 8 | } catch (error) { 9 | 10 | // if it's not a HTTP response error, 11 | // throw the error 12 | if(!error.response) { 13 | throw error 14 | } 15 | 16 | return parseError(error.response.body) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/lib/parse/response/submit_order.js: -------------------------------------------------------------------------------- 1 | const parseError = require('../error') 2 | 3 | module.exports = async (request) => { 4 | try { 5 | const response = await request 6 | 7 | return response 8 | } catch (error) { 9 | 10 | // if it's not a HTTP response error, 11 | // throw the error 12 | if(!error.response) { 13 | throw error 14 | } 15 | 16 | return parseError(error.response.body) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/lib/parse/response/release_tokens.js: -------------------------------------------------------------------------------- 1 | const parseError = require('../error') 2 | 3 | module.exports = async (request) => { 4 | try { 5 | const response = await request 6 | 7 | return response 8 | } catch (error) { 9 | 10 | // if it's not a HTTP response error, 11 | // throw the error 12 | if(!error.response) { 13 | throw error 14 | } 15 | 16 | return parseError(error.response.body) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/api/submit_buy_order.js: -------------------------------------------------------------------------------- 1 | const {post} = require('request-promise') 2 | const parse = require('../lib/parse/response/submit_order') 3 | 4 | module.exports = (efx, symbol, amount, price, gid, cid, signedOrder, validFor, partner_id, fee_rate) => { 5 | 6 | // force amount to be positive ( buy order ) 7 | amount = Math.abs(amount) 8 | 9 | return efx.submitOrder(symbol, amount, price, gid, cid, signedOrder, validFor, partner_id, fee_rate) 10 | } 11 | -------------------------------------------------------------------------------- /src/api/submit_sell_order.js: -------------------------------------------------------------------------------- 1 | const {post} = require('request-promise') 2 | const parse = require('../lib/parse/response/submit_order') 3 | 4 | module.exports = (efx, symbol, amount, price, gid, cid, signedOrder, validFor, partner_id, fee_rate) => { 5 | 6 | // force amount to be negative ( sell order ) 7 | amount = Math.abs(amount) * -1 8 | 9 | return efx.submitOrder(symbol, amount, price, gid, cid, signedOrder, validFor, partner_id, fee_rate) 10 | } 11 | -------------------------------------------------------------------------------- /src/api/eth/call.js: -------------------------------------------------------------------------------- 1 | module.exports = async (efx, abi, address, action, args, options = {}) => { 2 | const { web3 } = efx 3 | 4 | const contract = new web3.eth.Contract(abi, address) 5 | 6 | // using eth.call 7 | // parseInt( response, 16 ) to convert int 8 | /** 9 | return efx.web3.eth.call({ 10 | to: address, 11 | data: contract.methods[action](...args).encodeABI() 12 | }) 13 | **/ 14 | 15 | return contract.methods[action](...args).call() 16 | } 17 | -------------------------------------------------------------------------------- /src/lib/parse/error.js: -------------------------------------------------------------------------------- 1 | const reasons = require('../error/reasons') 2 | 3 | module.exports = (errorArray) => { 4 | 5 | // get the last word from the error 6 | const message = errorArray[2].split(" ").pop() 7 | 8 | let error = { 9 | code: errorArray[1], 10 | message: errorArray[2] 11 | } 12 | 13 | for(let message in reasons){ 14 | if(error.message == message){ 15 | error.reason = reasons[message].trim() 16 | } 17 | } 18 | 19 | return {error} 20 | } 21 | -------------------------------------------------------------------------------- /src/api/sign/sign.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Signs toSign assyncronously 3 | * 4 | * For more information, check: 5 | * https://web3js.readthedocs.io/en/1.0/web3-eth.html#sign 6 | */ 7 | 8 | module.exports = (efx, toSign) => { 9 | // metamask will take care of the 3rd parameter, "password" 10 | if (efx.web3.currentProvider.isMetaMask) { 11 | return efx.web3.eth.personal.sign(toSign, efx.get('account')) 12 | } else { 13 | return efx.web3.eth.sign(toSign, efx.get('account')) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/api/cancel_order.js: -------------------------------------------------------------------------------- 1 | const {post} = require('request-promise') 2 | const parse = require('../lib/parse/response/cancel_order') 3 | 4 | module.exports = async (efx, orderId, signature) => { 5 | if (!signature) { 6 | signature = await efx.sign.cancelOrder(orderId) 7 | } 8 | 9 | const url = efx.config.api + '/w/oc' 10 | 11 | const protocol = '0x' 12 | 13 | orderId = parseInt(orderId) 14 | const data = {orderId, protocol, signature} 15 | 16 | return parse(post(url, {json: data})) 17 | } 18 | -------------------------------------------------------------------------------- /src/api/contract/deposit_lock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the unlockUntil 3 | */ 4 | module.exports = (efx, token) => { 5 | const currency = efx.config['0x'].tokenRegistry[token] 6 | 7 | const args = [ 8 | efx.get('account') // address _owner 9 | ] 10 | 11 | const action = 'depositLock' 12 | 13 | // REVIEW: not sure if we will be able to read the contract array this way 14 | // TODO: Test it on ropsten 15 | return efx.eth.call(efx.contract.abi.locker, currency.wrapperAddress, action, args) 16 | } 17 | -------------------------------------------------------------------------------- /src/api/contract/is_approved.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Check if a token is approved for locking 3 | */ 4 | module.exports = (efx, token) => { 5 | // REVIEW: shall we throw if token is ETH or USDT ? 6 | const currency = efx.config['0x'].tokenRegistry[token] 7 | 8 | const args = [ 9 | efx.get('account'), // address _owner 10 | currency.wrapperAddress // address _spender 11 | ] 12 | 13 | const action = 'allowance' 14 | 15 | return efx.eth.call(efx.contract.abi.token, currency.tokenAddress, action, args) 16 | } 17 | -------------------------------------------------------------------------------- /src/lib/bfx/getUSDPrice.js: -------------------------------------------------------------------------------- 1 | const { post } = require('request-promise') 2 | const stableCoins = require('./stableCoins') 3 | 4 | const BFX_API = 'https://api.deversifi.com/bfx/v2' 5 | 6 | module.exports = async token => { 7 | if (stableCoins[token]) { 8 | return stableCoins[token] 9 | } 10 | 11 | const response = await post({ 12 | url: BFX_API + `/calc/fx`, 13 | json: true, 14 | body: { 15 | ccy1: token, 16 | ccy2: 'USD' 17 | } 18 | }) 19 | 20 | return response[0] 21 | } 22 | -------------------------------------------------------------------------------- /src/api/eth/get_network.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Returns current network id and name 3 | * see: 4 | * https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md#construction_worker-network-check 5 | * 6 | **/ 7 | module.exports = async (efx) => { 8 | const id = await efx.web3.eth.net.getId() 9 | 10 | const labels = { 11 | '1': 'mainnet', 12 | '2': 'morden', 13 | '3': 'ropsten', 14 | '4': 'Rinkeby', 15 | '42': 'Kovan' 16 | } 17 | 18 | return { 19 | id: id, 20 | name: labels[id] || 'unknown' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | // test environment 'https://test.ethfinex.com/trustless/v1', 4 | api: 'https://api.stg.deversifi.com/v1/trading', 5 | 6 | // default expiration time for orders in seconds, used by create_order.js 7 | defaultExpiry: 3600, 8 | 9 | // in case no provider is provided we will try connecting to the this default 10 | // address 11 | defaultProvider: 'http://localhost:8545', 12 | 13 | // default account to select in case no account is provided by the userConfig 14 | // parameter 15 | account: 0 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/bfx/getCandleOpen.js: -------------------------------------------------------------------------------- 1 | const getCandle = require('./getCandle') 2 | 3 | /** 4 | * We will always return 1 USD for stable coins 5 | */ 6 | const stableCoins = require('./stableCoins') 7 | 8 | module.exports = async (token, timeframe, timestamp ) => { 9 | if (stableCoins[token]) { 10 | return stableCoins[token] 11 | } 12 | 13 | timestamp = timestamp || Date.now() 14 | timeframe = timeframe || '1h' 15 | 16 | const candle = await getCandle(token, timestamp, timeframe) 17 | 18 | // return the open price for the 1 Hour candle 19 | return candle[2] 20 | } -------------------------------------------------------------------------------- /src/api/get_orders.js: -------------------------------------------------------------------------------- 1 | const { post } = require('request-promise') 2 | const parse = require('../lib/parse/response/orders') 3 | 4 | module.exports = async (efx, symbol, id, nonce, signature) => { 5 | let url = efx.config.api + '/r/orders' 6 | 7 | if (symbol) { 8 | url += '/t' + symbol 9 | } 10 | 11 | const protocol = '0x' 12 | 13 | if (!nonce) { 14 | nonce = ((Date.now() / 1000) + 30) + '' 15 | 16 | signature = await efx.sign(nonce.toString(16)) 17 | } 18 | 19 | const data = {id, nonce, signature, protocol} 20 | 21 | return parse(post(url, {json: data})) 22 | } 23 | -------------------------------------------------------------------------------- /test/helpers/ecRecover.js: -------------------------------------------------------------------------------- 1 | const utils = require('ethereumjs-util') 2 | 3 | module.exports = (message, signature) => { 4 | const prefix = new Buffer('\x19Ethereum Signed Message:\n') 5 | 6 | message = new Buffer(message) 7 | 8 | const prefixedMsg = utils.sha3( 9 | Buffer.concat([prefix, new Buffer(String(message.length)), message]) 10 | ) 11 | 12 | const res = utils.fromRpcSig(signature) 13 | 14 | const pubKey = utils.ecrecover( 15 | prefixedMsg, 16 | res.v, res.r, res.s 17 | ) 18 | 19 | // return address from this pubKey 20 | return utils.bufferToHex(utils.pubToAddress(pubKey)) 21 | } 22 | -------------------------------------------------------------------------------- /src/lib/bfx/calculateVolume.js: -------------------------------------------------------------------------------- 1 | const getUSDPrice = require('./getUSDPrice') 2 | const _ = require('lodash') 3 | 4 | module.exports = async (symbol, amount, price) => { 5 | let baseSymbol, quoteSymbol 6 | if (_.includes(symbol, ':')) { 7 | [baseSymbol, quoteSymbol] = _.split(symbol, ':', 2) 8 | } else { 9 | baseSymbol = symbol.substr(0, symbol.length - 3) 10 | quoteSymbol = symbol.substr(-3) 11 | } 12 | 13 | const quoteSymbolPrice = await getUSDPrice(quoteSymbol) 14 | 15 | // long or short the volume will be the same 16 | amount = Math.abs(amount) 17 | 18 | return amount * price * quoteSymbolPrice 19 | } -------------------------------------------------------------------------------- /src/api/get_orders_hist.js: -------------------------------------------------------------------------------- 1 | const { post } = require('request-promise') 2 | const parse = require('../lib/parse/response/orders') 3 | 4 | module.exports = async (efx, symbol, id, nonce, signature) => { 5 | let url = efx.config.api + '/r/orders/' 6 | 7 | if (symbol) { 8 | url += 't' + symbol + '/hist' 9 | } else { 10 | url += 'hist' 11 | } 12 | 13 | const protocol = '0x' 14 | 15 | if (!nonce) { 16 | nonce = ((Date.now() / 1000) + 30) + '' 17 | 18 | signature = await efx.sign(nonce.toString(16)) 19 | } 20 | 21 | const data = {nonce, protocol, signature, id} 22 | 23 | return parse(post(url, {json: data})) 24 | } 25 | -------------------------------------------------------------------------------- /test/fixtures/contracts/deploy.js: -------------------------------------------------------------------------------- 1 | // build artifacts and deploy 2 | const Web3 = require('web3') 3 | 4 | module.exports = async (contract, name) => { 5 | const provider = new Web3.providers.HttpProvider('http://localhost:8545') 6 | 7 | const web3 = new Web3(provider) 8 | 9 | const WETH = new web3.eth.Contract(JSON.parse(contract.interface)) 10 | 11 | const accounts = await web3.eth.getAccounts() 12 | 13 | const deployed = await WETH.deploy({data: contract.bytecode}).send({ 14 | from: accounts[0], 15 | gas: 1500000, 16 | gasPrice: '30000000000000' 17 | }) 18 | 19 | // save in memory reference 20 | require('./deployed')[name] = deployed 21 | 22 | return deployed 23 | } 24 | -------------------------------------------------------------------------------- /test/eth.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | const { assert } = require('chai') 4 | const instance = require('./helpers/instance') 5 | const mockGetConf = require('./fixtures/nock/get_conf') 6 | 7 | let efx 8 | 9 | before(async () => { 10 | mockGetConf() 11 | efx = await instance() 12 | }) 13 | 14 | it('efx.eth.getNetwork()', async () => { 15 | const network = await efx.eth.getNetwork() 16 | 17 | assert.ok(network.id) 18 | assert.ok(network.name) 19 | 20 | console.log(`Network: ${network.name} id: ${network.id}`) 21 | }) 22 | 23 | it('efx.web3.eth.getBlockNumber()', async () => { 24 | const block = await efx.web3.eth.getBlockNumber() 25 | 26 | console.log('Current block: ', block) 27 | }) 28 | -------------------------------------------------------------------------------- /src/lib/bfx/getCandle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Requests candle from Bitfinex. 3 | */ 4 | const { get } = require('request-promise') 5 | 6 | // TODO: move address to either /r/get/conf or to ENV variables 7 | const BFX_API = 'https://api.deversifi.com/bfx/v2/' 8 | 9 | module.exports = async (token, timestamp, timeframe) => { 10 | const symbol = `t${token}USD` 11 | 12 | let url = '' 13 | url += `${BFX_API}candles/trade:${timeframe}:${symbol}` 14 | url += `/hist?limit=1&end=${timestamp}` 15 | 16 | candle = await get(url, { json: true }) 17 | 18 | // ~ candle data 19 | // const date = new Date(candle[0][0]) 20 | // console.log("date ->", date) 21 | 22 | candle = candle[0] 23 | 24 | return candle 25 | } 26 | -------------------------------------------------------------------------------- /test/fixtures/nock/feeRate.js: -------------------------------------------------------------------------------- 1 | const nock = require('nock') 2 | 3 | module.exports = () => { 4 | const httpResponse = { 5 | address: '0x65CEEE596B2aba52Acc09f7B6C81955C1DB86404', 6 | timestamp: 1568959208939, 7 | fees: { 8 | small: { threshold: 0, feeBps: 25 }, 9 | medium: { threshold: 500, feeBps: 21 }, 10 | large: { threshold: 2000, feeBps: 20 } 11 | }, 12 | signature: '0x52f18b47494e465aa4ed0f0f123fae4d40d3ac0862b61862e6cc8e5a119dbfe1061a4ee381092a10350185071f4829dbfd6c5f2e26df76dee0593cbe3cbd87321b' 13 | } 14 | 15 | nock('https://api.deversifi.com') 16 | .get('/api/v1/feeRate/' + '0x65CEEE596B2aba52Acc09f7B6C81955C1DB86404') 17 | .reply(200, httpResponse) 18 | } 19 | -------------------------------------------------------------------------------- /src/api/eth/send.js: -------------------------------------------------------------------------------- 1 | module.exports = async (efx, abi, address, action, args, value = 0) => { 2 | if (efx.config.send){ 3 | return efx.config.send(efx, abi, address, action, args, value) 4 | } 5 | 6 | const { web3 } = efx 7 | 8 | const contract = new web3.eth.Contract(abi, address) 9 | 10 | const method = contract.methods[action](...args) 11 | 12 | const estimatedGas = await method.estimateGas({ 13 | from: efx.get('account'), 14 | value: value 15 | }) 16 | 17 | let options = { 18 | from: efx.get('account'), 19 | value: value, 20 | gas: estimatedGas 21 | } 22 | 23 | if(efx.get('gasPrice')){ 24 | options.gasPrice = efx.get('gasPrice') 25 | } 26 | 27 | return method.send(options) 28 | } 29 | -------------------------------------------------------------------------------- /test/helpers/instance.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creats a client instance for testing 3 | **/ 4 | const HDWalletProvider = require("truffle-hdwallet-provider"); 5 | const Web3 = require('web3') 6 | 7 | const EFX = require('../../src/efx') 8 | 9 | module.exports = async () => { 10 | const infuraURL = process.env.INFURA_URL 11 | const privateKey = process.env.PRIVATE_KEY 12 | 13 | const provider = new HDWalletProvider(privateKey, infuraURL) 14 | 15 | const web3 = new Web3(provider) 16 | 17 | const accounts = await web3.eth.getAccounts() 18 | 19 | let config = {} 20 | 21 | // It's possible to overwrite the API address with the testnet address 22 | // for example like this: 23 | config.api = 'https://test.ethfinex.com/trustless/v1' 24 | return EFX(web3, config) 25 | } 26 | -------------------------------------------------------------------------------- /examples/node_unlock_eth.js: -------------------------------------------------------------------------------- 1 | // Please check ../test/index.js for all examples and tests 2 | 3 | const EFX = require('..') 4 | 5 | work = async () => { 6 | // assuming you have a provider running at: 7 | // http://localhost:8545 8 | efx = await EFX() 9 | 10 | // unlock wallet so we can sign transactions 11 | await efx.account.unlock('password') 12 | 13 | // check how much ETH is already locked 14 | let response 15 | 16 | console.log("") 17 | 18 | console.log("efx.contract.unlock('ETH', 0.01)") 19 | 20 | // unlock some 21 | response = await efx.contract.unlock('ETH', 0.01, 1000) 22 | 23 | if(response.status){ 24 | console.log( " - OK") 25 | } else { 26 | console.log( "Error:") 27 | console.log(response) 28 | } 29 | 30 | } 31 | 32 | work() 33 | -------------------------------------------------------------------------------- /examples/node_release_tokens.js: -------------------------------------------------------------------------------- 1 | // Please check ../test/index.js for all examples and tests 2 | 3 | const EFX = require('..') 4 | 5 | work = async () => { 6 | // assuming you have a provider running at: 7 | // http://localhost:8545 8 | efx = await EFX() 9 | 10 | // unlock wallet so we can sign transactions 11 | await efx.account.unlock('password') 12 | 13 | // check how much ETH is already locked 14 | let response 15 | 16 | console.log("") 17 | 18 | // submit an order to BUY 0.01 ETH for 1000 USD 19 | console.log("efx.releaseTokens('ETH')") 20 | 21 | response = await efx.releaseTokens('ETH') 22 | 23 | if(response.length){ 24 | console.log(` - OK: #${response[0]}`) 25 | } else { 26 | console.log("Error:") 27 | console.log(response) 28 | } 29 | 30 | } 31 | 32 | work() 33 | 34 | -------------------------------------------------------------------------------- /test/helpers/checksum_address.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Checksum function described here: 3 | * https://github.com/ethereum/web3.js/issues/1277 4 | * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md 5 | * 6 | * NOTE: not used yet, but perhaps better than using .toLowerCase() 7 | * as we are currently doing 8 | */ 9 | const createKeccakHash = require('keccak') 10 | 11 | module.exports = (address) => { 12 | address = address.toLowerCase().replace('0x', '') 13 | 14 | var hash = createKeccakHash('keccak256').update(address).digest('hex') 15 | var checksum = '0x' 16 | 17 | for (var i = 0; i < address.length; i++) { 18 | if (parseInt(hash[i], 16) >= 8) { 19 | checksum += address[i].toUpperCase() 20 | } else { 21 | checksum += address[i] 22 | } 23 | } 24 | 25 | return checksum 26 | } 27 | -------------------------------------------------------------------------------- /src/api/account/select.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Finds and returns an account based on given id. 3 | * 4 | * id - Can be an index of the array such as 0 or an address 5 | **/ 6 | module.exports = async (efx, id) => { 7 | // check for ethereum accounts and select a default one 8 | const accounts = await efx.web3.eth.getAccounts() 9 | 10 | if (typeof id === 'number') { 11 | if (!accounts[id]) { 12 | console.error('Error: You have no account at index:', +id) 13 | } 14 | 15 | // emit and store current account 16 | return efx.set('account', accounts[id]) 17 | } 18 | 19 | for (let index in accounts) { 20 | if (accounts[index] === id) { 21 | // emit and store current account 22 | return efx.set('account', accounts[index]) 23 | } 24 | } 25 | 26 | return efx.set('account', null) 27 | } 28 | -------------------------------------------------------------------------------- /examples/node_buy_eth.js: -------------------------------------------------------------------------------- 1 | // Please check ../test/index.js for all examples and tests 2 | 3 | const EFX = require('..') 4 | 5 | work = async () => { 6 | // assuming you have a provider running at: 7 | // http://localhost:8545 8 | efx = await EFX() 9 | 10 | // unlock wallet so we can sign transactions 11 | await efx.account.unlock('password') 12 | 13 | // check how much ETH is already locked 14 | let response 15 | 16 | console.log("") 17 | 18 | // submit an order to BUY 0.01 ETH for 1000 USD 19 | console.log("efx.submitOrder('ETHUSD', 0.01, 1000)") 20 | 21 | response = await efx.submitOrder('ETHUSD', 0.01, 1000) 22 | 23 | if(response.length){ 24 | console.log(` - Submitted Order: #${response[0]}`) 25 | } else { 26 | console.log("Error:") 27 | console.log(response) 28 | } 29 | 30 | } 31 | 32 | work() 33 | 34 | -------------------------------------------------------------------------------- /examples/node_sell_eth.js: -------------------------------------------------------------------------------- 1 | // Please check ../test/index.js for all examples and tests 2 | 3 | const EFX = require('..') 4 | 5 | work = async () => { 6 | // assuming you have a provider running at: 7 | // http://localhost:8545 8 | efx = await EFX() 9 | 10 | // unlock wallet so we can sign transactions 11 | await efx.account.unlock('password') 12 | 13 | // check how much ETH is already locked 14 | let response 15 | 16 | console.log("") 17 | 18 | // submit an order to SELL 0.01 ETH for 200 USD 19 | console.log("efx.submitOrder('ETHUSD', -0.02, 100)") 20 | 21 | response = await efx.submitOrder('ETHUSD', -0.02, 100) 22 | 23 | if(response.length){ 24 | console.log(` - Submitted Order: #${response[0]}`) 25 | } else { 26 | console.log("Error:") 27 | console.log(response) 28 | } 29 | 30 | } 31 | 32 | work() 33 | 34 | -------------------------------------------------------------------------------- /src/api/sign/order.js: -------------------------------------------------------------------------------- 1 | const { MetamaskSubprovider } = require ("@0x/subproviders") 2 | const {signatureUtils, orderHashUtils} = require('@0x/order-utils') 3 | 4 | module.exports = async (efx, order) => { 5 | const orderHash = orderHashUtils.getOrderHashHex(order) 6 | 7 | const provider = efx.isMetaMask 8 | ? new MetamaskSubprovider(efx.web3.currentProvider) 9 | : efx.web3.currentProvider 10 | 11 | const signature = await signatureUtils.ecSignHashAsync( 12 | provider, 13 | orderHash, 14 | efx.get('account') 15 | ) 16 | 17 | const signedOrder = Object.assign({}, order, { signature }) 18 | 19 | /** 20 | const isValid = signatureUtils.isValidSignatureAsync(orderHash, signedOrder, efx.get('account').toLowerCase()) 21 | 22 | console.log( "is_valid ->", isValid) 23 | **/ 24 | 25 | return signedOrder 26 | } 27 | -------------------------------------------------------------------------------- /src/api/get_fee_rate.js: -------------------------------------------------------------------------------- 1 | const { get } = require('request-promise') 2 | const _ = require('lodash') 3 | 4 | const calculateVolume = require('../lib/bfx/calculateVolume') 5 | 6 | /** 7 | * 8 | * Calculate feeRate based on deversifi feeRate rules 9 | */ 10 | module.exports = async (efx, symbol, amount, price) => { 11 | 12 | const volume = await calculateVolume(symbol, amount, price) 13 | 14 | // fetch freeRate from the api 15 | 16 | const account = efx.get('account') 17 | 18 | const url = efx.config['0x'].feeApiUrl + account 19 | 20 | let feeRates = await get(url, { json: true}) 21 | 22 | // filter all fees with threshold below volume 23 | let feeRate = _.filter(feeRates.fees, (item) => item.threshold <= volume) 24 | 25 | // pick the cheapest one 26 | feeRate = _.sortBy(feeRate, 'feeBps')[0] 27 | 28 | return { 29 | feeRate, 30 | feeRates 31 | } 32 | } -------------------------------------------------------------------------------- /examples/node_get_orders.js: -------------------------------------------------------------------------------- 1 | // Please check ../test/index.js for all examples and tests 2 | 3 | const EFX = require('..') 4 | 5 | work = async () => { 6 | // assuming you have a provider running at: 7 | // http://localhost:8545 8 | efx = await EFX() 9 | 10 | // unlock wallet so we can sign transactions 11 | await efx.account.unlock('password') 12 | 13 | // check how much ETH is already locked 14 | let response 15 | 16 | response = await efx.contract.locked('ETH') 17 | 18 | console.log("") 19 | 20 | console.log("efx.getOrders()") 21 | 22 | // Cancel all orders 23 | response = await efx.getOrders() 24 | 25 | console.log(`Found ${response.length} orders`) 26 | 27 | for(const order of response){ 28 | console.log("") 29 | 30 | console.log( "order #", order.id ) 31 | console.log( " amount:", order.amount) 32 | console.log( " price:", order.price) 33 | } 34 | 35 | } 36 | 37 | work() 38 | 39 | -------------------------------------------------------------------------------- /src/api/release_tokens.js: -------------------------------------------------------------------------------- 1 | const { post } = require('request-promise') 2 | const parse = require('../lib/parse/response/release_tokens') 3 | const reasons = require('../lib/error/reasons.js') 4 | 5 | module.exports = async (efx, token, nonce, signature) => { 6 | const url = efx.config.api + '/w/releaseTokens' 7 | 8 | const currency = efx.config['0x'].tokenRegistry[token] 9 | 10 | if (!nonce) { 11 | nonce = ((Date.now() / 1000) + 30) + '' 12 | 13 | signature = await efx.sign(nonce.toString(16)) 14 | } else { 15 | if(!signature){ 16 | // TODO: review error format 17 | return { 18 | error: 'ERR_RELEASE_TOKENS_NONCE_REQUIRES_SIGNATURE', 19 | reason: reasons.ERR_RELEASE_TOKENS_NONCE_REQUIRES_SIGNATURE.trim() 20 | } 21 | } 22 | } 23 | 24 | const protocol = '0x' 25 | 26 | const data = { 27 | nonce, 28 | signature, 29 | tokenAddress: currency.wrapperAddress, 30 | protocol 31 | } 32 | 33 | return parse(post(url, {json: data})) 34 | } 35 | -------------------------------------------------------------------------------- /examples/node_cancel_orders.js: -------------------------------------------------------------------------------- 1 | // Please check ../test/index.js for all examples and tests 2 | 3 | const EFX = require('..') 4 | 5 | work = async () => { 6 | // assuming you have a provider running at: 7 | // http://localhost:8545 8 | efx = await EFX() 9 | 10 | // unlock wallet so we can sign transactions 11 | await efx.account.unlock('password') 12 | 13 | // check how much ETH is already locked 14 | let response 15 | 16 | response = await efx.contract.locked('ETH') 17 | 18 | console.log("") 19 | 20 | console.log("efx.getOrders()") 21 | 22 | // Cancel all orders 23 | response = await efx.getOrders() 24 | 25 | console.log(`Found ${response.length} orders`) 26 | 27 | for(const order of response){ 28 | console.log("") 29 | 30 | console.log( "cancel order #", order.id ) 31 | 32 | try { 33 | response = await efx.cancelOrder(order.id) 34 | console.log(" - OK") 35 | } catch(e) { 36 | //console.log( 'e ->', e) 37 | console.log( "error:", e.response.body ) 38 | } 39 | } 40 | 41 | } 42 | 43 | work() 44 | 45 | -------------------------------------------------------------------------------- /examples/node_lock_eth_usd.js: -------------------------------------------------------------------------------- 1 | 2 | const EFX = require('..') 3 | 4 | work = async () => { 5 | // assuming you have a provider running at: 6 | // http://localhost:8545 7 | efx = await EFX() 8 | 9 | // unlock wallet so we can sign transactions 10 | await efx.account.unlock('password') 11 | 12 | // check how much ETH is already locked 13 | let response 14 | 15 | // lock some ETH 16 | 17 | //console.log("efx.contract.lock('ETH', 0.02, 10)") 18 | //response = await efx.contract.lock('ETH', 0.02, 10) 19 | //response = await efx.contract.approve('USD') 20 | response = await efx.contract.lock('ETH', 0.05, 10) 21 | 22 | if(response.status){ 23 | console.log( " - OK") 24 | } else { 25 | console.log( "Error:") 26 | console.log(response) 27 | } 28 | 29 | // lock some USD 30 | 31 | /** 32 | console.log("efx.contract.lock('USD', 100, 10)") 33 | response = await efx.contract.lock('USD', 100, 100) 34 | 35 | if(response.status){ 36 | console.log( " - OK") 37 | } else { 38 | console.log( "Error:") 39 | console.log(response) 40 | }**/ 41 | 42 | } 43 | 44 | work() 45 | 46 | -------------------------------------------------------------------------------- /src/api/contract/approve.js: -------------------------------------------------------------------------------- 1 | const reasons = require('../../lib/error/reasons') 2 | /** 3 | * Approves a token for locking 4 | * 5 | */ 6 | module.exports = async (efx, token) => { 7 | const currency = efx.config['0x'].tokenRegistry[token] 8 | 9 | // REVIEW: 2 ** 256 -1 should be the max value for "uint" 10 | const amount = ((2 ** 256) - 1).toString(16) 11 | 12 | const args = [ 13 | currency.wrapperAddress, // address _spender 14 | amount // uint amount 15 | ] 16 | 17 | // TODO: review error format 18 | if(token == 'USD' && (await efx.contract.isApproved(token) != 0)){ 19 | return { 20 | error: 'ERR_TRADING_ETHFX_CANT_APPROVE_USDT_TWICE', 21 | reason: reasons.ERR_TRADING_ETHFX_CANT_APPROVE_USDT_TWICE.trim() 22 | } 23 | } 24 | 25 | if(token == 'ETH'){ 26 | return { 27 | error: 'ERR_TRADING_ETHFX_APPROVE_ETH_NOT_REQUIRED', 28 | reason: reasons.ERR_TRADING_ETHFX_APPROVE_ETH_NOT_REQUIRED.trim() 29 | } 30 | } 31 | 32 | const action = 'approve' 33 | 34 | return efx.eth.send( 35 | efx.contract.abi.token, 36 | currency.tokenAddress, 37 | action, 38 | args 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /examples/node_get_fee_rate.js: -------------------------------------------------------------------------------- 1 | // check truffle-hdwallet-provider on github for more information 2 | const HDWalletProvider = require("truffle-hdwallet-provider"); 3 | const Web3 = require("Web3") 4 | 5 | // Please check ../test/index.js for all examples and tests 6 | const EFX = require('..') 7 | 8 | const privateKey = '' // Account's private key 9 | const infuraKey = '' // Your Infura API KEY 10 | const infuraURL = 'https://kovan.infura.io/v3/' + infuraKey 11 | 12 | work = async () => { 13 | 14 | const provider = new HDWalletProvider(privateKey, infuraURL) 15 | const web3 = new Web3(provider) 16 | 17 | const testConfig = { 18 | api: 'https://staging-api.deversifi.com/v1/trading' 19 | } 20 | 21 | efx = await EFX(web3, testConfig) 22 | 23 | console.log("") 24 | 25 | // submit an order to SELL 0.01 ETH for 300 USD 26 | console.log("getFeeRate('ETHUSD', 200, 200)") 27 | 28 | const response = await efx.getFeeRate('ETHUSD', 200, 200) 29 | 30 | if(response){ 31 | console.log(` - Fee Rates: #${JSON.stringify(response, null, 2)}`) 32 | } else { 33 | console.log("Error:") 34 | console.log(response) 35 | } 36 | 37 | } 38 | 39 | work() 40 | 41 | -------------------------------------------------------------------------------- /examples/node_sell_eth_infura.js: -------------------------------------------------------------------------------- 1 | // check truffle-hdwallet-provider on github for more information 2 | const HDWalletProvider = require("truffle-hdwallet-provider"); 3 | const Web3 = require("Web3") 4 | 5 | // Please check ../test/index.js for all examples and tests 6 | const EFX = require('..') 7 | 8 | const privateKey = '' // Account's private key 9 | const infuraKey = '' // Your Infura API KEY 10 | const infuraURL = 'https://kovan.infura.io/v3/' + infuraKey 11 | 12 | work = async () => { 13 | 14 | const provider = new HDWalletProvider(privateKey, infuraURL) 15 | const web3 = new Web3(provider) 16 | 17 | const testConfig = { 18 | api: 'https://staging-api.deversifi.com/v1/trading' 19 | } 20 | 21 | efx = await EFX(web3, testConfig) 22 | 23 | console.log("") 24 | 25 | // submit an order to SELL 0.01 ETH for 120 USD 26 | console.log("efx.submitOrder('ETHUSD', -0.1, 120)") 27 | 28 | const response = await efx.submitOrder('ETHUSD', -0.1, 120) 29 | 30 | if(response.length){ 31 | console.log(` - Submitted Order: #${response[0]}`) 32 | } else { 33 | console.log("Error:") 34 | console.log(response) 35 | } 36 | 37 | } 38 | 39 | work() 40 | 41 | -------------------------------------------------------------------------------- /test/helpers/compile.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fs = require('fs') 3 | 4 | // compiling contracts 5 | const solc = require('solc') 6 | 7 | const CONTRACTS_PATH = path.join(__dirname, '../contracts/') 8 | 9 | /** 10 | * Compile all .sol files in this folder and save them as .js 11 | */ 12 | 13 | const compile = async (filename) => { 14 | filename = filename.replace('.sol', '') 15 | 16 | const filePath = path.join(CONTRACTS_PATH, filename + '.sol') 17 | const source = fs.readFileSync(filePath, 'utf-8') 18 | 19 | // grabs the contract reference 20 | const compiled = solc.compile(source, 1) 21 | 22 | const contract = compiled.contracts[':' + filename] 23 | 24 | if (contract) { 25 | console.log(' OK!') 26 | } 27 | 28 | if (compiled.errors) { 29 | if (contract) { 30 | console.log('warnings:') 31 | } else { 32 | console.log('errors:') 33 | } 34 | 35 | console.log(compiled.errors) 36 | } 37 | 38 | if (!contract) return 39 | 40 | fs.writeFileSync(filePath + '.json', JSON.stringify(contract)) 41 | } 42 | 43 | const allFiles = fs.readdirSync(CONTRACTS_PATH) 44 | // grab all filename ending with .sol 45 | const files = allFiles.filter((file) => /\.sol$/.test(file)) 46 | 47 | for (var file of files) { 48 | compile(file) 49 | } 50 | -------------------------------------------------------------------------------- /src/api/submit_order.js: -------------------------------------------------------------------------------- 1 | const {post} = require('request-promise') 2 | const parse = require('../lib/parse/response/submit_order') 3 | 4 | module.exports = async (efx, symbol, amount, price, gid, cid, signedOrder, validFor, partner_id, fee_rate, dynamicFeeRate) => { 5 | if (!(symbol && amount && price)) { 6 | throw new Error('order, symbol, amount and price are required') 7 | } 8 | 9 | if(!fee_rate) { 10 | if(!dynamicFeeRate){ 11 | dynamicFeeRate = await efx.getFeeRate(symbol, amount, price) 12 | } 13 | 14 | fee_rate = dynamicFeeRate.feeRate.feeBps / 10000 15 | } 16 | 17 | //TODO: check if symbol is a valid symbol 18 | if(!signedOrder){ 19 | const order = efx.contract.createOrder(symbol, amount, price, validFor, fee_rate) 20 | 21 | signedOrder = await efx.sign.order(order) 22 | } 23 | 24 | const meta = signedOrder 25 | 26 | const type = 'EXCHANGE LIMIT' 27 | 28 | const protocol = '0x' 29 | 30 | symbol = 't' + symbol 31 | 32 | const data = { 33 | gid, 34 | cid, 35 | type, 36 | symbol, 37 | amount, 38 | price, 39 | meta, 40 | protocol, 41 | partner_id, 42 | fee_rate, 43 | dynamicFeeRate 44 | } 45 | 46 | const url = efx.config.api + '/w/on' 47 | 48 | return parse(post(url, {json: data})) 49 | } 50 | -------------------------------------------------------------------------------- /examples/node_balance_and_locked.js: -------------------------------------------------------------------------------- 1 | // Please check ../test/index.js for all examples and tests 2 | 3 | const EFX = require('..') 4 | 5 | work = async () => { 6 | // assuming you have a provider running at: 7 | // http://localhost:8545 8 | efx = await EFX() 9 | 10 | // unlock wallet so we can sign transactions 11 | await efx.account.unlock('password') 12 | 13 | // check how much ETH is already locked 14 | let response 15 | 16 | response = await efx.contract.locked('ETH') 17 | 18 | const lockedETH = Number(efx.web3.utils.fromWei(response)).toFixed(8) 19 | 20 | response = await efx.contract.locked('USD') 21 | 22 | const lockedUSD = Number(efx.web3.utils.fromWei(response)).toFixed(8) 23 | 24 | // check what's the ETH balance for this account 25 | response = await efx.account.balance() 26 | 27 | const balanceETH = Number(efx.web3.utils.fromWei(response)).toFixed(8) 28 | 29 | // check what's the USD balance for this account 30 | response = await efx.account.tokenBalance('USD') 31 | 32 | const balanceUSD = Number(response) / Math.pow(10, 6) 33 | 34 | console.log( `Your account: ${efx.get('account')}` ) 35 | console.log( ` - balance: ${balanceETH} ETH` ) 36 | console.log( ` - balance: ${balanceUSD} USD` ) 37 | console.log( ` - locked: ${lockedETH} ETH` ) 38 | console.log( ` - locked: ${lockedUSD} USD` ) 39 | 40 | } 41 | 42 | work() 43 | 44 | -------------------------------------------------------------------------------- /examples/node_spam_get_orders.js: -------------------------------------------------------------------------------- 1 | // Please check ../test/index.js for all examples and tests 2 | 3 | const EFX = require('..') 4 | 5 | const _ = require('lodash') 6 | 7 | let addresses = [] 8 | 9 | getOrders = async (efx, account) => { 10 | 11 | console.log("Getting order for account: ", account) 12 | 13 | let response 14 | 15 | try { 16 | await efx.account.select(account) 17 | 18 | response = await efx.getOrders() 19 | } catch(e){ 20 | console.log( "failed to get orders", e.message) 21 | } 22 | 23 | if(!response || response.error){ 24 | console.log(`Error getting orders for ${account}`) 25 | console.log(response.error) 26 | console.log() 27 | } 28 | 29 | setTimeout(() => {getOrders(efx,account), 500}) 30 | } 31 | 32 | work = async () => { 33 | // assuming you have a provider running at: 34 | // http://localhost:8545 35 | const efx = await EFX() 36 | 37 | const accounts = await efx.web3.eth.getAccounts() 38 | 39 | console.log( `using ${accounts.length} accounts` ) 40 | 41 | // unlock all accounts and triggering getOrders 42 | let account 43 | for( let index in accounts ) { 44 | account = accounts[index] 45 | 46 | await efx.account.select(account) 47 | 48 | await efx.account.unlock('password') 49 | 50 | setTimeout(() => getOrders(efx, accounts[index]), 100 * index) 51 | } 52 | 53 | } 54 | 55 | work() 56 | 57 | 58 | -------------------------------------------------------------------------------- /test/deploy.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | const {assert} = require('chai') 4 | 5 | const deploy = require('./contracts/deploy') 6 | const deployed = require('./contracts/deployed') 7 | 8 | it('Deploy WETH9_ contract', async () => { 9 | // you need to run the following command in order to generate this json: 10 | // npm run test:contracts:compile 11 | const json = require('./contracts/WrapperLockEth.sol') 12 | 13 | await deploy(json, 'WrapperLockEth') 14 | 15 | assert.ok(deployed.WETH9_) 16 | assert.ok(deployed.WETH9_.methods) 17 | assert.ok(deployed.WETH9_.options.address) 18 | }) 19 | 20 | it('Deploy WUSD9_ contract', async () => { 21 | // you need to run the following command in order to generate this json: 22 | // npm run test:contracts:compile 23 | const json = require('./contracts/WrapperLockUsd.sol') 24 | 25 | await deploy(json, 'WUSD9_') 26 | 27 | assert.ok(deployed.WETH9_) 28 | assert.ok(deployed.WETH9_.methods) 29 | assert.ok(deployed.WETH9_.options.address) 30 | }) 31 | 32 | it('Deploy ZRXToken contract', async () => { 33 | // you need to run the following command in order to generate this json: 34 | // npm run test:contracts:compile 35 | const json = require('./contracts/ZRXToken.sol.json') 36 | 37 | await deploy(json, 'ZRXToken') 38 | 39 | assert.ok(deployed.WETH9_) 40 | assert.ok(deployed.WETH9_.methods) 41 | assert.ok(deployed.WETH9_.options.address) 42 | }) 43 | -------------------------------------------------------------------------------- /test/account.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | const instance = require('./helpers/instance') 3 | const { assert } = require('chai') 4 | const mockGetConf = require('./fixtures/nock/get_conf') 5 | 6 | let efx 7 | 8 | before(async () => { 9 | mockGetConf() 10 | efx = await instance() 11 | }) 12 | 13 | it('efx.account.select(0) // first account is selected by default', () => { 14 | // you can select account using an address or an account index 15 | // it will return null if address isnt found on the list of accounts 16 | efx.account.select('0x97EBb3391B30F495CE8cB97857dB9B72D3e9DbCB') 17 | 18 | // you can also select it using an account index 19 | efx.account.select(0) 20 | 21 | // you can fetch and subscribe to an account at the same time: 22 | efx.on('account', (account) => { 23 | console.log('Your current account:', account) 24 | }) 25 | 26 | // or simply fetch it 27 | const selectedAccount = efx.get('account') 28 | }) 29 | 30 | it('await efx.account.balance() // return ETH balance', async () => { 31 | const response = await efx.account.balance() 32 | 33 | assert.notOk(isNaN(response)) 34 | 35 | console.log( 'eth balance ->', efx.web3.utils.fromWei(response) ) 36 | }) 37 | 38 | it("await efx.account.tokenBalance('USD') // return USD balance", async () => { 39 | const token = 'USD' 40 | 41 | const response = await efx.account.tokenBalance(token) 42 | 43 | assert.notOk(isNaN(response)) 44 | }) -------------------------------------------------------------------------------- /src/api/contract/lock.js: -------------------------------------------------------------------------------- 1 | const BigNumber = require('bignumber.js'); 2 | /** 3 | * Execute 'deposit' method on locker address 4 | * 5 | * duration - duration the tokens will be locked, in hours 6 | */ 7 | module.exports = async (efx, token, amount, duration) => { 8 | const currency = efx.config['0x'].tokenRegistry[token] 9 | 10 | // value we sending to the lockerContract 11 | const value = (new BigNumber(10)).pow(currency.decimals).times(amount).integerValue(BigNumber.ROUND_FLOOR).toString() 12 | 13 | const action = 'deposit' 14 | 15 | // In order to lock tokens we call deposit with value and forTime 16 | const args = [ 17 | value, // uint256 value 18 | duration // uint256 forTime 19 | ] 20 | 21 | // In order to lock ETH we simply send ETH to the lockerAddress 22 | if (token === 'ETH') { 23 | return efx.eth.send( 24 | efx.contract.abi.locker, 25 | currency.wrapperAddress, 26 | action, 27 | args, 28 | value // send ETH to the contract 29 | ) 30 | } 31 | 32 | try { 33 | return efx.eth.send( 34 | efx.contract.abi.locker, 35 | currency.wrapperAddress, 36 | action, 37 | args 38 | ) 39 | } catch(e){ 40 | if(!efx.contract.isApproved(token)){ 41 | return { 42 | error: 'ERR_CORE_ETHFX_NEEDS_APPROVAL', 43 | reason: reasons.ERR_CORE_ETHFX_NEEDS_APPROVAL.trim() 44 | } 45 | } else { 46 | throw(e) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/api/contract/unlock.js: -------------------------------------------------------------------------------- 1 | const BigNumber = require('bignumber.js'); 2 | /** 3 | * Call unlock method on wrapper contract 4 | */ 5 | module.exports = async (efx, token, amount, nonce, signature) => { 6 | const currency = efx.config['0x'].tokenRegistry[token] 7 | 8 | // value we asking to unlock 9 | const value = (new BigNumber(10)).pow(currency.decimals).times(amount).integerValue(BigNumber.ROUND_FLOOR).toString() 10 | 11 | const action = 'withdraw' 12 | 13 | // get timestamp for depositLock 14 | const depositLock = await efx.contract.depositLock(token) 15 | 16 | // arguments for the contract call 17 | let args = [value] 18 | 19 | // no need to call releaseTokens 20 | if( Date.now() / 1000 > depositLock ) { 21 | args = args.concat([0, '0x0000000000000000000000000000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000000000000000000000000000', 0]) 22 | } 23 | 24 | // we need to call releaseTokens to fetch a signed permission to unlock 25 | if( Date.now() / 1000 < depositLock ) { 26 | const response = await efx.releaseTokens(token, nonce, signature) 27 | 28 | if(response.error) return response 29 | 30 | const sig = response.releaseSignature 31 | 32 | // push values into arguments array 33 | args = args.concat([sig.v, sig.r, sig.s, response.unlockUntil]) 34 | } 35 | 36 | return efx.eth.send( 37 | efx.contract.abi.locker, 38 | currency.wrapperAddress, 39 | action, 40 | args 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /test/fixtures/nock/get_conf.js: -------------------------------------------------------------------------------- 1 | const nock = require('nock') 2 | 3 | module.exports = () => { 4 | const apiResponse = { 5 | "0x":{ 6 | "protocol":"0x", 7 | "minOrderTime":300, 8 | "tokenRegistry":{ 9 | "ETH":{ 10 | "decimals":18, 11 | "wrapperAddress":"0xaa7427d8f17d87a28f5e1ba3adbb270badbe1011", 12 | "minOrderSize":0.1 13 | }, 14 | "USD":{ 15 | "decimals":6, 16 | "wrapperAddress":"0x1a9b2d827f26b7d7c18fec4c1b27c1e8deeba26e", 17 | "tokenAddress":"0xdac17f958d2ee523a2206206994597c13d831ec7", 18 | "minOrderSize":25, 19 | "settleSpread":-0.026 20 | }, 21 | "ZRX":{ 22 | "decimals":18, 23 | "wrapperAddress":"0xcf67d7a481ceeca0a77f658991a00366fed558f7", 24 | "tokenAddress":"0xe41d2489571d322189246dafa5ebde1f4699f498", 25 | "minOrderSize":40 26 | }, 27 | }, 28 | "ethfinexAddress":"0x61b9898c9b60a159fc91ae8026563cd226b7a0c1", // gets fee paid in 29 | "feeApiUrl": "https://staging-api.deversifi.com/v1/pub/feeRate/", 30 | "exchangeAddress":"0xdcdb42c9a256690bd153a7b409751adfc8dd5851", // actual exchange contract address 31 | "exchangeSymbols":[ 32 | "tETHUSD", 33 | "tZRXUSD", 34 | "tZRXETH" 35 | ] 36 | } 37 | } 38 | 39 | nock('https://test.ethfinex.com:443', {"encodedQueryParams":true}) 40 | .post('/trustless/v1/r/get/conf', {}) 41 | .reply(200, apiResponse) 42 | } 43 | -------------------------------------------------------------------------------- /src/efx.js: -------------------------------------------------------------------------------- 1 | const bind = require('./lib/bind_api') 2 | const defaultConfig = require('./config') 3 | const Web3 = require('web3') 4 | const aware = require('aware') 5 | const BigNumber = require('bignumber.js') 6 | BigNumber.config({ EXPONENTIAL_AT: 1e+9 }) 7 | 8 | /** 9 | * web3 - web3 object 10 | * config - config to be merged with defaultConfig 11 | */ 12 | module.exports = async (web3, userConfig = {}) => { 13 | // binds all ./api methods into a fresh object, similar to creating an instance 14 | let efx = bind() 15 | 16 | // adds key-value storage and event emitting capabilities 17 | aware(efx) 18 | 19 | // merge user config with default config 20 | // needed for the efx.getConfig method 21 | efx.config = Object.assign({}, defaultConfig, userConfig) 22 | 23 | // ethfinex exchange config 24 | const exchangeConf = await efx.getConfig() 25 | 26 | //user config has priority 27 | efx.config = Object.assign({}, defaultConfig, exchangeConf, userConfig ) 28 | 29 | // working towards being as compatible as possible 30 | efx.isBrowser = typeof window !== 'undefined' 31 | 32 | efx.isMetaMask = false 33 | 34 | if (efx.isBrowser && window.web3) { 35 | efx.isMetaMask = window.web3.currentProvider.isMetaMask 36 | } 37 | 38 | // If no web3 is provided we will fallback to: 39 | // - window.web3.currentProvider object i.e. user is using MetaMask 40 | // - http://localhost:8545 41 | if (!web3) { 42 | // sudo make-me browser friendly 43 | if (efx.isBrowser && window.web3) { 44 | web3 = new Web3(window.web3.currentProvider) 45 | } else { 46 | web3 = new Web3(efx.config.defaultProvider) 47 | } 48 | } 49 | 50 | // save web3 instance int it 51 | efx.web3 = web3 52 | 53 | // REVIEW: should we actually use web3.eth.defaultAccount ? 54 | // see: https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md#raising_hand-account-list-reflects-user-preference 55 | await efx.account.select(efx.config.account) 56 | 57 | if (!efx.get('account')) { 58 | console.warn('Please specify a valid account or account index') 59 | } 60 | 61 | return efx 62 | } 63 | -------------------------------------------------------------------------------- /examples/node_spam_lock_eth_and_usd.js: -------------------------------------------------------------------------------- 1 | // Please check ../test/index.js for all examples and tests 2 | 3 | const EFX = require('..') 4 | 5 | work = async () => { 6 | try { 7 | 8 | // assuming you have a provider running at: 9 | // http://localhost:8545 10 | efx = await EFX() 11 | 12 | // check how much ETH is already locked 13 | let response 14 | 15 | const accounts = await efx.web3.eth.getAccounts() 16 | 17 | for(let account of accounts){ 18 | 19 | await efx.account.select(account) 20 | 21 | // unlock wallet so we can sign transactions 22 | await efx.account.unlock('password') 23 | 24 | response = await efx.contract.locked('ETH') 25 | 26 | const lockedETH = Number(efx.web3.utils.fromWei(response)) 27 | 28 | response = await efx.contract.locked('USD') 29 | 30 | const lockedUSD = Number(efx.web3.utils.fromWei(response)) 31 | 32 | // check what's the ETH balance for this account 33 | response = await efx.account.balance() 34 | 35 | const balanceETH = Number(efx.web3.utils.fromWei(response)) 36 | 37 | // check what's the USD balance for this account 38 | response = await efx.account.tokenBalance('USD') 39 | 40 | const balanceUSD = Number(response) / Math.pow(10, 6) 41 | 42 | // lock everything expect 0.1 ETH 43 | if(balanceETH > 0.1) { 44 | 45 | console.log(`efx.contract.lock('ETH', ${balanceETH - 0.1}, 10)`) 46 | 47 | response = await efx.contract.lock('ETH', balanceETH - 0.1, 10) 48 | } 49 | 50 | console.log( "balanceUSD ->", balanceUSD ) 51 | 52 | // lock all USE 53 | if(balanceUSD){ 54 | console.log(`efx.contract.lock('USD', ${balanceUSD}, 10)`) 55 | 56 | response = await efx.contract.approve('USD') 57 | response = await efx.contract.lock('USD', balanceUSD, 10) 58 | } 59 | 60 | console.log( `Account: ${efx.get('account')}` ) 61 | console.log( ` - balance: ${balanceETH} ETH` ) 62 | console.log( ` - balance: ${balanceUSD} USD` ) 63 | console.log( ` - locked: ${lockedETH} ETH` ) 64 | console.log( ` - locked: ${lockedUSD} USD` ) 65 | 66 | console.log('') 67 | } 68 | } catch(e) { 69 | console.log( 'e ->', e.stack) 70 | } 71 | 72 | } 73 | 74 | work() 75 | 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "efx-api-node", 3 | "version": "2.0.3", 4 | "main": "src/efx.js", 5 | "contributors": [ 6 | "Henrique Matias ", 7 | "Will Harborne " 8 | ], 9 | "standard": { 10 | "ignore": [ 11 | "/examples", 12 | "/dist" 13 | ] 14 | }, 15 | "scripts": { 16 | "lint": "standard", 17 | "lint:fix": "standard --fix", 18 | "test": "env-cmd mocha ./test/index.js --timeout 1200000000", 19 | "test:watch": "env-cmd mocha ./test/index.js --watch ./ --timeout 1200000000", 20 | "test:web": "mochify ./test/index.js --timeout 1200000000", 21 | "test:web:watch": "mochify ./test/index.js --watch --timeout 1200000000", 22 | "build": "npm run build:web:browserify; npm run build:web:minify; npm run build:web:hash", 23 | "build:web:browserify": "del ./dist/*.js; browserify ./src/efx.js --standalone EFX -o ./dist/efx.js", 24 | "build:web:minify": "node-minify --compressor uglify-es --input './dist/efx.js' --output './dist/efx.min.js'", 25 | "build:web:hash": "hash-filename ./dist/efx*.js;del ./dist/{efx.js,efx.min.js}", 26 | "build:web:run": "browserify --debug ./src/efx.js --standalone EFX | browser-run --port 2222" 27 | }, 28 | "engines": { 29 | "node": "^10.16.0", 30 | "yarn": "^1.17.0" 31 | }, 32 | "keywords": [ 33 | "deversifi", 34 | "ethfinex", 35 | "ethereum" 36 | ], 37 | "homepage": "https://github.com/ethfinex/efx-api-node", 38 | "license": "MIT", 39 | "dependencies": { 40 | "@0x/order-utils": "^7.0.2", 41 | "@0x/subproviders": "^4.0.4", 42 | "aware": "^0.3.1", 43 | "bignumber.js": "^7.2.1", 44 | "bluebird": "^3.5.1", 45 | "ethereumjs-util": "^5.2.0", 46 | "lodash": "^4.17.10", 47 | "request": "^2.87.0", 48 | "request-promise": "^4.2.2", 49 | "web3": "1.0.0-beta.33" 50 | }, 51 | "devDependencies": { 52 | "del": "^3.0.0", 53 | "truffle-hdwallet-provider": "^1.0.17", 54 | "browser-run": "^5.0.0", 55 | "browserify": "^16.2.2", 56 | "chai": "^4.1.2", 57 | "del-cli": "^1.1.0", 58 | "env-cmd": "^10.0.1", 59 | "ganache-cli": "^6.1.4", 60 | "hash-filename": "^1.0.4", 61 | "keccak": "^1.4.0", 62 | "mocha": "^5.1.1", 63 | "mochify": "^5.8.0", 64 | "nock": "^9.4.1", 65 | "node-minify": "^3.2.0", 66 | "solc": "^0.4.24", 67 | "standard": "^11.0.1" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | const instance = require('./helpers/instance') 4 | const { assert } = require('chai') 5 | const nock = require('nock') 6 | const mockGetConf = require('./fixtures/nock/get_conf') 7 | 8 | // TODO: use nockBack and record fixtures to disk. 9 | // leaving this code here as reference 10 | // 11 | // const nockBack = require('nock').back 12 | // nockBack.setMode('record'); 13 | // nockBack.fixtures = __dirname + '/fixtures/nock'; 14 | 15 | describe('~ efx-api-node', async () => { 16 | // sometimes nock gets stuck between test:watch 17 | nock.cleanAll() 18 | 19 | it('efx = await EFX(web3) // create an instance without throwing', async () => { 20 | 21 | //nock.recorder.rec() 22 | 23 | mockGetConf() 24 | 25 | const efx = await instance() 26 | 27 | //nock.restore() 28 | 29 | assert.ok(efx.config['0x']) 30 | assert.ok(efx.config['0x'].exchangeAddress) 31 | assert.ok(efx.config['0x'].ethfinexAddress) 32 | assert.ok(efx.config['0x'].exchangeSymbols) 33 | assert.ok(efx.config['0x'].tokenRegistry) 34 | 35 | assert.ok(efx.config['0x'].tokenRegistry.ETH) 36 | assert.equal(efx.config['0x'].tokenRegistry.ETH.decimals, 18) 37 | 38 | assert.ok(efx.config['0x'].tokenRegistry.ETH.wrapperAddress) 39 | 40 | assert.ok(efx.config['0x'].tokenRegistry.USD) 41 | assert.ok(efx.config['0x'].tokenRegistry.USD.wrapperAddress) 42 | //assert.ok(result['0x'].tokenRegistry.USDwrapperAddress) 43 | 44 | }) 45 | 46 | // TODO: deploy contracts to local granache or testnet in order to test 47 | // some contract methods 48 | describe('Deploy contracts to test:rpc', () => { 49 | // require('./deploy') 50 | }) 51 | 52 | describe('Account', () => { 53 | // require('./account') 54 | }) 55 | 56 | describe('Signing', () => { 57 | // require('./signing') 58 | }) 59 | 60 | describe('Blockchain API', () => { 61 | // comment the line below if you want to skip blockchain tests 62 | // you need a ropsten node with some ETH / ZRX in order to test 63 | // those. FIXME: need contracts deployed during test 64 | //require('./blockchain-api') 65 | }) 66 | 67 | describe('HTTP API', () => { 68 | try { 69 | require('./http-api') 70 | } catch(e){ 71 | 72 | console.log("e ->", e) 73 | } 74 | }) 75 | 76 | return 77 | 78 | describe('ETH calls', () => { 79 | require('./eth.js') 80 | }) 81 | }) 82 | -------------------------------------------------------------------------------- /examples/node.js: -------------------------------------------------------------------------------- 1 | // Please check ../test/index.js for all examples and tests 2 | 3 | const EFX = require('..') 4 | 5 | work = async () => { 6 | // assuming you have a provider running at: 7 | // http://localhost:8545 8 | efx = await EFX() 9 | 10 | // unlock wallet so we can sign transactions 11 | await efx.account.unlock('password') 12 | 13 | // check how much ETH is already locked 14 | let response 15 | 16 | response = await efx.contract.locked('ETH') 17 | 18 | const locked = Number(efx.web3.utils.fromWei(response)).toFixed(8) 19 | 20 | response = await efx.account.balance('ETH') 21 | 22 | const balance = Number(efx.web3.utils.fromWei(response)).toFixed(8) 23 | 24 | console.log( `Your account: ${efx.get('account')}` ) 25 | console.log( ` - balance: ${balance} ETH` ) 26 | console.log( ` - locked: ${locked} ETH` ) 27 | 28 | console.log("") 29 | 30 | console.log("efx.contract.lock('ETH', 0.02, 1)") 31 | // lock some more 32 | response = await efx.contract.lock('ETH', 0.02, 1) 33 | 34 | if(response.status){ 35 | console.log( " - OK") 36 | } else { 37 | console.log( "Error:") 38 | console.log(response) 39 | } 40 | 41 | /** 42 | console.log("") 43 | 44 | console.log("efx.submitOrder('ETH', -0.01, 1000)") 45 | 46 | // submit an order to sell ETH for 1000 USD 47 | response = await efx.submitOrder('ETHUSD', -0.01, 1000) 48 | 49 | if(response.length){ 50 | console.log(` - Submitted Order #: ${response[0]}`) 51 | } else { 52 | console.log("Error:") 53 | console.log(response) 54 | } 55 | **/ 56 | 57 | console.log("") 58 | 59 | console.log("efx.getOrders('ETHUSD')") 60 | 61 | // Cancel all orders 62 | response = await efx.getOrders('ETHUSD') 63 | 64 | console.log(`Found ${response.length} orders`) 65 | 66 | for(const order of response){ 67 | console.log("") 68 | 69 | console.log(`efx.cancelOrder(${order.id})`) 70 | 71 | try { 72 | response = await efx.cancelOrder(order.id) 73 | console.log(" - OK") 74 | } catch(e) { 75 | //console.log( 'e ->', e) 76 | console.log( "error:", e.response.body ) 77 | } 78 | } 79 | 80 | console.log("") 81 | 82 | console.log("efx.contract.unlock('ETH', 0.01)") 83 | 84 | // unlock some 85 | response = await efx.contract.unlock('ETH', 0.01, 1000) 86 | 87 | if(response.status){ 88 | console.log( " - OK") 89 | } else { 90 | console.log( "Error:") 91 | console.log(response) 92 | } 93 | 94 | } 95 | 96 | work() 97 | 98 | -------------------------------------------------------------------------------- /test/signing.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | const { assert } = require('chai') 4 | 5 | const instance = require('./helpers/instance') 6 | const ecRecover = require('./helpers/ecRecover') 7 | const mockGetConf = require('./fixtures/nock/get_conf') 8 | 9 | let efx 10 | 11 | before(async () => { 12 | mockGetConf() 13 | 14 | efx = await instance() 15 | }) 16 | 17 | it('efx.sign(toSign) // sign arbitrary objects', async () => { 18 | // when signing hex values we should remove the 0x 19 | const message = '0xa4d9a634348b09f23a5bbd3568f8b12b91ff499c' 20 | 21 | const signature = await efx.sign(message.slice(2)) 22 | 23 | const recovered = ecRecover(message.slice(2), signature) 24 | 25 | assert.equal(efx.get('account').toLowerCase(), recovered.toLowerCase()) 26 | }) 27 | 28 | it('create and sign a buy order', async () => { 29 | const symbol = 'ETHUSD' 30 | const amount = 0.8 31 | const price = 274 32 | 33 | const order = efx.contract.createOrder(symbol, amount, price) 34 | 35 | const signed = await efx.sign.order(order) 36 | 37 | //assert.notEqual(signed, order) 38 | 39 | const sellAmount = amount * price 40 | const makerAmount = efx.web3.utils.toBN( 41 | Math.trunc(10 ** efx.config['0x'].tokenRegistry.USD.decimals * sellAmount) 42 | ).toString(10) 43 | 44 | // assert.equal(signed.makerTokenAddress, efx.config['0x'].tokenRegistry.USD.wrapperAddress) 45 | // assert.equal(signed.makerTokenAmount, makerAmount) 46 | 47 | // const buyAmount = amount 48 | // const takerAmount = efx.web3.utils.toBN( 49 | // Math.trunc(10 ** efx.config['0x'].tokenRegistry.ETH.decimals * buyAmount) 50 | // ).toString(10) 51 | 52 | // assert.equal(signed.takerTokenAddress, efx.config['0x'].tokenRegistry.ETH.wrapperAddress) 53 | // assert.equal(signed.takerTokenAmount, takerAmount) 54 | }) 55 | 56 | it('create and sign a sell order', async () => { 57 | const symbol = 'ETHUSD' 58 | const amount = -1.5 59 | const price = 300 60 | 61 | const order = efx.contract.createOrder(symbol, amount, price) 62 | 63 | const signed = await efx.sign.order(order) 64 | 65 | // TODO: update tests to correct values 66 | 67 | // const sellAmount = Math.abs(amount) 68 | // const makerAmount = efx.web3.utils.toBN( 69 | // Math.trunc(10 ** efx.config['0x'].tokenRegistry.ETH.decimals * sellAmount) 70 | // ).toString(10) 71 | 72 | // assert.equal(signed.makerTokenAddress, efx.config['0x'].tokenRegistry.ETH.wrapperAddress) 73 | // assert.equal(signed.makerTokenAmount, makerAmount) 74 | 75 | // const buyAmount = Math.abs(amount * price) 76 | // const takerAmount = efx.web3.utils.toBN( 77 | // Math.trunc(10 **efx.config['0x'].tokenRegistry.USD.decimals * buyAmount) 78 | // ).toString(10) 79 | 80 | // assert.equal(signed.takerTokenAddress, efx.config['0x'].tokenRegistry.USD.wrapperAddress) 81 | // assert.equal(signed.takerTokenAmount, takerAmount) 82 | }) 83 | -------------------------------------------------------------------------------- /src/lib/bind_api.js: -------------------------------------------------------------------------------- 1 | /** 2 | * - Creates an efx instance 3 | * - Load all functions from the ./api folder into this instance 4 | * - Binds the functions so they will always receive efx as first argument 5 | * 6 | * This way we get a regular looking API on top of functional code 7 | */ 8 | const _ = require('lodash') 9 | 10 | module.exports = () => { 11 | const efx = {} 12 | 13 | // returns a function that will call api functions prepending efx 14 | // as first argument 15 | const compose = (funk) => { 16 | return _.partial(funk, efx) 17 | } 18 | 19 | // efx.account functions 20 | efx.account = { 21 | balance: compose(require('../api/account/balance')), 22 | tokenBalance: compose(require('../api/account/token_balance')), 23 | select: compose(require('../api/account/select')), 24 | unlock: compose(require('../api/account/unlock')) 25 | } 26 | 27 | // efx.contract functions 28 | efx.contract = { 29 | approve: compose(require('../api/contract/approve')), 30 | isApproved: compose(require('../api/contract/is_approved')), 31 | depositLock: compose(require('../api/contract/deposit_lock')), 32 | lock: compose(require('../api/contract/lock')), 33 | locked: compose(require('../api/contract/locked')), 34 | unlock: compose(require('../api/contract/unlock')), 35 | createOrder: compose(require('../api/contract/create_order')), 36 | createOrderV2: compose(require('../api/contract/create_order')), 37 | abi: { 38 | locker: require('../api/contract/abi/locker.abi.js'), 39 | token: require('../api/contract/abi/token.abi.js') 40 | } 41 | } 42 | 43 | // efx.eth functions 44 | efx.eth = { 45 | call: compose(require('../api/eth/call')), 46 | send: compose(require('../api/eth/send')), 47 | getNetwork: compose(require('../api/eth/get_network')) 48 | } 49 | 50 | // efx.sign functions 51 | efx.sign = compose(require('../api/sign/sign')) 52 | // hack to get a nice method signature 53 | efx.sign.order = compose(require('../api/sign/order')) 54 | efx.sign.orderV2 = compose(require('../api/sign/order')) 55 | efx.sign.cancelOrder = compose(require('../api/sign/cancel_order')) 56 | efx.sign.request = compose(require('../api/sign/request')) 57 | 58 | // efx main functions 59 | efx.getConfig = compose(require('../api/get_config')) 60 | efx.getFeeRate = compose(require('../api/get_fee_rate')) 61 | efx.cancelOrder = compose(require('../api/cancel_order')) 62 | efx.getOrder = compose(require('../api/get_order')) 63 | efx.getOrders = compose(require('../api/get_orders')) 64 | efx.getOrdersHist = compose(require('../api/get_orders_hist')) 65 | efx.releaseTokens = compose(require('../api/release_tokens')) 66 | efx.submitOrder = compose(require('../api/submit_order')) 67 | efx.submitOrderV2 = compose(require('../api/submit_order')) 68 | efx.submitBuyOrder = compose(require('../api/submit_buy_order')) 69 | efx.submitSellOrder = compose(require('../api/submit_sell_order')) 70 | 71 | return efx 72 | } 73 | -------------------------------------------------------------------------------- /test/blockchain-api.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | const { assert } = require('chai') 4 | 5 | const instance = require('./instance') 6 | const mockGetConf = require('./fixtures/nock/get_conf') 7 | 8 | let efx 9 | 10 | before(async () => { 11 | mockGetConf() 12 | 13 | efx = await instance() 14 | }) 15 | 16 | 17 | return 18 | 19 | it("efx.contract.locked('ETH') // returns amount of ETH locked", async () => { 20 | const token = 'ETH' 21 | 22 | const response = await efx.contract.locked(token) 23 | 24 | console.log( 'locked eth ->', efx.web3.utils.fromWei(response) ) 25 | 26 | assert.notOk(isNaN(response)) 27 | }) 28 | 29 | it("efx.contract.depositLock('ETH') // returns depositLock value", async () => { 30 | const token = 'ETH' 31 | 32 | const response = await efx.contract.depositLock(token) 33 | 34 | assert.notOk(isNaN(response)) 35 | }) 36 | 37 | it("efx.contract.isApproved('ZRX') // returns allowance", async () => { 38 | const token = 'ZRX' 39 | 40 | const response = await efx.contract.isApproved(token) 41 | 42 | assert.notOk(isNaN(response)) 43 | }) 44 | 45 | it("efx.contract.approve('ZRX') // should yield Approval event", async () => { 46 | const token = 'ZRX' 47 | 48 | const response = await efx.contract.approve(token) 49 | 50 | assert.ok(response.events.Approval) 51 | // TODO: - validate receipt fields 52 | }) 53 | 54 | it("efx.contract.lock('ETH', 0.1, duration) // lock 0.0001 ETH", async () => { 55 | const token = 'ETH' 56 | const amount = 0.2 57 | const duration = 1 58 | 59 | // const response = await efx.contract.lock(token, amount, duration) 60 | const response = await efx.contract.lock(token, amount, duration) 61 | 62 | assert.equal(response.status, true) 63 | }) 64 | 65 | it("efx.contract.lock('ZRX', 0.0001, duration) // lock 0.0001 ZRX", async () => { 66 | const token = 'ZRX' 67 | const amount = 0.0001 68 | const duration = 250000 69 | 70 | // const response = await efx.contract.lock(token, amount, duration) 71 | const response = await efx.contract.lock(token, amount, duration) 72 | 73 | assert.ok(response.events.Transfer) 74 | 75 | // TODO: - validate receipt fields 76 | }) 77 | 78 | it("efx.contract.unlock('ETH', 0.01) // unlock 0.0001 ETH", async () => { 79 | const token = 'ETH' 80 | const amount = 0.1 81 | 82 | // const response = await efx.contract.lock(token, amount, duration) 83 | const response = await efx.contract.unlock(token, amount) 84 | 85 | console.log( 'unlock response ->', response ) 86 | 87 | assert.equal(response.status, true) 88 | // TODO: - validate receipt fields 89 | }) 90 | 91 | it("efx.contract.unlock('ETH', 100) // fail to unlock 100 ETH", async () => { 92 | const token = 'ETH' 93 | const amount = 10000 94 | 95 | try { 96 | await efx.contract.unlock(token, amount) 97 | } catch (error) { 98 | // parity tests yielded this error 99 | let test = /Transaction ran out of gas/.test(error.message) 100 | 101 | // geth error 102 | test = test || /gas required exceeds allowance/.test(error.message) 103 | 104 | assert.ok(test) 105 | } 106 | }) 107 | -------------------------------------------------------------------------------- /src/api/contract/create_order.js: -------------------------------------------------------------------------------- 1 | const {assetDataUtils, generatePseudoRandomSalt} = require('@0x/order-utils') 2 | const BigNumber = require('bignumber.js') 3 | const _ = require('lodash') 4 | 5 | module.exports = (efx, symbol, amount, price, validFor, fee_rate = 0.0025) => { 6 | const { web3, config } = efx 7 | 8 | if(fee_rate < 0 || fee_rate > 0.05 || isNaN(fee_rate)){ 9 | // use 0.0025 for 0.25% fee 10 | // use 0.01 for 1% fee 11 | // use 0.05 for 5% fee 12 | throw('fee_rate should be between 0 and 0.05') 13 | } 14 | 15 | // symbols are always 3 letters 16 | let baseSymbol, quoteSymbol 17 | if (_.includes(symbol, ':')) { 18 | [baseSymbol, quoteSymbol] = _.split(symbol, ':', 2) 19 | } else { 20 | baseSymbol = symbol.substr(0, symbol.length - 3) 21 | quoteSymbol = symbol.substr(-3) 22 | } 23 | 24 | const buySymbol = amount > 0 ? baseSymbol : quoteSymbol 25 | const sellSymbol = amount > 0 ? quoteSymbol : baseSymbol 26 | 27 | const sellCurrency = efx.config['0x'].tokenRegistry[sellSymbol] 28 | const buyCurrency = efx.config['0x'].tokenRegistry[buySymbol] 29 | 30 | let buyAmount, sellAmount 31 | 32 | if (amount > 0) { 33 | buyAmount = (new BigNumber(10)) 34 | .pow(buyCurrency.decimals) 35 | .times(amount) 36 | .times(1 + (buyCurrency.settleSpread || 0)) 37 | .times(1 - fee_rate) 38 | .integerValue(BigNumber.ROUND_FLOOR) 39 | 40 | sellAmount = (new BigNumber(10)) 41 | .pow(sellCurrency.decimals) 42 | .times(amount) 43 | .times(price) 44 | .times(1 + (sellCurrency.settleSpread || 0)) 45 | .integerValue(BigNumber.ROUND_FLOOR) 46 | 47 | // console.log( "Buying " + amount + ' ' + buySymbol + " for: " + price + ' ' + sellSymbol ) 48 | } 49 | 50 | if (amount < 0) { 51 | buyAmount = (new BigNumber(10)) 52 | .pow(buyCurrency.decimals) 53 | .times(amount) 54 | .times(price) 55 | .abs() 56 | .times(1 + (buyCurrency.settleSpread || 0)) 57 | .times(1 - fee_rate) 58 | .integerValue(BigNumber.ROUND_FLOOR) 59 | 60 | sellAmount = (new BigNumber(10)) 61 | .pow(sellCurrency.decimals) 62 | .times(amount) 63 | .abs() 64 | .times(1 + (sellCurrency.settleSpread || 0)) 65 | .integerValue(BigNumber.ROUND_FLOOR) 66 | 67 | // console.log( "Selling " + Math.abs(amount) + ' ' + sellSymbol + " for: " + price + ' ' + buySymbol ) 68 | } 69 | 70 | let expiration 71 | expiration = Math.round((new Date()).getTime() / 1000) 72 | expiration += validFor || config.defaultExpiry 73 | 74 | // create order object 75 | const order = { 76 | makerAddress: efx.get('account').toLowerCase(), 77 | takerAddress: '0x0000000000000000000000000000000000000000', 78 | 79 | feeRecipientAddress: efx.config['0x'].ethfinexAddress.toLowerCase(), 80 | senderAddress: efx.config['0x'].ethfinexAddress.toLowerCase(), 81 | 82 | makerAssetAmount: sellAmount, 83 | 84 | takerAssetAmount: buyAmount, 85 | 86 | makerFee: new BigNumber(0), 87 | 88 | takerFee: new BigNumber(0), 89 | 90 | expirationTimeSeconds: new BigNumber(expiration), 91 | 92 | salt: generatePseudoRandomSalt(), 93 | 94 | makerAssetData: assetDataUtils.encodeERC20AssetData(sellCurrency.wrapperAddress.toLowerCase()), 95 | 96 | takerAssetData: assetDataUtils.encodeERC20AssetData(buyCurrency.wrapperAddress.toLowerCase()), 97 | 98 | exchangeAddress: efx.config['0x'].exchangeAddress.toLowerCase() 99 | } 100 | 101 | return order 102 | } 103 | -------------------------------------------------------------------------------- /test/fixtures/contracts/WrapperLockUsd.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.4.24; 2 | 3 | import "./zeppelin/token/BasicToken.sol"; 4 | import "./zeppelin/token/ERC20.sol"; 5 | import "./zeppelin/token/ERC20Old.sol"; 6 | import "./zeppelin/math/SafeMath.sol"; 7 | import "./zeppelin/ownership/Ownable.sol"; 8 | /* 9 | 10 | Copyright Will Harborne (Ethfinex) 2017 11 | 12 | */ 13 | 14 | contract WrapperLockUsd is BasicToken, Ownable { 15 | using SafeMath for uint256; 16 | 17 | address public TRANSFER_PROXY; 18 | mapping (address => bool) private isSigner; 19 | 20 | string public name; 21 | string public symbol; 22 | uint public decimals; 23 | address public originalToken = 0x00; 24 | 25 | mapping (address => uint) public depositLock; 26 | mapping (address => uint256) public balances; 27 | 28 | function WrapperLockUsd(string _name, string _symbol, uint _decimals, address _transferProxy) Ownable() { 29 | TRANSFER_PROXY = _transferProxy; 30 | name = _name; 31 | symbol = _symbol; 32 | decimals = _decimals; 33 | isSigner[msg.sender] = true; 34 | } 35 | 36 | function deposit(uint _value, uint _forTime) public payable returns (bool success) { 37 | require(_forTime >= 1); 38 | require(now + _forTime * 1 hours >= depositLock[msg.sender]); 39 | balances[msg.sender] = balances[msg.sender].add(msg.value); 40 | depositLock[msg.sender] = now + _forTime * 1 hours; 41 | return true; 42 | } 43 | 44 | function withdraw( 45 | uint8 v, 46 | bytes32 r, 47 | bytes32 s, 48 | uint _value, 49 | uint signatureValidUntilBlock 50 | ) 51 | public 52 | returns 53 | (bool) 54 | { 55 | require(balanceOf(msg.sender) >= _value); 56 | if (now > depositLock[msg.sender]) { 57 | balances[msg.sender] = balances[msg.sender].sub(_value); 58 | msg.sender.transfer(_value); 59 | } else { 60 | require(block.number < signatureValidUntilBlock); 61 | require(isValidSignature(keccak256(msg.sender, address(this), signatureValidUntilBlock), v, r, s)); 62 | balances[msg.sender] = balances[msg.sender].sub(_value); 63 | msg.sender.transfer(_value); 64 | } 65 | return true; 66 | } 67 | 68 | function withdrawDifferentToken(address _token, bool _erc20old) public onlyOwner returns (bool) { 69 | require(ERC20(_token).balanceOf(address(this)) > 0); 70 | if (_erc20old) { 71 | ERC20Old(_token).transfer(msg.sender, ERC20(_token).balanceOf(address(this))); 72 | } else { 73 | ERC20(_token).transfer(msg.sender, ERC20(_token).balanceOf(address(this))); 74 | } 75 | return true; 76 | } 77 | 78 | function transfer(address _to, uint256 _value) public returns (bool) { 79 | return false; 80 | } 81 | 82 | function transferFrom(address _from, address _to, uint _value) public { 83 | require(_to == owner || _from == owner); 84 | assert(msg.sender == TRANSFER_PROXY); 85 | balances[_to] = balances[_to].add(_value); 86 | balances[_from] = balances[_from].sub(_value); 87 | Transfer(_from, _to, _value); 88 | } 89 | 90 | function allowance(address _owner, address _spender) public constant returns (uint) { 91 | if (_spender == TRANSFER_PROXY) { 92 | return 2**256 - 1; 93 | } 94 | } 95 | 96 | function balanceOf(address _owner) public constant returns (uint256) { 97 | return balances[_owner]; 98 | } 99 | 100 | function isValidSignature( 101 | bytes32 hash, 102 | uint8 v, 103 | bytes32 r, 104 | bytes32 s) 105 | public 106 | constant 107 | returns (bool) 108 | { 109 | return isSigner[ecrecover( 110 | keccak256("\x19Ethereum Signed Message:\n32", hash), 111 | v, 112 | r, 113 | s 114 | )]; 115 | } 116 | 117 | function addSigner(address _newSigner) public { 118 | require(isSigner[msg.sender]); 119 | isSigner[_newSigner] = true; 120 | } 121 | 122 | function keccak(address _sender, address _wrapper, uint _validTill) public constant returns(bytes32) { 123 | return keccak256(_sender, _wrapper, _validTill); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /test/fixtures/contracts/WrapperLockEth.sol: -------------------------------------------------------------------------------- 1 | 2 | pragma solidity 0.4.24; 3 | 4 | import "./zeppelin/token/BasicToken.sol"; 5 | import "./zeppelin/token/ERC20.sol"; 6 | import "./zeppelin/token/ERC20Old.sol"; 7 | import "./zeppelin/math/SafeMath.sol"; 8 | import "./zeppelin/ownership/Ownable.sol"; 9 | /* 10 | 11 | Copyright Will Harborne (Ethfinex) 2017 12 | 13 | */ 14 | 15 | contract WrapperLockEth is BasicToken, Ownable { 16 | using SafeMath for uint256; 17 | 18 | address public TRANSFER_PROXY; 19 | mapping (address => bool) private isSigner; 20 | 21 | string public name; 22 | string public symbol; 23 | uint public decimals; 24 | address public originalToken = 0x00; 25 | 26 | mapping (address => uint) public depositLock; 27 | mapping (address => uint256) public balances; 28 | 29 | function WrapperLockEth(string _name, string _symbol, uint _decimals, address _transferProxy) Ownable() { 30 | TRANSFER_PROXY = _transferProxy; 31 | name = _name; 32 | symbol = _symbol; 33 | decimals = _decimals; 34 | isSigner[msg.sender] = true; 35 | } 36 | 37 | function deposit(uint _value, uint _forTime) public payable returns (bool success) { 38 | require(_forTime >= 1); 39 | require(now + _forTime * 1 hours >= depositLock[msg.sender]); 40 | balances[msg.sender] = balances[msg.sender].add(msg.value); 41 | depositLock[msg.sender] = now + _forTime * 1 hours; 42 | return true; 43 | } 44 | 45 | function withdraw( 46 | uint8 v, 47 | bytes32 r, 48 | bytes32 s, 49 | uint _value, 50 | uint signatureValidUntilBlock 51 | ) 52 | public 53 | returns 54 | (bool) 55 | { 56 | require(balanceOf(msg.sender) >= _value); 57 | if (now > depositLock[msg.sender]) { 58 | balances[msg.sender] = balances[msg.sender].sub(_value); 59 | msg.sender.transfer(_value); 60 | } else { 61 | require(block.number < signatureValidUntilBlock); 62 | require(isValidSignature(keccak256(msg.sender, address(this), signatureValidUntilBlock), v, r, s)); 63 | balances[msg.sender] = balances[msg.sender].sub(_value); 64 | msg.sender.transfer(_value); 65 | } 66 | return true; 67 | } 68 | 69 | function withdrawDifferentToken(address _token, bool _erc20old) public onlyOwner returns (bool) { 70 | require(ERC20(_token).balanceOf(address(this)) > 0); 71 | if (_erc20old) { 72 | ERC20Old(_token).transfer(msg.sender, ERC20(_token).balanceOf(address(this))); 73 | } else { 74 | ERC20(_token).transfer(msg.sender, ERC20(_token).balanceOf(address(this))); 75 | } 76 | return true; 77 | } 78 | 79 | function transfer(address _to, uint256 _value) public returns (bool) { 80 | return false; 81 | } 82 | 83 | function transferFrom(address _from, address _to, uint _value) public { 84 | require(_to == owner || _from == owner); 85 | assert(msg.sender == TRANSFER_PROXY); 86 | balances[_to] = balances[_to].add(_value); 87 | balances[_from] = balances[_from].sub(_value); 88 | Transfer(_from, _to, _value); 89 | } 90 | 91 | function allowance(address _owner, address _spender) public constant returns (uint) { 92 | if (_spender == TRANSFER_PROXY) { 93 | return 2**256 - 1; 94 | } 95 | } 96 | 97 | function balanceOf(address _owner) public constant returns (uint256) { 98 | return balances[_owner]; 99 | } 100 | 101 | function isValidSignature( 102 | bytes32 hash, 103 | uint8 v, 104 | bytes32 r, 105 | bytes32 s) 106 | public 107 | constant 108 | returns (bool) 109 | { 110 | return isSigner[ecrecover( 111 | keccak256("\x19Ethereum Signed Message:\n32", hash), 112 | v, 113 | r, 114 | s 115 | )]; 116 | } 117 | 118 | function addSigner(address _newSigner) public { 119 | require(isSigner[msg.sender]); 120 | isSigner[_newSigner] = true; 121 | } 122 | 123 | function keccak(address _sender, address _wrapper, uint _validTill) public constant returns(bytes32) { 124 | return keccak256(_sender, _wrapper, _validTill); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /examples/node_spam_submit_orders.js: -------------------------------------------------------------------------------- 1 | // Please check ../test/index.js for all examples and tests 2 | 3 | const EFX = require('..') 4 | 5 | const _ = require('lodash') 6 | const P = require('bluebird') 7 | 8 | let addresses = [] 9 | 10 | submitBuyOrder = async (efx, account, price) => { 11 | await efx.account.select(account) 12 | 13 | await efx.account.unlock('password') 14 | 15 | const amount = 25 / price 16 | 17 | response = await efx.submitOrder('ETHUSD', amount, price) 18 | 19 | console.log( `Submited buy ${amount} ETH for ${price} on behalf of ${account}`) 20 | 21 | if(response.error){ 22 | console.log( " - error", response.error) 23 | } else { 24 | console.log( " - OK", response) 25 | } 26 | } 27 | 28 | submitSellOrder = async (efx, account, price) => { 29 | await efx.account.select(account) 30 | 31 | await efx.account.unlock('password') 32 | 33 | const amount = 25 / price 34 | 35 | response = await efx.submitOrder('ETHUSD', -amount, price) 36 | 37 | console.log( `Submited sell -${amount} ETH for ${price} on behalf of ${account}`) 38 | 39 | if(response.error){ 40 | console.log( " - error", response.error) 41 | } else { 42 | console.log( " - OK", response) 43 | } 44 | } 45 | 46 | cancelAllOrders = async(efx, account) => { 47 | 48 | await efx.account.select(account) 49 | 50 | await efx.account.unlock('password') 51 | 52 | // Cancel all orders 53 | response = await efx.getOrders() 54 | 55 | console.log(`Found ${response.length} orders from ${account}`) 56 | 57 | for(const order of response){ 58 | console.log("") 59 | 60 | console.log( "cancel order #", order.id ) 61 | 62 | try { 63 | response = await efx.cancelOrder(order.id) 64 | console.log(" - OK") 65 | } catch(e) { 66 | //console.log( 'e ->', e) 67 | console.log( "error:", e.response.body ) 68 | } 69 | } 70 | } 71 | 72 | work = async () => { 73 | // assuming you have a provider running at: 74 | // http://localhost:8545 75 | const efx = await EFX() 76 | 77 | const accounts = await efx.web3.eth.getAccounts() 78 | 79 | let indexes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 80 | 81 | indexes = _.shuffle(indexes) 82 | 83 | const INITIAL_PRICE = 200 + Math.random() * 100 - 50 84 | 85 | const randomOffset = () => Math.random() * 10 - 5 86 | 87 | const sellOrders = Promise.all([ 88 | // send 5 orders in parallel 89 | submitSellOrder(efx, accounts[indexes[0]], INITIAL_PRICE + 0 + randomOffset() ), 90 | submitSellOrder(efx, accounts[indexes[1]], INITIAL_PRICE + 1 + randomOffset() ), 91 | submitSellOrder(efx, accounts[indexes[2]], INITIAL_PRICE + 2 + randomOffset() ), 92 | submitSellOrder(efx, accounts[indexes[3]], INITIAL_PRICE + 3 + randomOffset() ), 93 | submitSellOrder(efx, accounts[indexes[4]], INITIAL_PRICE + 4 + randomOffset() ) 94 | ]) 95 | 96 | //console.log( "-------------" ) 97 | //console.log( "sent all sells" ) 98 | //console.log( "" ) 99 | 100 | const buyOrders = Promise.all([ 101 | // send 5 orders in parallel 102 | submitBuyOrder(efx, accounts[indexes[5]], INITIAL_PRICE - 4 + randomOffset() ), 103 | submitBuyOrder(efx, accounts[indexes[6]], INITIAL_PRICE - 3 + randomOffset() ), 104 | submitBuyOrder(efx, accounts[indexes[7]], INITIAL_PRICE - 2 + randomOffset() ), 105 | submitBuyOrder(efx, accounts[indexes[8]], INITIAL_PRICE - 1 + randomOffset() ), 106 | submitBuyOrder(efx, accounts[indexes[9]], INITIAL_PRICE - 0 + randomOffset() ) 107 | ]) 108 | 109 | //console.log( "-------------" ) 110 | //console.log( "cancelled all sells" ) 111 | //console.log( "" ) 112 | 113 | console.log("Firing 10 orders") 114 | console.log('--------- --------- --------') 115 | console.log('') 116 | 117 | await P.all([sellOrders, buyOrders]) 118 | 119 | console.log('') 120 | console.log('--------- --------- --------') 121 | console.log('') 122 | 123 | console.log("Cancelling all orders") 124 | console.log('--------- --------- --------') 125 | console.log('') 126 | 127 | let cancelOrders = [] 128 | 129 | for(let account of accounts){ 130 | 131 | // pseudo-randomly keep some orders in 132 | if(Math.random() < 0.5){ continue } 133 | 134 | //cancelOrders.push(await cancelAllOrders(efx, account)) 135 | } 136 | 137 | await P.all(cancelOrders) 138 | 139 | console.log('--------- --------- --------') 140 | console.log('') 141 | 142 | console.log(' -- OK') 143 | 144 | setTimeout(work, 500) 145 | 146 | } 147 | 148 | work() 149 | 150 | 151 | -------------------------------------------------------------------------------- /src/api/contract/abi/locker.abi.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The ABI for the ETH wrapper contract 3 | * 4 | * see: 5 | * https://ropsten.etherscan.io/address/0x965808e7f815cfffd4c018ef2ba4c5a65eba087e#code 6 | * 7 | */ 8 | module.exports = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"originalToken","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"uint256"},{"name":"v","type":"uint8"},{"name":"r","type":"bytes32"},{"name":"s","type":"bytes32"},{"name":"signatureValidUntilBlock","type":"uint256"}],"name":"withdraw","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balances","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_wrapper","type":"address"},{"name":"_validTill","type":"uint256"}],"name":"keccak","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"isSigner","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"hash","type":"bytes32"},{"name":"v","type":"uint8"},{"name":"r","type":"bytes32"},{"name":"s","type":"bytes32"}],"name":"isValidSignature","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"TRANSFER_PROXY","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"depositLock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"},{"name":"_erc20old","type":"bool"}],"name":"withdrawDifferentToken","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"uint256"},{"name":"_forTime","type":"uint256"}],"name":"deposit","outputs":[{"name":"success","type":"bool"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_newSigner","type":"address"}],"name":"addSigner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_name","type":"string"},{"name":"_symbol","type":"string"},{"name":"_decimals","type":"uint256"},{"name":"_transferProxy","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}] 9 | -------------------------------------------------------------------------------- /src/lib/error/reasons.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | ERR_MAKERTOKEN_ADDRESS_INVALID: ` 4 | The 'maker' token address provided in the signed-order did not 5 | match the pair specified by the API call. 6 | `, 7 | ERR_TAKERTOKEN_ADDRESS_INVALID: ` 8 | The 'taker' token address provided in the signed-order did not 9 | match the pair specified by the API call. 10 | `, 11 | ERR_MAKERTOKEN_AMOUNT_INVALID: ` 12 | The signed-order maker amount did not match with the 13 | amount and price specified in the API call. 14 | `, 15 | ERR_TAKERTOKEN_AMOUNT_INVALID: ` 16 | The signed-order taker amount did not match with the 17 | amount and price specified in the API call. 18 | `, 19 | ERR_CORE_ETHFX_0X_ONLY_EXCHANGE_LIMIT_ORDERS: ` 20 | Only exchange limit orders are currently accepted. 21 | `, 22 | ERR_CORE_ETHFX_0X_INVALID_CCY: ` 23 | The token specified is currently not tradable via the trustless api. 24 | `, 25 | ERR_CORE_ETHFX_0X_SIGNATURE_INVALID: ` 26 | The signature provided with the order was invalid. 27 | `, 28 | ERR_CORE_ETHFX_0X_FEE_RECIPIENT_INVALID: ` 29 | The fee recipient address was not specified as Ethfinex. 30 | `, 31 | ERR_CORE_ETHFX_0X_TAKER_INVALID: ` 32 | The taker address for the trade was not specified as Ethfinex. 33 | `, 34 | ERR_CORE_ETHFX_0X_EXCHANGE_INVALID: ` 35 | The exchange contract address for the trade was not specified correctly. 36 | `, 37 | ERR_CORE_ETHFX_0X_EXPIRED: ` 38 | The order expiration date was too soon, or has already passed. 39 | `, 40 | ERR_CORE_ETHFX_0X_BELOW_MIN_SIZE: ` 41 | The order size was below the minimum allowed. 42 | `, 43 | ERR_CORE_ETHFX_0X_LOCK_TIME_INSUFFICIENT: ` 44 | The tokens required for the trade are not locked for long enough. 45 | `, 46 | ERR_CORE_ETHFX_0X_LOCK_INVALID: ` 47 | The tokens required must be locked until after the order expiration time. 48 | `, 49 | ERR_CORE_ETHFX_NEEDS_APPROVAL: ` 50 | The tokens required must be locked until after the order expiration time. 51 | `, 52 | ERR_CORE_ETHFX_0X_BALANCE_EMPTY: ` 53 | You do not have any balance of the locked tokens required. 54 | `, 55 | ERR_CORE_ETHFX_0X_UNLOCK_TOO_LONG: ` 56 | The time of validity for the unlock request is too long. 57 | `, 58 | ERR_TRADING_ETHFX_TRUSTLESS_API_RELEASE_TOKENS_ORDERS_ACTIVE: ` 59 | You must cancel active orders which involve selected tokens before unlocking. 60 | `, 61 | ERR_TRADING_ETHFX_TRUSTLESS_TOKEN_VERIFY: ` 62 | The provided authentication signature was not valid. 63 | `, 64 | ERR_TRADING_ETHFX_TRUSTLESS_ORDER_INVALID: ` 65 | The order requested for cancelation does not exist or is not active. 66 | `, 67 | ERR_TRADING_ETHFX_TRUSTLESS_OWNER_INVALID: ` 68 | The signature for cancellation request does not match the order owner. 69 | `, 70 | ERR_TRADING_ETHFX_TRUSTLESS_ORDER_CANCEL_FAILURE_0: ` 71 | Cancellation failed. Please try again. 72 | `, 73 | ERR_TRADING_ETHFX_TRUSTLESS_ORDER_CANCEL_FAILURE_1: ` 74 | Cancellation failed. Please try again. 75 | `, 76 | ERR_TRADING_ETHFX_TRUSTLESS_ORDER_CANCEL_FAILURE_2: ` 77 | Cancellation failed. Please try again. 78 | `, 79 | ERR_TRADING_ETHFX_TRUSTLESS_API_ORDER_SUBMIT_CONCURRENCY: ` 80 | You cannot submit new orders concurrently. Please wait and submit again. 81 | `, 82 | ERR_TRADING_ETHFX_TRUSTLESS_TOKEN_INVALID: ` 83 | The submitted nonce must be a timestamp in the future. 84 | `, 85 | ERR_TRADING_ETHFX_TRUSTLESS_ORDER_SUBMIT_FAILURE_0: ` 86 | Failed to submit order to order book. 87 | `, 88 | ERR_TRADING_ETHFX_TRUSTLESS_ORDER_SUBMIT_FAILURE_1: ` 89 | Failed to submit order to order book. 90 | `, 91 | ERR_TRADING_ETHFX_TRUSTLESS_API_RELEASE_TOKENS_GEN: ` 92 | Failed to contact ethereum node, please try request again. 93 | `, 94 | ERR_TRADING_ETHFX_TRUSTLESS_API_RELEASE_TOKENS_RELEASE: ` 95 | Failed to sign release permission, please try request again. 96 | `, 97 | ERR_TRADING_ETHFX_HOT_SIZE_INVALID: ` 98 | Unable to place order of this size at the moment. Please place smaller order or wait and re-submit your order later. 99 | `, 100 | ERR_TRADING_ETHFX_HOT_BALANCE_INSUFFICIENT: ` 101 | Too many settlements currently pending on-chain, please wait and re-submit your order. 102 | `, 103 | ERR_TRADING_ETHFX_TRUSTLESS_PROTO_INVALID: ` 104 | Trustless protocol type not specified, or invalid, in order submission. 105 | `, 106 | ERR_TRADING_ETHFX_TRUSTLESS_BALANCE_INVALID: ` 107 | Insufficient unused balance to place order. 108 | `, 109 | ERR_TRADING_ETHFX_CANT_APPROVE_USDT_TWICE:` 110 | You need to set allowance to 0 before approving this token. 111 | `, 112 | ERR_RELEASE_TOKENS_NONCE_REQUIRES_SIGNATURE:` 113 | When providing a nonce you should also provide a signature. 114 | `, 115 | ERR_TRADING_ETHFX_APPROVE_ETH_NOT_REQUIRED:` 116 | Approving ETH is not required. 117 | `, 118 | ERR_EFXAPI_ORDER_INVALID:` 119 | Invalid Order 120 | `, 121 | ERR_TRADING_ETHFX_TRUSTLESS_API_NECTAR_INVALID:` 122 | During Beta phase you are required to hold NEC in your personal wallet to trade. 123 | `, 124 | } 125 | -------------------------------------------------------------------------------- /test/fixtures/contracts/ZRXToken.sol: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2017 ZeroEx Intl. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | */ 18 | 19 | pragma solidity ^0.4.18; 20 | 21 | contract Token { 22 | 23 | /// @return total amount of tokens 24 | function totalSupply() constant returns (uint supply) {} 25 | 26 | /// @param _owner The address from which the balance will be retrieved 27 | /// @return The balance 28 | function balanceOf(address _owner) constant returns (uint balance) {} 29 | 30 | /// @notice send `_value` token to `_to` from `msg.sender` 31 | /// @param _to The address of the recipient 32 | /// @param _value The amount of token to be transferred 33 | /// @return Whether the transfer was successful or not 34 | function transfer(address _to, uint _value) returns (bool success) {} 35 | 36 | /// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` 37 | /// @param _from The address of the sender 38 | /// @param _to The address of the recipient 39 | /// @param _value The amount of token to be transferred 40 | /// @return Whether the transfer was successful or not 41 | function transferFrom(address _from, address _to, uint _value) returns (bool success) {} 42 | 43 | /// @notice `msg.sender` approves `_addr` to spend `_value` tokens 44 | /// @param _spender The address of the account able to transfer the tokens 45 | /// @param _value The amount of wei to be approved for transfer 46 | /// @return Whether the approval was successful or not 47 | function approve(address _spender, uint _value) returns (bool success) {} 48 | 49 | /// @param _owner The address of the account owning tokens 50 | /// @param _spender The address of the account able to transfer the tokens 51 | /// @return Amount of remaining tokens allowed to spent 52 | function allowance(address _owner, address _spender) constant returns (uint remaining) {} 53 | 54 | event Transfer(address indexed _from, address indexed _to, uint _value); 55 | event Approval(address indexed _owner, address indexed _spender, uint _value); 56 | } 57 | 58 | contract StandardToken is Token { 59 | 60 | function transfer(address _to, uint _value) returns (bool) { 61 | //Default assumes totalSupply can't be over max (2^256 - 1). 62 | if (balances[msg.sender] >= _value && balances[_to] + _value >= balances[_to]) { 63 | balances[msg.sender] -= _value; 64 | balances[_to] += _value; 65 | emit Transfer(msg.sender, _to, _value); 66 | return true; 67 | } else { return false; } 68 | } 69 | 70 | function transferFrom(address _from, address _to, uint _value) returns (bool) { 71 | if (balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value >= balances[_to]) { 72 | balances[_to] += _value; 73 | balances[_from] -= _value; 74 | allowed[_from][msg.sender] -= _value; 75 | emit Transfer(_from, _to, _value); 76 | return true; 77 | } else { return false; } 78 | } 79 | 80 | function balanceOf(address _owner) constant returns (uint) { 81 | return balances[_owner]; 82 | } 83 | 84 | function approve(address _spender, uint _value) returns (bool) { 85 | allowed[msg.sender][_spender] = _value; 86 | emit Approval(msg.sender, _spender, _value); 87 | return true; 88 | } 89 | 90 | function allowance(address _owner, address _spender) constant returns (uint) { 91 | return allowed[_owner][_spender]; 92 | } 93 | 94 | mapping (address => uint) balances; 95 | mapping (address => mapping (address => uint)) allowed; 96 | uint public totalSupply; 97 | } 98 | 99 | contract ZRXToken is StandardToken { 100 | 101 | uint8 constant public decimals = 18; 102 | uint public totalSupply = 10**27; // 1 billion tokens, 18 decimal places 103 | string constant public name = "0x Protocol Token"; 104 | string constant public symbol = "ZRX"; 105 | 106 | uint constant MAX_UINT = 2**256 - 1; 107 | 108 | constructor() { 109 | balances[msg.sender] = totalSupply; 110 | } 111 | 112 | /// @dev ERC20 transferFrom, modified such that an allowance of MAX_UINT represents an unlimited allowance. 113 | /// @param _from Address to transfer from. 114 | /// @param _to Address to transfer to. 115 | /// @param _value Amount to transfer. 116 | /// @return Success of transfer. 117 | function transferFrom(address _from, address _to, uint _value) 118 | public 119 | returns (bool) 120 | { 121 | uint allowance = allowed[_from][msg.sender]; 122 | if (balances[_from] >= _value 123 | && allowance >= _value 124 | && balances[_to] + _value >= balances[_to] 125 | ) { 126 | balances[_to] += _value; 127 | balances[_from] -= _value; 128 | if (allowance < MAX_UINT) { 129 | allowed[_from][msg.sender] -= _value; 130 | } 131 | emit Transfer(_from, _to, _value); 132 | return true; 133 | } else { 134 | return false; 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/api/contract/abi/token.abi.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Token abi, used for approving locks 3 | * 4 | * see: 5 | * USD @ ropsten network 6 | * https://ropsten.etherscan.io/address/0x0736d0c130b2ead47476cc262dbed90d7c4eeabd#code 7 | */ 8 | module.exports = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_upgradedAddress","type":"address"}],"name":"deprecate","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"deprecated","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_evilUser","type":"address"}],"name":"addBlackList","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"upgradedAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balances","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"maximumFee","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"_totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_maker","type":"address"}],"name":"getBlackListStatus","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowed","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"who","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newBasisPoints","type":"uint256"},{"name":"newMaxFee","type":"uint256"}],"name":"setParams","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"issue","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"redeem","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"basisPointsRate","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"isBlackListed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_clearedUser","type":"address"}],"name":"removeBlackList","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"MAX_UINT","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_blackListedUser","type":"address"}],"name":"destroyBlackFunds","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_initialSupply","type":"uint256"},{"name":"_name","type":"string"},{"name":"_symbol","type":"string"},{"name":"_decimals","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Issue","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Redeem","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newAddress","type":"address"}],"name":"Deprecate","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"feeBasisPoints","type":"uint256"},{"indexed":false,"name":"maxFee","type":"uint256"}],"name":"Params","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_blackListedUser","type":"address"},{"indexed":false,"name":"_balance","type":"uint256"}],"name":"DestroyedBlackFunds","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_user","type":"address"}],"name":"AddedBlackList","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_user","type":"address"}],"name":"RemovedBlackList","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpause","type":"event"}] 9 | -------------------------------------------------------------------------------- /test/http-api.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | const { assert } = require('chai') 4 | const nock = require('nock') 5 | const mockGetConf = require('./fixtures/nock/get_conf') 6 | const mockFeeRate = require('./fixtures/nock/feeRate') 7 | const instance = require('./helpers/instance') 8 | const utils = require('ethereumjs-util') 9 | 10 | // TODO: use arrayToOrder to convert response from HTTP API 11 | //const orderToArray = require('lib-js-util-schema') 12 | 13 | let efx 14 | 15 | before(async () => { 16 | mockGetConf() 17 | efx = await instance() 18 | }) 19 | 20 | const ecRecover = require('./helpers/ecRecover') 21 | 22 | it('efx.cancelOrder(orderId) // handle INVALID ERROR order', async () => { 23 | const orderId = 1 24 | const apiResponse = [ 25 | 'error', 26 | 10020, 27 | 'ERR_EFXAPI_ORDER_INVALID' 28 | ] 29 | 30 | nock('https://test.ethfinex.com') 31 | .post('/trustless/v1/w/oc', async (body) => { 32 | assert.equal(body.orderId, orderId) 33 | assert.equal(body.protocol, '0x') 34 | 35 | assert.ok(body.signature) 36 | 37 | let toSign = utils.sha3(orderId.toString(16)) 38 | toSign = utils.bufferToHex(toSign).slice(2) 39 | 40 | const recovered = ecRecover(toSign, body.signature) 41 | 42 | // TODO: fix ecRecover algo for orderId signature 43 | //assert.equal(efx.get('account').toLowerCase(), recovered.toLowerCase()) 44 | return true 45 | }) 46 | .reply(500, apiResponse) 47 | 48 | result = await efx.cancelOrder(orderId) 49 | 50 | assert.equal(result.error.code, 10020) 51 | assert.equal(result.error.message, 'ERR_EFXAPI_ORDER_INVALID') 52 | assert.ok(result.error.reason) 53 | 54 | }) 55 | 56 | it('efx.cancelOrder(orderId, signedOrder) // cancels a previously signed order', async () => { 57 | const orderId = 1 58 | const signedOrder = await efx.sign.cancelOrder(orderId) 59 | const apiResponse = [1234] 60 | 61 | nock('https://test.ethfinex.com') 62 | .post('/trustless/v1/w/oc', async (body) => { 63 | assert.equal(body.orderId, orderId) 64 | assert.equal(body.protocol, '0x') 65 | 66 | assert.ok(body.signature) 67 | 68 | let toSign = utils.sha3(orderId.toString(16)) 69 | toSign = utils.bufferToHex(toSign).slice(2) 70 | 71 | const recovered = ecRecover(toSign, body.signature) 72 | 73 | // TODO: fix ecRecover algo for orderId signature 74 | //assert.equal(efx.get('account').toLowerCase(), recovered.toLowerCase()) 75 | 76 | return true 77 | }) 78 | .reply(200, apiResponse) 79 | 80 | const response = await efx.cancelOrder(orderId, signedOrder) 81 | assert.deepEqual(response, apiResponse) 82 | }) 83 | 84 | it('efx.getOrder(orderId)', async () => { 85 | const orderId = 1 86 | 87 | const apiResponse = [[1234]] 88 | 89 | nock('https://test.ethfinex.com') 90 | .post('/trustless/v1/r/orders', (body) => { 91 | assert.equal(body.id, orderId) 92 | assert.equal(body.protocol, '0x') 93 | assert.ok(body.nonce) 94 | assert.ok(body.signature) 95 | 96 | // sign the nonce from scratched 97 | let toSign = body.nonce.toString(16) 98 | 99 | const recovered = ecRecover(toSign, body.signature) 100 | 101 | assert.equal(efx.get('account').toLowerCase(), recovered.toLowerCase()) 102 | 103 | return true 104 | }) 105 | .reply(200, apiResponse) 106 | 107 | const response = await efx.getOrder(orderId) 108 | 109 | // TODO: 110 | // - record real response using nock.recorder.rec() 111 | // - validate the actual response 112 | assert.deepEqual(response, apiResponse) 113 | }) 114 | 115 | it('efx.getOrders()', async () => { 116 | 117 | const apiResponse = [[1234], [1235]] 118 | 119 | nock('https://test.ethfinex.com') 120 | .post('/trustless/v1/r/orders', (body) => { 121 | assert.ok(body.nonce) 122 | assert.ok(body.signature) 123 | 124 | assert.equal(body.protocol, '0x') 125 | 126 | // sign the nonce from scratched 127 | let toSign = body.nonce.toString(16) 128 | 129 | const recovered = ecRecover(toSign, body.signature) 130 | 131 | assert.equal(efx.get('account').toLowerCase(), recovered.toLowerCase()) 132 | 133 | return true 134 | }) 135 | .reply(200, apiResponse) 136 | 137 | const response = await efx.getOrders() 138 | 139 | assert.deepEqual(response, apiResponse) 140 | }) 141 | 142 | it('efx.getOrderHist(null, null, nonce, signature)', async () => { 143 | 144 | const nonce = ((Date.now() / 1000) + 60 * 60 * 24) + '' 145 | const signature = await efx.sign(nonce.toString(16)) 146 | 147 | const httpResponse = [{ _id: '5b56333fd952c07b351c5940', 148 | id: '1151079509', 149 | type: 'EXCHANGE LIMIT', 150 | pair: 'ETHUSD', 151 | status: 'CANCELED', 152 | created_at: '2018-07-21 16:15:58', 153 | updated_at: '2018-07-23 19:52:51', 154 | user_id: 5, 155 | amount: '-0.10000000', 156 | price: '10000.00000000', 157 | originalamount: '-0.10000000', 158 | routing: 'BFX', 159 | lockedperiod: 0, 160 | trailingprice: '0.00000000', 161 | hidden: 0, 162 | vir: 0, 163 | maxrate: '0.00000000000000000000', 164 | placed_id: null, 165 | placed_trades: null, 166 | nopayback: null, 167 | avg_price: '0.00000000000000000000', 168 | active: 0, 169 | fiat_currency: 'USD', 170 | cid: '58558087372', 171 | cid_date: '2018-07-21', 172 | mseq: '2', 173 | gid: null, 174 | flags: null, 175 | price_aux_limit: '0.00000000', 176 | type_prev: null, 177 | tif: '3570', 178 | v_pair: 'ETHUSD', 179 | meta: 180 | { '$F15': 1, 181 | auth: '0x97ebb3391b30f495ce8cb97857db9b72d3e9dbcb' }, 182 | symbol: 'tETHUSD', 183 | t: 1532375571000 }, 184 | { _id: '5b56333fd952c07b351c593f', 185 | id: '1151079508', 186 | type: 'EXCHANGE LIMIT', 187 | pair: 'ETHUSD', 188 | status: 'CANCELED', 189 | created_at: '2018-07-21 16:15:53', 190 | updated_at: '2018-07-23 19:52:51', 191 | user_id: 5, 192 | amount: '-0.10000000', 193 | price: '10000.00000000', 194 | originalamount: '-0.10000000', 195 | routing: 'BFX', 196 | lockedperiod: 0, 197 | trailingprice: '0.00000000', 198 | hidden: 0, 199 | vir: 0, 200 | maxrate: '0.00000000000000000000', 201 | placed_id: null, 202 | placed_trades: null, 203 | nopayback: null, 204 | avg_price: '0.00000000000000000000', 205 | active: 0, 206 | fiat_currency: 'USD', 207 | cid: '58552546110', 208 | cid_date: '2018-07-21', 209 | mseq: '2', 210 | gid: null, 211 | flags: null, 212 | price_aux_limit: '0.00000000', 213 | type_prev: null, 214 | tif: '3570', 215 | v_pair: 'ETHUSD', 216 | meta: 217 | { '$F15': 1, 218 | auth: '0x97ebb3391b30f495ce8cb97857db9b72d3e9dbcb' }, 219 | symbol: 'tETHUSD', 220 | t: 1532375571000 221 | }] 222 | 223 | nock('https://test.ethfinex.com') 224 | .post('/trustless/v1/r/orders/hist', (body) => { 225 | assert.equal(body.nonce, nonce) 226 | assert.equal(body.signature, signature) 227 | 228 | return true 229 | }) 230 | .reply(200, httpResponse) 231 | 232 | const response = await efx.getOrdersHist(null, null, nonce, signature) 233 | 234 | assert.deepEqual(response, httpResponse) 235 | }) 236 | 237 | it("efx.releaseTokens('USD')", async () => { 238 | const token = 'USD' 239 | 240 | nock('https://test.ethfinex.com') 241 | .post('/trustless/v1/w/releaseTokens', async (body) => { 242 | assert.ok(body.nonce) 243 | assert.ok(body.signature) 244 | assert.equal(body.tokenAddress, efx.config['0x'].tokenRegistry[token].wrapperAddress) 245 | 246 | return true 247 | }) 248 | .reply(200, { 249 | status: 'success', 250 | releaseSignature: '0x...' 251 | }) 252 | 253 | // REVIEW: releaseTokens still timing out 254 | // need to actually test it 255 | const response = await efx.releaseTokens(token) 256 | 257 | assert.ok(response.releaseSignature) 258 | assert.equal(response.status, 'success') 259 | }) 260 | 261 | it('efx.submitOrder(ETHUSD, 1, 100)', async () => { 262 | 263 | mockFeeRate() 264 | 265 | nock('https://test.ethfinex.com') 266 | .post('/trustless/v1/w/on', async (body) => { 267 | assert.equal(body.type, 'EXCHANGE LIMIT') 268 | assert.equal(body.symbol, 'tETHUSD') 269 | assert.equal(body.amount, -0.1) 270 | assert.equal(body.price, 1000) 271 | assert.equal(body.protocol, '0x') 272 | 273 | const {meta} = body 274 | 275 | // TODO: actually hash the signature the same way and make 276 | // and test it instead of simply check if it exists 277 | assert.ok(body.meta.signature) 278 | 279 | assert.ok(body.dynamicFeeRate.feeRate) 280 | assert.ok(body.dynamicFeeRate.feeRates) 281 | assert.ok(body.dynamicFeeRate.feeRates.signature) 282 | 283 | return true 284 | }) 285 | .reply(200, { all: 'good' }) 286 | 287 | const symbol = 'ETHUSD' 288 | const amount = -0.1 289 | const price = 1000 290 | 291 | const response = await efx.submitOrder(symbol, amount, price) 292 | 293 | // TODO: 294 | // - record real response using nock.recorder.rec() 295 | // - validate the actual response 296 | assert.ok(response) 297 | }) 298 | 299 | it('efx.submitSignedOrder(order)', async () => { 300 | 301 | // TODO: move tests with mocks to individual files, probably inside of 302 | // test/http/ folder 303 | const httpResponse = [[1234]] 304 | 305 | mockFeeRate() 306 | 307 | nock('https://test.ethfinex.com') 308 | .post('/trustless/v1/w/on', async (body) => { 309 | assert.equal(body.type, 'EXCHANGE LIMIT') 310 | assert.equal(body.symbol, 'tETHUSD') 311 | assert.equal(body.amount, -0.1) 312 | assert.equal(body.price, 10000) 313 | assert.equal(body.protocol, '0x') 314 | 315 | const {meta} = body 316 | 317 | // TODO: actually hash the signature the same way and make 318 | // and test it instead of simply check if it exists 319 | assert.ok(body.meta.signature) 320 | 321 | return true 322 | }) 323 | .reply(200, httpResponse) 324 | 325 | const symbol = 'ETHUSD' 326 | const amount = -0.1 327 | const price = 10000 328 | 329 | const order = efx.contract.createOrder(symbol, amount, price) 330 | 331 | const signedOrder = await efx.sign.order(order) 332 | 333 | const response = await efx.submitOrder(symbol, amount, price, null, null, signedOrder) 334 | 335 | // TODO: 336 | // - record real response using nock.recorder.rec() 337 | // - validate the actual response 338 | assert.ok(response) 339 | }) 340 | 341 | it('efx.feeRate(ETHUSD, 0.1, 10000) gets feeBps for correct threshold', async () => { 342 | 343 | // TODO: move tests with mocks to individual files, probably inside of 344 | // test/http/ folder 345 | const httpResponse = { 346 | address: '0x65CEEE596B2aba52Acc09f7B6C81955C1DB86404', 347 | timestamp: 1568959208939, 348 | fees: { 349 | small: { threshold: 0, feeBps: 25 }, 350 | medium: { threshold: 500, feeBps: 21 }, 351 | large: { threshold: 2000, feeBps: 20 } 352 | }, 353 | signature: '0x52f18b47494e465aa4ed0f0f123fae4d40d3ac0862b61862e6cc8e5a119dbfe1061a4ee381092a10350185071f4829dbfd6c5f2e26df76dee0593cbe3cbd87321b' 354 | } 355 | 356 | nock('https://api.deversifi.com') 357 | .get('/api/v1/feeRate/' + efx.get('account')) 358 | .reply(200, httpResponse) 359 | 360 | const symbol = 'ETHUSD' 361 | const amount = -0.1 362 | const price = 10000 363 | 364 | const response = await efx.getFeeRate(symbol, amount, price) 365 | 366 | assert.equal(response.feeRate.threshold, 500) 367 | assert.equal(response.feeRate.feeBps, 21) 368 | }) 369 | 370 | it('efx.feeRate(MKRETH, -5, 2.5) gets feeBps for correct threshold', async () => { 371 | 372 | const httpResponse = { 373 | address: '0x65CEEE596B2aba52Acc09f7B6C81955C1DB86404', 374 | timestamp: 1568959208939, 375 | fees: { 376 | small: { threshold: 0, feeBps: 25 }, 377 | medium: { threshold: 500, feeBps: 21 }, 378 | large: { threshold: 2000, feeBps: 20 } 379 | }, 380 | signature: '0x52f18b47494e465aa4ed0f0f123fae4d40d3ac0862b61862e6cc8e5a119dbfe1061a4ee381092a10350185071f4829dbfd6c5f2e26df76dee0593cbe3cbd87321b' 381 | } 382 | 383 | nock('https://api.deversifi.com') 384 | .get('/api/v1/feeRate/' + '0x65CEEE596B2aba52Acc09f7B6C81955C1DB86404') 385 | .reply(200, httpResponse) 386 | 387 | const symbol = 'MKRETH' 388 | const amount = -5 389 | const price = 2.5 390 | 391 | const response = await efx.getFeeRate(symbol, amount, price) 392 | 393 | assert.equal(response.feeRate.threshold, 2000) 394 | assert.equal(response.feeRate.feeBps, 20) 395 | assert.deepEqual(response.feeRates.fees, httpResponse.fees) 396 | 397 | 398 | }) 399 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # DeversiFi Trading API Client Library for Node.JS 4 | 5 | A Node.JS client for the DeversiFi API 6 | 7 | Note, DeversiFi evolved from Ethfinex Trustless, and this client library is due to be replaced at the end of February 2020 with an updated version. 8 | 9 | - [Installation](#installation) 10 | - [NPM](#npm) 11 | - [Prebuild for browser](#prebuild-for-browser) 12 | - [Setup](#setup) 13 | - [Authentication](#authentication) 14 | - [Pre Requisites](#pre-requisites) 15 | - [Instancing](#instancing) 16 | - [Using MetaMask or a local node](#using-metamask-or-a-local-node) 17 | - [Using a remote node](#using-a-remote-node) 18 | - [Using Infura](#using-infura) 19 | - [Configuration](#configuration) 20 | - [Gas Price](#gas-price) 21 | - [Placing an Order](#placing-an-order) 22 | - [Approving tokens](#approving-tokens) 23 | - [Locking tokens](#locking-tokens) 24 | - [Submitting an order](#submitting-an-order) 25 | - [Tether market shift](#tether-market-shift) 26 | - [Cancelling Orders](#cancelling-orders) 27 | - [Standard cancel](#standard-cancel) 28 | - [Signing externally](#signing-externally) 29 | - [Account History](#account-history) 30 | - [Unlocking Tokens](#unlocking-tokens) 31 | - [More examples](#more-examples) 32 | - [Submitting a buy order](#submitting-a-buy-order) 33 | - [Submitting a sell order](#submitting-a-sell-order) 34 | - [Fetching info about specific order](#fetchin-info-about-specific-order) 35 | - [Troubleshooting](#troubleshooting) 36 | - [Developing](#developing) 37 | - [Setup](#setup-1) 38 | - [Run a node](#run-a-node) 39 | - [Implementing-a-new-future](#implementing-a-new-feature) 40 | - [Useful Links](#links) 41 | - [Developing](#developing) 42 | 43 | 44 | ## Installation 45 | 46 | ### NPM 47 | 48 | ```bash 49 | npm i efx-api-node 50 | ``` 51 | ### Prebuild for browser 52 | 53 | Alternatively on the browser you can use the standalone build 54 | ```html 55 | 56 | ``` 57 | 58 | ## Setup 59 | 60 | ### Authentication 61 | 62 | Authentication to make all the following requests is done by signing using an 63 | Ethereum private key. Signing is handled by the DeversiFi client 64 | library if the account is available and unlocked. However if signing using 65 | a hardware wallet, or using a raw private key, the message and signature need 66 | to be prepared separately. 67 | 68 | ### Pre Requisites 69 | 70 | - An ethereum wallet 71 | - A web3 provider with your account or a private key 72 | * Such as MetaMask, keystore file, hardware wallet or raw private key 73 | 74 | ### Instancing 75 | 76 | #### Using MetaMask or a local node 77 | 78 | ```javascript 79 | 80 | // In case of MetaMask make sure you call ethereum.enable() before using it 81 | const EFX = require('efx-api-node') 82 | const efx = await EFX() 83 | ``` 84 | 85 | #### Using a remote node 86 | 87 | ```javascript 88 | const EFX = require('efx-api-node') 89 | const web3 = new EFX.Web3("https://your-web3-provider") 90 | const efx = await EFX(web3) 91 | ``` 92 | 93 | #### Using Infura 94 | 95 | 96 | ````javascript 97 | const HDWalletProvider = require("truffle-hdwallet-provider"); 98 | const Web3 = require("Web3") 99 | 100 | const privateKey = '8F085...' // Account's private key 101 | const infuraKey = '9e28b...' // Your Infura API KEY 102 | const infuraURL = 'https://mainnet.infura.io/v3/' + infuraKey 103 | 104 | const provider = new HDWalletProvider(privateKey, infuraURL) 105 | const web3 = new Web3(provider) 106 | 107 | efx = await EFX(web3) 108 | ```` 109 | 110 | View the full example: [/examples/node_sell_eth_infura.js](/examples/node_sell_eth_infura.js) 111 | 112 | #### Configuration 113 | 114 | It's possible to overwrite values on the configuration on a per instance basis. 115 | 116 | The [default configuration](./src/config.js) can be overwriten with an optional 117 | parameter `userConf` when calling the EFX function. 118 | 119 | For instance: 120 | 121 | ```javascript 122 | efx = await EFX(web3, { 123 | api: 'https://your-custom-api-address' 124 | }) 125 | ``` 126 | 127 | The configuration is also merged with the configuration provided by the exchange 128 | on the HTTP endpoint `/v1/trading/r/get/conf` which at the moment looks similar 129 | to this: 130 | 131 | ```json 132 | "0x":{ 133 | "minOrderTime":300, 134 | "tokenRegistry":{ 135 | "ETH":{ 136 | "decimals":18, 137 | "wrapperAddress":"0x965808e7f815cfffd4c018ef2ba4c5a65eba087e", 138 | "minOrderSize":0.02 139 | }, 140 | "USD":{ 141 | "decimals":6, 142 | "wrapperAddress":"0x83e42e6d1ac009285376340ef64bac1c7d106c89", 143 | "tokenAddress":"0x0736d0c130b2ead47476cc262dbed90d7c4eeabd", 144 | "minOrderSize":10, 145 | "settleSpread": 0.002 146 | } 147 | }, 148 | "deversifiAddress":"0x9faf5515f177f3a8a845d48c19032b33cc54c09c", 149 | "exchangeAddress":"0x67799a5e640bc64ca24d3e6813842754e546d7b1", 150 | "exchangeSymbols":[ 151 | "tETHUSD" 152 | ] 153 | } 154 | ``` 155 | 156 | The complete compiled configuration is accessible through `efx.config`, for instance: 157 | 158 | ```javascript 159 | const efx = await EFX() 160 | 161 | const config = efx.config 162 | ``` 163 | 164 | #### Gas Price 165 | 166 | You can setup a custom gas price by setting up the 'gasPrice' property 167 | ```javascript 168 | const efx = await EFX() 169 | 170 | efx.set('gasPrice', web3.utils.toWei('2', 'gwei')) 171 | 172 | ``` 173 | 174 | ### Placing an Order 175 | 176 | Before placing an order, you are required to lock tokens into the DeversiFi wrapper 177 | contracts. This allows for guaranteed execution and ensures DeversiFi orders 178 | can be added directly onto the centralised order book, and matched against 179 | trades from centralised users. 180 | 181 | ### Approving Tokens 182 | 183 | 184 | The first time locking an ERC20 Ethereum-based token from a specific account, 185 | you are required to approve it to interact with the time-lock smart contracts. 186 | 187 | ```javascript 188 | const token = 'ZRX' 189 | efx.contract.approve(token) 190 | ``` 191 | 192 | This step does not need to be repeated again, and subsequently you are required 193 | only to call the lock function. This transfers tokens into the wrapper token 194 | contract, ready to trade. 195 | 196 | 197 | ### Locking tokens 198 | 199 | ```javascript 200 | const token = 'ZRX' 201 | const amount = 15 // Number of tokens to lock 202 | const forTime = 48 // Time after which unlocking does not require permission 203 | 204 | const response = await efx.contract.lock(token, amount, forTime) 205 | ``` 206 | 207 | The time limit specified when locking is a maximum - tokens can always be 208 | unlocked after this time limit (in hours) expires. In order to unlock tokens 209 | before this expires, you must request a signed permission from DeversiFi. 210 | 211 | This is always returned if you have no orders open involving those tokens. 212 | 213 | ### Submitting an order 214 | 215 | 216 | ```javascript 217 | const symbol = 'ZRXETH' 218 | const amount = -15 219 | const price = 0.0025 220 | 221 | const orderId = await efx.submitOrder(symbol, amount, price) 222 | ``` 223 | 224 | Orders are generated and submitted, returning either an `orderId` or error. A 225 | full list of possible errors and their associated explanation is available [here](https://docs.deversifi.com/?version=latest#troubleshooting). 226 | 227 | When submitting this order we use the 3 first parameters: 228 | 229 | - `symbol` is the pair which you wish to trade 230 | - `amount` is specified in the first currency in the symbol (i.e. ZRXETH). For a 231 | sell, amount is negative. Amount accepts either maximum 8 d.p, or as many 232 | decimals as are available on the relevant token's smart contract if it is 233 | fewer than 8. 234 | - `price` is specified in the second currency in the symbol (i.e. ZRXETH). Prices 235 | should be specified to 5 s.f. maximum. 236 | 237 | **Warning:** DeversiFi orders will always be settled at the **exact price you specify**, and can never be adjusted by DeversiFi, **even if it is at a worse price than the market**. 238 | 239 | For example, when placing a sell order, if the `price` specified is below the highest bid available on the order book, the order will be executed instantly at market. However, the amount you receive will reflect only the `price` that you entered, and not the market price at the time of execution. 240 | 241 | The client library also provides methods for [submitBuyOrder](./src/api/submit_buy_order.js) 242 | and [submitSellOrder](./src/api/submit_sell_order.js). 243 | 244 | You can additionally provide 245 | 246 | - `gid` - Group ID for your order 247 | - `cid` - Client order ID 248 | - `signedOrder` - A previously signed order, in case you're handling signing 249 | - `validFor` - optional amount of hours this order will be valid for, default 250 | to 3600 seconds as specified [on the default configuration](./src/config.js#L5) 251 | 252 | ### Tether market shift 253 | 254 | The XXX/**USDT** markets on DeversiFi build on the liquidity of XXX/**USD** markets on 255 | centralised exchanges. However since there is often not a 256 | direct 1:1 rate between USD and USDT, a shift must be applied to the order books. 257 | 258 | The configuration for DeversiFi returns a `settleSpread` parameter: 259 | 260 | ```json 261 | "USD":{ 262 | "decimals":6, 263 | "wrapperAddress":"0x83e42e6d1ac009285376340ef64bac1c7d106c89", 264 | "tokenAddress":"0x0736d0c130b2ead47476cc262dbed90d7c4eeabd", 265 | "minOrderSize":10, 266 | "settleSpread": 0.02 267 | } 268 | ``` 269 | 270 | This `settleSpread` is indicative of the current USDT/USD exchange rate. When orders are placed 271 | on USDT markets, the settlement price in the signed order must be shifted by the `settleSpread` 272 | parameter before the order is accepted. 273 | 274 | For example, if placing a buy order on the ETH/USD(T) market at a price of 100 USD relative to the centralised exchange the order will be settled on DeversiFi at a price of 102 USDT. 275 | Equally a sell order at 100 USD would receive 102 USDT when settled on DeversiFi. 276 | 277 | ```javascript 278 | efx.submitOrder(symbol, amount, price) // => settlementPrice = price * (1 + settleSpread) 279 | ``` 280 | The `settleSpread` parameter is set dynamically as a 30 minute rolling mean of the USDT/USD 281 | market exchange rate. When placing orders using `submitOrder` or generating them with 282 | `createOrder` the shift is applied for you. 283 | 284 | 285 | ### Cancelling Orders 286 | Cancelling orders requires the `orderId` you wish to cancel to be signed by the 287 | address which created and placed the order. 288 | 289 | #### Standard Cancel 290 | 291 | In case you're not signing the requests yourself 292 | 293 | ```javascript 294 | await efx.cancelOrder(orderId) 295 | ``` 296 | 297 | #### Signing Externally 298 | 299 | In case you're signing the requests yourself: 300 | 301 | ```javascript 302 | const sig = await efx.sign(parseInt(orderId).toString(16)) 303 | const sigConcat = ethUtils.toRpcSig(sig.v, ethUtils.toBuffer(sig.r), ethUtils.toBuffer(sig.s)) 304 | 305 | await efx.cancelOrder(parseInt(orderId), sigConcat) 306 | ``` 307 | 308 | ### Account History 309 | 310 | If you already have an unlocked wallet available to web3 to use for signing, 311 | you can simply get open orders and order history from the API as follows: 312 | 313 | ```javascript 314 | // Get all open orders 315 | const openOrders = await efx.getOrders() 316 | 317 | // Get all historical orders 318 | const historicalOrders = await efx.getOrdersHist() 319 | ``` 320 | 321 | If an unlocked account is not available to sign with, for example when using a 322 | raw private key or hardware wallet, authentication `nonce` and `signature` must be 323 | pre-signed and passed into the calls. `nonce` is required to be a timestamp less 324 | than 3 hours in the future. `signature` is the `nonce` signed using the relevant 325 | private key for the address who's orders you wish to view. 326 | 327 | ```javascript 328 | const ethUtils = require('ethereumjs-utils') 329 | 330 | const privKey = /* Your Private Key */ 331 | const nonce = ((Date.now() / 1000) + 10800) + '' 332 | 333 | const hash = ethUtils.hashPersonalMessage(ethUtils.toBuffer(nonce.toString(16))) 334 | const signature = ethUtils.ecsign(hash, privKey) 335 | 336 | // Get all open orders 337 | const openOrders = await efx.getOrders(null, null, nonce, signature) 338 | 339 | // Get all historical orders 340 | const historicalOrders = await efx.getOrdersHist(null, null, nonce, signature) 341 | ``` 342 | 343 | ### Unlocking tokens 344 | 345 | If tokens are not used in active orders they can always be unlocked. If 346 | unlocking after the time specified when locking has expired, no permission is 347 | required. When unlocking before this, DeversiFi must sign a release permission, 348 | after verifying that you have no orders currently active which require that token. 349 | 350 | If you need permission the library will [automatically call the expected endpoint](./src/api/contract/unlock.js#L24) 351 | on DeversiFi API to ask for such permission. 352 | 353 | ```javascript 354 | const token = 'ZRX' 355 | const amount = 15 356 | const response = await efx.contract.unlock(token, amount) 357 | ``` 358 | 359 | When a particular token's lock time has not yet expired, permission is required 360 | from DeversiFi to unlock early. This permission can be requested directly from 361 | DeversiFi using an API call. 362 | 363 | The request must be authenticated using a nonce and signature, and the response 364 | contains a signed permission from DeversiFi. This permission will always be 365 | granted if DeversiFi is online and your address has no open orders involving 366 | those tokens. In case you're signing the requests yourself you could use the 367 | following code: 368 | 369 | ```javascript 370 | // This example shows how to generate the signature from a raw private key 371 | // Signing using hardware wallets such as Ledger or Trezor can be done using their documentation 372 | 373 | const ethUtils = require('ethereumjs-utils') 374 | 375 | const privKey = /* Your Private Key */ 376 | const nonce = ((Date.now() / 1000) + 350) + '' 377 | 378 | const hash = ethUtils.hashPersonalMessage(ethUtils.toBuffer(nonce.toString(16))) 379 | const signature = ethUtils.ecsign(hash, privKey) 380 | 381 | const response = await efx.contract.unlock(token, amount, nonce, signature) 382 | 383 | ``` 384 | 385 | 386 | 387 | 388 | ```js 389 | 390 | const token = 'ZRX' 391 | const amount = 0.001 392 | 393 | const response = await efx.contract.unlock(token, amount, forTime) 394 | 395 | ``` 396 | 397 | ## More Examples 398 | 399 | Aside from these examples, there are complete examples in the [examples folder](./src/examples) 400 | ### Submitting a buy order 401 | 402 | ```js 403 | 404 | const symbol = 'ETHUSD' 405 | const amount = 1 406 | const price = 100 407 | 408 | efx.submitOrder(symbol, amount, price) 409 | 410 | ``` 411 | 412 | ### Submitting a sell order 413 | 414 | ```js 415 | 416 | const symbol = 'ETHUSD' 417 | const amount = -1 418 | const price = 100 419 | 420 | const orderId = await efx.submitOrder(symbol, amount, price) 421 | 422 | ``` 423 | 424 | ### Fetching info about specific order 425 | 426 | ```js 427 | 428 | const id = 1 429 | 430 | const order = await efx.getOrder(id) 431 | 432 | ``` 433 | 434 | ## Troubleshooting 435 | 436 | A list of error codes returned by the API and reasons are available [here](./src/lib/error/reasons.js#L1). 437 | Some more detailed explanations can also be found in the [API Documentation](https://docs.deversifi.com). 438 | 439 | If you have suggestions to improve this guide or any of the available 440 | documentation, please raise an issue on Github, or email [info@deversifi.com](mailto:info@deversifi.com). 441 | 442 | ## Links 443 | 444 | - [API documentation](https://docs.deversifi.com) 445 | - [DeversiFi developer guide](https://blog.deversifi.com/ethfinex-trustless-developer-guide/) 446 | 447 | ## Developing 448 | 449 | ### Setup 450 | 451 | - `git clone` 452 | - `npm install` 453 | - `bash <(curl https://get.parity.io -L) # install parity` 454 | 455 | ### Run a node 456 | 457 | On kovan: 458 | 459 | ```bash 460 | parity --chain kovan --jsonrpc-apis=all --geth 461 | ``` 462 | * note the jsonrpc set to all 463 | * note the `--geth` in order to be compatible with `geth`'s unlock 'duration' parameter 464 | 465 | On ropsten: 466 | ```bash 467 | geth --testnet --fast --bootnodes "enode://20c9ad97c081d63397d7b685a412227a40e23c8bdc6688c6f37e97cfbc22d2b4d1db1510d8f61e6a8866ad7f0e17c02b14182d37ea7c3c8b9c2683aeb6b733a1@52.169.14.227:30303,enode://6ce05930c72abc632c58e2e4324f7c7ea478cec0ed4fa2528982cf34483094e9cbc9216e7aa349691242576d552a2a56aaeae426c5303ded677ce455ba1acd9d@13.84.180.240:30303" --rpc --rpccorsdomain "*" --rpcapi "eth,web3,personal,net" 468 | ``` 469 | 470 | Alternatively, thanks to [ganache-cli](https://github.com/trufflesuite/ganache-cli) we can 471 | easily run an eth rpc node emulator. (NOTE: currently tests will fail using ganache) 472 | 473 | ```bash 474 | npm test:rpc 475 | ``` 476 | 477 | ### Implementing a new feature 478 | 479 | Starting by watching the test files ( you will need a node running ) 480 | 481 | ```bash 482 | $ npm run test:watch 483 | ``` 484 | 485 | - Write the tests for your new features on the `./test/` 486 | - Add your tests to './test/index.js' file if necessary 487 | - Create your features on ./src/ folder 488 | 489 | * _You will need a ropsten node to do blockchain related tests_ 490 | 491 | ### Testing 492 | 493 | #### On node.js 494 | 495 | ```bash 496 | $ npm run test 497 | ``` 498 | 499 | #### On a headless browser ( using browserify and mochify ) 500 | 501 | ```bash 502 | $ npm run test:web 503 | ``` 504 | 505 | #### Manually on your browser on a browser console 506 | 507 | - Very useful in case you want to issue commands from Google Chrome 508 | while using MetaMask ! 509 | 510 | ```bash 511 | $ npm run build:web:run 512 | ``` 513 | 514 | - Open your browser on [http://localhost:2222](http://localhost:2222) 515 | 516 | ### Building for browers 517 | 518 | - This will build the whole library as one big ugly standalone js file ( uses browserify ) 519 | 520 | ```bash 521 | $ npm run build 522 | ``` 523 | 524 | 525 | ## TODO 526 | 527 | - Allow blockchain tests without relying on a local testnet node by using 528 | npm run test:rpc ( ganache ) and deploying mocked contracts at the beginning 529 | of the test. 530 | 531 | - Setup Travis-ci to test node.js, browser and standalone build. [see this page](https://blog.travis-ci.com/2017-09-12-build-stages-order-and-conditions) 532 | 533 | ## License 534 | 535 | MIT 536 | -------------------------------------------------------------------------------- /test/fixtures/contracts/ZRXToken.sol.json: -------------------------------------------------------------------------------- 1 | {"assembly":{".code":[{"begin":3902,"end":5171,"name":"PUSH","value":"80"},{"begin":3902,"end":5171,"name":"PUSH","value":"40"},{"begin":3902,"end":5171,"name":"MSTORE"},{"begin":4011,"end":4017,"name":"PUSH","value":"33B2E3C9FD0803CE8000000"},{"begin":3985,"end":4017,"name":"PUSH","value":"3"},{"begin":3985,"end":4017,"name":"SSTORE"},{"begin":4203,"end":4268,"name":"CALLVALUE"},{"begin":8,"end":17,"name":"DUP1"},{"begin":5,"end":7,"name":"ISZERO"},{"begin":5,"end":7,"name":"PUSH [tag]","value":"1"},{"begin":5,"end":7,"name":"JUMPI"},{"begin":30,"end":31,"name":"PUSH","value":"0"},{"begin":27,"end":28,"name":"DUP1"},{"begin":20,"end":32,"name":"REVERT"},{"begin":5,"end":7,"name":"tag","value":"1"},{"begin":5,"end":7,"name":"JUMPDEST"},{"begin":-1,"end":-1,"name":"POP"},{"begin":4250,"end":4261,"name":"PUSH","value":"3"},{"begin":4250,"end":4261,"name":"SLOAD"},{"begin":4236,"end":4246,"name":"CALLER"},{"begin":4227,"end":4235,"name":"PUSH","value":"0"},{"begin":4227,"end":4247,"name":"SWAP1"},{"begin":4227,"end":4247,"name":"DUP2"},{"begin":4227,"end":4247,"name":"MSTORE"},{"begin":4227,"end":4247,"name":"PUSH","value":"20"},{"begin":4227,"end":4247,"name":"DUP2"},{"begin":4227,"end":4247,"name":"SWAP1"},{"begin":4227,"end":4247,"name":"MSTORE"},{"begin":4227,"end":4247,"name":"PUSH","value":"40"},{"begin":4227,"end":4247,"name":"SWAP1"},{"begin":4227,"end":4247,"name":"KECCAK256"},{"begin":4227,"end":4261,"name":"SSTORE"},{"begin":3902,"end":5171,"name":"PUSH #[$]","value":"0000000000000000000000000000000000000000000000000000000000000000"},{"begin":3902,"end":5171,"name":"DUP1"},{"begin":3902,"end":5171,"name":"PUSH [$]","value":"0000000000000000000000000000000000000000000000000000000000000000"},{"begin":3902,"end":5171,"name":"PUSH","value":"0"},{"begin":3902,"end":5171,"name":"CODECOPY"},{"begin":3902,"end":5171,"name":"PUSH","value":"0"},{"begin":3902,"end":5171,"name":"RETURN"}],".data":{"0":{".auxdata":"a165627a7a7230582076dc710b0af17d64baa89ff5ee1254f48caa2a16b273f7ba7f3bd9791573d3e60029",".code":[{"begin":3902,"end":5171,"name":"PUSH","value":"80"},{"begin":3902,"end":5171,"name":"PUSH","value":"40"},{"begin":3902,"end":5171,"name":"MSTORE"},{"begin":3902,"end":5171,"name":"PUSH","value":"4"},{"begin":3902,"end":5171,"name":"CALLDATASIZE"},{"begin":3902,"end":5171,"name":"LT"},{"begin":3902,"end":5171,"name":"PUSH [tag]","value":"1"},{"begin":3902,"end":5171,"name":"JUMPI"},{"begin":3902,"end":5171,"name":"PUSH","value":"FFFFFFFF"},{"begin":3902,"end":5171,"name":"PUSH","value":"100000000000000000000000000000000000000000000000000000000"},{"begin":3902,"end":5171,"name":"PUSH","value":"0"},{"begin":3902,"end":5171,"name":"CALLDATALOAD"},{"begin":3902,"end":5171,"name":"DIV"},{"begin":3902,"end":5171,"name":"AND"},{"begin":3902,"end":5171,"name":"PUSH","value":"6FDDE03"},{"begin":3902,"end":5171,"name":"DUP2"},{"begin":3902,"end":5171,"name":"EQ"},{"begin":3902,"end":5171,"name":"PUSH [tag]","value":"2"},{"begin":3902,"end":5171,"name":"JUMPI"},{"begin":3902,"end":5171,"name":"DUP1"},{"begin":3902,"end":5171,"name":"PUSH","value":"95EA7B3"},{"begin":3902,"end":5171,"name":"EQ"},{"begin":3902,"end":5171,"name":"PUSH [tag]","value":"3"},{"begin":3902,"end":5171,"name":"JUMPI"},{"begin":3902,"end":5171,"name":"DUP1"},{"begin":3902,"end":5171,"name":"PUSH","value":"18160DDD"},{"begin":3902,"end":5171,"name":"EQ"},{"begin":3902,"end":5171,"name":"PUSH [tag]","value":"4"},{"begin":3902,"end":5171,"name":"JUMPI"},{"begin":3902,"end":5171,"name":"DUP1"},{"begin":3902,"end":5171,"name":"PUSH","value":"23B872DD"},{"begin":3902,"end":5171,"name":"EQ"},{"begin":3902,"end":5171,"name":"PUSH [tag]","value":"5"},{"begin":3902,"end":5171,"name":"JUMPI"},{"begin":3902,"end":5171,"name":"DUP1"},{"begin":3902,"end":5171,"name":"PUSH","value":"313CE567"},{"begin":3902,"end":5171,"name":"EQ"},{"begin":3902,"end":5171,"name":"PUSH [tag]","value":"6"},{"begin":3902,"end":5171,"name":"JUMPI"},{"begin":3902,"end":5171,"name":"DUP1"},{"begin":3902,"end":5171,"name":"PUSH","value":"70A08231"},{"begin":3902,"end":5171,"name":"EQ"},{"begin":3902,"end":5171,"name":"PUSH [tag]","value":"7"},{"begin":3902,"end":5171,"name":"JUMPI"},{"begin":3902,"end":5171,"name":"DUP1"},{"begin":3902,"end":5171,"name":"PUSH","value":"95D89B41"},{"begin":3902,"end":5171,"name":"EQ"},{"begin":3902,"end":5171,"name":"PUSH [tag]","value":"8"},{"begin":3902,"end":5171,"name":"JUMPI"},{"begin":3902,"end":5171,"name":"DUP1"},{"begin":3902,"end":5171,"name":"PUSH","value":"A9059CBB"},{"begin":3902,"end":5171,"name":"EQ"},{"begin":3902,"end":5171,"name":"PUSH [tag]","value":"9"},{"begin":3902,"end":5171,"name":"JUMPI"},{"begin":3902,"end":5171,"name":"DUP1"},{"begin":3902,"end":5171,"name":"PUSH","value":"DD62ED3E"},{"begin":3902,"end":5171,"name":"EQ"},{"begin":3902,"end":5171,"name":"PUSH [tag]","value":"10"},{"begin":3902,"end":5171,"name":"JUMPI"},{"begin":3902,"end":5171,"name":"tag","value":"1"},{"begin":3902,"end":5171,"name":"JUMPDEST"},{"begin":3902,"end":5171,"name":"PUSH","value":"0"},{"begin":3902,"end":5171,"name":"DUP1"},{"begin":3902,"end":5171,"name":"REVERT"},{"begin":4062,"end":4111,"name":"tag","value":"2"},{"begin":4062,"end":4111,"name":"JUMPDEST"},{"begin":4062,"end":4111,"name":"CALLVALUE"},{"begin":8,"end":17,"name":"DUP1"},{"begin":5,"end":7,"name":"ISZERO"},{"begin":5,"end":7,"name":"PUSH [tag]","value":"11"},{"begin":5,"end":7,"name":"JUMPI"},{"begin":30,"end":31,"name":"PUSH","value":"0"},{"begin":27,"end":28,"name":"DUP1"},{"begin":20,"end":32,"name":"REVERT"},{"begin":5,"end":7,"name":"tag","value":"11"},{"begin":5,"end":7,"name":"JUMPDEST"},{"begin":4062,"end":4111,"name":"POP"},{"begin":4062,"end":4111,"name":"PUSH [tag]","value":"12"},{"begin":4062,"end":4111,"name":"PUSH [tag]","value":"13"},{"begin":4062,"end":4111,"name":"JUMP"},{"begin":4062,"end":4111,"name":"tag","value":"12"},{"begin":4062,"end":4111,"name":"JUMPDEST"},{"begin":4062,"end":4111,"name":"PUSH","value":"40"},{"begin":4062,"end":4111,"name":"DUP1"},{"begin":4062,"end":4111,"name":"MLOAD"},{"begin":4062,"end":4111,"name":"PUSH","value":"20"},{"begin":4062,"end":4111,"name":"DUP1"},{"begin":4062,"end":4111,"name":"DUP3"},{"begin":4062,"end":4111,"name":"MSTORE"},{"begin":4062,"end":4111,"name":"DUP4"},{"begin":4062,"end":4111,"name":"MLOAD"},{"begin":4062,"end":4111,"name":"DUP2"},{"begin":4062,"end":4111,"name":"DUP4"},{"begin":4062,"end":4111,"name":"ADD"},{"begin":4062,"end":4111,"name":"MSTORE"},{"begin":4062,"end":4111,"name":"DUP4"},{"begin":4062,"end":4111,"name":"MLOAD"},{"begin":4062,"end":4111,"name":"SWAP2"},{"begin":4062,"end":4111,"name":"SWAP3"},{"begin":4062,"end":4111,"name":"DUP4"},{"begin":4062,"end":4111,"name":"SWAP3"},{"begin":4062,"end":4111,"name":"SWAP1"},{"begin":4062,"end":4111,"name":"DUP4"},{"begin":4062,"end":4111,"name":"ADD"},{"begin":4062,"end":4111,"name":"SWAP2"},{"begin":4062,"end":4111,"name":"DUP6"},{"begin":4062,"end":4111,"name":"ADD"},{"begin":4062,"end":4111,"name":"SWAP1"},{"begin":4062,"end":4111,"name":"DUP1"},{"begin":4062,"end":4111,"name":"DUP4"},{"begin":4062,"end":4111,"name":"DUP4"},{"begin":4062,"end":4111,"name":"PUSH","value":"0"},{"begin":8,"end":108,"name":"tag","value":"14"},{"begin":8,"end":108,"name":"JUMPDEST"},{"begin":33,"end":36,"name":"DUP4"},{"begin":30,"end":31,"name":"DUP2"},{"begin":27,"end":37,"name":"LT"},{"begin":8,"end":108,"name":"ISZERO"},{"begin":8,"end":108,"name":"PUSH [tag]","value":"15"},{"begin":8,"end":108,"name":"JUMPI"},{"begin":90,"end":101,"name":"DUP2"},{"begin":90,"end":101,"name":"DUP2"},{"begin":90,"end":101,"name":"ADD"},{"begin":84,"end":102,"name":"MLOAD"},{"begin":71,"end":82,"name":"DUP4"},{"begin":71,"end":82,"name":"DUP3"},{"begin":71,"end":82,"name":"ADD"},{"begin":64,"end":103,"name":"MSTORE"},{"begin":52,"end":54,"name":"PUSH","value":"20"},{"begin":45,"end":55,"name":"ADD"},{"begin":8,"end":108,"name":"PUSH [tag]","value":"14"},{"begin":8,"end":108,"name":"JUMP"},{"begin":8,"end":108,"name":"tag","value":"15"},{"begin":8,"end":108,"name":"JUMPDEST"},{"begin":12,"end":26,"name":"POP"},{"begin":4062,"end":4111,"name":"POP"},{"begin":4062,"end":4111,"name":"POP"},{"begin":4062,"end":4111,"name":"POP"},{"begin":4062,"end":4111,"name":"SWAP1"},{"begin":4062,"end":4111,"name":"POP"},{"begin":4062,"end":4111,"name":"SWAP1"},{"begin":4062,"end":4111,"name":"DUP2"},{"begin":4062,"end":4111,"name":"ADD"},{"begin":4062,"end":4111,"name":"SWAP1"},{"begin":4062,"end":4111,"name":"PUSH","value":"1F"},{"begin":4062,"end":4111,"name":"AND"},{"begin":4062,"end":4111,"name":"DUP1"},{"begin":4062,"end":4111,"name":"ISZERO"},{"begin":4062,"end":4111,"name":"PUSH [tag]","value":"17"},{"begin":4062,"end":4111,"name":"JUMPI"},{"begin":4062,"end":4111,"name":"DUP1"},{"begin":4062,"end":4111,"name":"DUP3"},{"begin":4062,"end":4111,"name":"SUB"},{"begin":4062,"end":4111,"name":"DUP1"},{"begin":4062,"end":4111,"name":"MLOAD"},{"begin":4062,"end":4111,"name":"PUSH","value":"1"},{"begin":4062,"end":4111,"name":"DUP4"},{"begin":4062,"end":4111,"name":"PUSH","value":"20"},{"begin":4062,"end":4111,"name":"SUB"},{"begin":4062,"end":4111,"name":"PUSH","value":"100"},{"begin":4062,"end":4111,"name":"EXP"},{"begin":4062,"end":4111,"name":"SUB"},{"begin":4062,"end":4111,"name":"NOT"},{"begin":4062,"end":4111,"name":"AND"},{"begin":4062,"end":4111,"name":"DUP2"},{"begin":4062,"end":4111,"name":"MSTORE"},{"begin":4062,"end":4111,"name":"PUSH","value":"20"},{"begin":4062,"end":4111,"name":"ADD"},{"begin":4062,"end":4111,"name":"SWAP2"},{"begin":4062,"end":4111,"name":"POP"},{"begin":4062,"end":4111,"name":"tag","value":"17"},{"begin":4062,"end":4111,"name":"JUMPDEST"},{"begin":4062,"end":4111,"name":"POP"},{"begin":4062,"end":4111,"name":"SWAP3"},{"begin":4062,"end":4111,"name":"POP"},{"begin":4062,"end":4111,"name":"POP"},{"begin":4062,"end":4111,"name":"POP"},{"begin":4062,"end":4111,"name":"PUSH","value":"40"},{"begin":4062,"end":4111,"name":"MLOAD"},{"begin":4062,"end":4111,"name":"DUP1"},{"begin":4062,"end":4111,"name":"SWAP2"},{"begin":4062,"end":4111,"name":"SUB"},{"begin":4062,"end":4111,"name":"SWAP1"},{"begin":4062,"end":4111,"name":"RETURN"},{"begin":3444,"end":3636,"name":"tag","value":"3"},{"begin":3444,"end":3636,"name":"JUMPDEST"},{"begin":3444,"end":3636,"name":"CALLVALUE"},{"begin":8,"end":17,"name":"DUP1"},{"begin":5,"end":7,"name":"ISZERO"},{"begin":5,"end":7,"name":"PUSH [tag]","value":"18"},{"begin":5,"end":7,"name":"JUMPI"},{"begin":30,"end":31,"name":"PUSH","value":"0"},{"begin":27,"end":28,"name":"DUP1"},{"begin":20,"end":32,"name":"REVERT"},{"begin":5,"end":7,"name":"tag","value":"18"},{"begin":5,"end":7,"name":"JUMPDEST"},{"begin":-1,"end":-1,"name":"POP"},{"begin":3444,"end":3636,"name":"PUSH [tag]","value":"19"},{"begin":-1,"end":-1,"name":"PUSH","value":"1"},{"begin":-1,"end":-1,"name":"PUSH","value":"A0"},{"begin":-1,"end":-1,"name":"PUSH","value":"2"},{"begin":-1,"end":-1,"name":"EXP"},{"begin":-1,"end":-1,"name":"SUB"},{"begin":3444,"end":3636,"name":"PUSH","value":"4"},{"begin":3444,"end":3636,"name":"CALLDATALOAD"},{"begin":3444,"end":3636,"name":"AND"},{"begin":3444,"end":3636,"name":"PUSH","value":"24"},{"begin":3444,"end":3636,"name":"CALLDATALOAD"},{"begin":3444,"end":3636,"name":"PUSH [tag]","value":"20"},{"begin":3444,"end":3636,"name":"JUMP"},{"begin":3444,"end":3636,"name":"tag","value":"19"},{"begin":3444,"end":3636,"name":"JUMPDEST"},{"begin":3444,"end":3636,"name":"PUSH","value":"40"},{"begin":3444,"end":3636,"name":"DUP1"},{"begin":3444,"end":3636,"name":"MLOAD"},{"begin":3444,"end":3636,"name":"SWAP2"},{"begin":3444,"end":3636,"name":"ISZERO"},{"begin":3444,"end":3636,"name":"ISZERO"},{"begin":3444,"end":3636,"name":"DUP3"},{"begin":3444,"end":3636,"name":"MSTORE"},{"begin":3444,"end":3636,"name":"MLOAD"},{"begin":3444,"end":3636,"name":"SWAP1"},{"begin":3444,"end":3636,"name":"DUP2"},{"begin":3444,"end":3636,"name":"SWAP1"},{"begin":3444,"end":3636,"name":"SUB"},{"begin":3444,"end":3636,"name":"PUSH","value":"20"},{"begin":3444,"end":3636,"name":"ADD"},{"begin":3444,"end":3636,"name":"SWAP1"},{"begin":3444,"end":3636,"name":"RETURN"},{"begin":3985,"end":4017,"name":"tag","value":"4"},{"begin":3985,"end":4017,"name":"JUMPDEST"},{"begin":3985,"end":4017,"name":"CALLVALUE"},{"begin":8,"end":17,"name":"DUP1"},{"begin":5,"end":7,"name":"ISZERO"},{"begin":5,"end":7,"name":"PUSH [tag]","value":"21"},{"begin":5,"end":7,"name":"JUMPI"},{"begin":30,"end":31,"name":"PUSH","value":"0"},{"begin":27,"end":28,"name":"DUP1"},{"begin":20,"end":32,"name":"REVERT"},{"begin":5,"end":7,"name":"tag","value":"21"},{"begin":5,"end":7,"name":"JUMPDEST"},{"begin":3985,"end":4017,"name":"POP"},{"begin":3985,"end":4017,"name":"PUSH [tag]","value":"22"},{"begin":3985,"end":4017,"name":"PUSH [tag]","value":"23"},{"begin":3985,"end":4017,"name":"JUMP"},{"begin":3985,"end":4017,"name":"tag","value":"22"},{"begin":3985,"end":4017,"name":"JUMPDEST"},{"begin":3985,"end":4017,"name":"PUSH","value":"40"},{"begin":3985,"end":4017,"name":"DUP1"},{"begin":3985,"end":4017,"name":"MLOAD"},{"begin":3985,"end":4017,"name":"SWAP2"},{"begin":3985,"end":4017,"name":"DUP3"},{"begin":3985,"end":4017,"name":"MSTORE"},{"begin":3985,"end":4017,"name":"MLOAD"},{"begin":3985,"end":4017,"name":"SWAP1"},{"begin":3985,"end":4017,"name":"DUP2"},{"begin":3985,"end":4017,"name":"SWAP1"},{"begin":3985,"end":4017,"name":"SUB"},{"begin":3985,"end":4017,"name":"PUSH","value":"20"},{"begin":3985,"end":4017,"name":"ADD"},{"begin":3985,"end":4017,"name":"SWAP1"},{"begin":3985,"end":4017,"name":"RETURN"},{"begin":4555,"end":5169,"name":"tag","value":"5"},{"begin":4555,"end":5169,"name":"JUMPDEST"},{"begin":4555,"end":5169,"name":"CALLVALUE"},{"begin":8,"end":17,"name":"DUP1"},{"begin":5,"end":7,"name":"ISZERO"},{"begin":5,"end":7,"name":"PUSH [tag]","value":"24"},{"begin":5,"end":7,"name":"JUMPI"},{"begin":30,"end":31,"name":"PUSH","value":"0"},{"begin":27,"end":28,"name":"DUP1"},{"begin":20,"end":32,"name":"REVERT"},{"begin":5,"end":7,"name":"tag","value":"24"},{"begin":5,"end":7,"name":"JUMPDEST"},{"begin":-1,"end":-1,"name":"POP"},{"begin":4555,"end":5169,"name":"PUSH [tag]","value":"19"},{"begin":-1,"end":-1,"name":"PUSH","value":"1"},{"begin":-1,"end":-1,"name":"PUSH","value":"A0"},{"begin":-1,"end":-1,"name":"PUSH","value":"2"},{"begin":-1,"end":-1,"name":"EXP"},{"begin":-1,"end":-1,"name":"SUB"},{"begin":4555,"end":5169,"name":"PUSH","value":"4"},{"begin":4555,"end":5169,"name":"CALLDATALOAD"},{"begin":4555,"end":5169,"name":"DUP2"},{"begin":4555,"end":5169,"name":"AND"},{"begin":4555,"end":5169,"name":"SWAP1"},{"begin":4555,"end":5169,"name":"PUSH","value":"24"},{"begin":4555,"end":5169,"name":"CALLDATALOAD"},{"begin":4555,"end":5169,"name":"AND"},{"begin":4555,"end":5169,"name":"PUSH","value":"44"},{"begin":4555,"end":5169,"name":"CALLDATALOAD"},{"begin":4555,"end":5169,"name":"PUSH [tag]","value":"26"},{"begin":4555,"end":5169,"name":"JUMP"},{"begin":3944,"end":3979,"name":"tag","value":"6"},{"begin":3944,"end":3979,"name":"JUMPDEST"},{"begin":3944,"end":3979,"name":"CALLVALUE"},{"begin":8,"end":17,"name":"DUP1"},{"begin":5,"end":7,"name":"ISZERO"},{"begin":5,"end":7,"name":"PUSH [tag]","value":"27"},{"begin":5,"end":7,"name":"JUMPI"},{"begin":30,"end":31,"name":"PUSH","value":"0"},{"begin":27,"end":28,"name":"DUP1"},{"begin":20,"end":32,"name":"REVERT"},{"begin":5,"end":7,"name":"tag","value":"27"},{"begin":5,"end":7,"name":"JUMPDEST"},{"begin":3944,"end":3979,"name":"POP"},{"begin":3944,"end":3979,"name":"PUSH [tag]","value":"28"},{"begin":3944,"end":3979,"name":"PUSH [tag]","value":"29"},{"begin":3944,"end":3979,"name":"JUMP"},{"begin":3944,"end":3979,"name":"tag","value":"28"},{"begin":3944,"end":3979,"name":"JUMPDEST"},{"begin":3944,"end":3979,"name":"PUSH","value":"40"},{"begin":3944,"end":3979,"name":"DUP1"},{"begin":3944,"end":3979,"name":"MLOAD"},{"begin":3944,"end":3979,"name":"PUSH","value":"FF"},{"begin":3944,"end":3979,"name":"SWAP1"},{"begin":3944,"end":3979,"name":"SWAP3"},{"begin":3944,"end":3979,"name":"AND"},{"begin":3944,"end":3979,"name":"DUP3"},{"begin":3944,"end":3979,"name":"MSTORE"},{"begin":3944,"end":3979,"name":"MLOAD"},{"begin":3944,"end":3979,"name":"SWAP1"},{"begin":3944,"end":3979,"name":"DUP2"},{"begin":3944,"end":3979,"name":"SWAP1"},{"begin":3944,"end":3979,"name":"SUB"},{"begin":3944,"end":3979,"name":"PUSH","value":"20"},{"begin":3944,"end":3979,"name":"ADD"},{"begin":3944,"end":3979,"name":"SWAP1"},{"begin":3944,"end":3979,"name":"RETURN"},{"begin":3339,"end":3438,"name":"tag","value":"7"},{"begin":3339,"end":3438,"name":"JUMPDEST"},{"begin":3339,"end":3438,"name":"CALLVALUE"},{"begin":8,"end":17,"name":"DUP1"},{"begin":5,"end":7,"name":"ISZERO"},{"begin":5,"end":7,"name":"PUSH [tag]","value":"30"},{"begin":5,"end":7,"name":"JUMPI"},{"begin":30,"end":31,"name":"PUSH","value":"0"},{"begin":27,"end":28,"name":"DUP1"},{"begin":20,"end":32,"name":"REVERT"},{"begin":5,"end":7,"name":"tag","value":"30"},{"begin":5,"end":7,"name":"JUMPDEST"},{"begin":-1,"end":-1,"name":"POP"},{"begin":3339,"end":3438,"name":"PUSH [tag]","value":"22"},{"begin":-1,"end":-1,"name":"PUSH","value":"1"},{"begin":-1,"end":-1,"name":"PUSH","value":"A0"},{"begin":-1,"end":-1,"name":"PUSH","value":"2"},{"begin":-1,"end":-1,"name":"EXP"},{"begin":-1,"end":-1,"name":"SUB"},{"begin":3339,"end":3438,"name":"PUSH","value":"4"},{"begin":3339,"end":3438,"name":"CALLDATALOAD"},{"begin":3339,"end":3438,"name":"AND"},{"begin":3339,"end":3438,"name":"PUSH [tag]","value":"32"},{"begin":3339,"end":3438,"name":"JUMP"},{"begin":4117,"end":4154,"name":"tag","value":"8"},{"begin":4117,"end":4154,"name":"JUMPDEST"},{"begin":4117,"end":4154,"name":"CALLVALUE"},{"begin":8,"end":17,"name":"DUP1"},{"begin":5,"end":7,"name":"ISZERO"},{"begin":5,"end":7,"name":"PUSH [tag]","value":"33"},{"begin":5,"end":7,"name":"JUMPI"},{"begin":30,"end":31,"name":"PUSH","value":"0"},{"begin":27,"end":28,"name":"DUP1"},{"begin":20,"end":32,"name":"REVERT"},{"begin":5,"end":7,"name":"tag","value":"33"},{"begin":5,"end":7,"name":"JUMPDEST"},{"begin":4117,"end":4154,"name":"POP"},{"begin":4117,"end":4154,"name":"PUSH [tag]","value":"12"},{"begin":4117,"end":4154,"name":"PUSH [tag]","value":"35"},{"begin":4117,"end":4154,"name":"JUMP"},{"begin":2472,"end":2887,"name":"tag","value":"9"},{"begin":2472,"end":2887,"name":"JUMPDEST"},{"begin":2472,"end":2887,"name":"CALLVALUE"},{"begin":8,"end":17,"name":"DUP1"},{"begin":5,"end":7,"name":"ISZERO"},{"begin":5,"end":7,"name":"PUSH [tag]","value":"40"},{"begin":5,"end":7,"name":"JUMPI"},{"begin":30,"end":31,"name":"PUSH","value":"0"},{"begin":27,"end":28,"name":"DUP1"},{"begin":20,"end":32,"name":"REVERT"},{"begin":5,"end":7,"name":"tag","value":"40"},{"begin":5,"end":7,"name":"JUMPDEST"},{"begin":-1,"end":-1,"name":"POP"},{"begin":2472,"end":2887,"name":"PUSH [tag]","value":"19"},{"begin":-1,"end":-1,"name":"PUSH","value":"1"},{"begin":-1,"end":-1,"name":"PUSH","value":"A0"},{"begin":-1,"end":-1,"name":"PUSH","value":"2"},{"begin":-1,"end":-1,"name":"EXP"},{"begin":-1,"end":-1,"name":"SUB"},{"begin":2472,"end":2887,"name":"PUSH","value":"4"},{"begin":2472,"end":2887,"name":"CALLDATALOAD"},{"begin":2472,"end":2887,"name":"AND"},{"begin":2472,"end":2887,"name":"PUSH","value":"24"},{"begin":2472,"end":2887,"name":"CALLDATALOAD"},{"begin":2472,"end":2887,"name":"PUSH [tag]","value":"42"},{"begin":2472,"end":2887,"name":"JUMP"},{"begin":3642,"end":3768,"name":"tag","value":"10"},{"begin":3642,"end":3768,"name":"JUMPDEST"},{"begin":3642,"end":3768,"name":"CALLVALUE"},{"begin":8,"end":17,"name":"DUP1"},{"begin":5,"end":7,"name":"ISZERO"},{"begin":5,"end":7,"name":"PUSH [tag]","value":"43"},{"begin":5,"end":7,"name":"JUMPI"},{"begin":30,"end":31,"name":"PUSH","value":"0"},{"begin":27,"end":28,"name":"DUP1"},{"begin":20,"end":32,"name":"REVERT"},{"begin":5,"end":7,"name":"tag","value":"43"},{"begin":5,"end":7,"name":"JUMPDEST"},{"begin":-1,"end":-1,"name":"POP"},{"begin":3642,"end":3768,"name":"PUSH [tag]","value":"22"},{"begin":-1,"end":-1,"name":"PUSH","value":"1"},{"begin":-1,"end":-1,"name":"PUSH","value":"A0"},{"begin":-1,"end":-1,"name":"PUSH","value":"2"},{"begin":-1,"end":-1,"name":"EXP"},{"begin":-1,"end":-1,"name":"SUB"},{"begin":3642,"end":3768,"name":"PUSH","value":"4"},{"begin":3642,"end":3768,"name":"CALLDATALOAD"},{"begin":3642,"end":3768,"name":"DUP2"},{"begin":3642,"end":3768,"name":"AND"},{"begin":3642,"end":3768,"name":"SWAP1"},{"begin":3642,"end":3768,"name":"PUSH","value":"24"},{"begin":3642,"end":3768,"name":"CALLDATALOAD"},{"begin":3642,"end":3768,"name":"AND"},{"begin":3642,"end":3768,"name":"PUSH [tag]","value":"45"},{"begin":3642,"end":3768,"name":"JUMP"},{"begin":4062,"end":4111,"name":"tag","value":"13"},{"begin":4062,"end":4111,"name":"JUMPDEST"},{"begin":4062,"end":4111,"name":"PUSH","value":"40"},{"begin":4062,"end":4111,"name":"DUP1"},{"begin":4062,"end":4111,"name":"MLOAD"},{"begin":4062,"end":4111,"name":"DUP1"},{"begin":4062,"end":4111,"name":"DUP3"},{"begin":4062,"end":4111,"name":"ADD"},{"begin":4062,"end":4111,"name":"SWAP1"},{"begin":4062,"end":4111,"name":"SWAP2"},{"begin":4062,"end":4111,"name":"MSTORE"},{"begin":4062,"end":4111,"name":"PUSH","value":"11"},{"begin":4062,"end":4111,"name":"DUP2"},{"begin":4062,"end":4111,"name":"MSTORE"},{"begin":4062,"end":4111,"name":"PUSH","value":"30782050726F746F636F6C20546F6B656E000000000000000000000000000000"},{"begin":4062,"end":4111,"name":"PUSH","value":"20"},{"begin":4062,"end":4111,"name":"DUP3"},{"begin":4062,"end":4111,"name":"ADD"},{"begin":4062,"end":4111,"name":"MSTORE"},{"begin":4062,"end":4111,"name":"DUP2"},{"begin":4062,"end":4111,"name":"JUMP","value":"[out]"},{"begin":3444,"end":3636,"name":"tag","value":"20"},{"begin":3444,"end":3636,"name":"JUMPDEST"},{"begin":3525,"end":3535,"name":"CALLER"},{"begin":3501,"end":3505,"name":"PUSH","value":"0"},{"begin":3517,"end":3536,"name":"DUP2"},{"begin":3517,"end":3536,"name":"DUP2"},{"begin":3517,"end":3536,"name":"MSTORE"},{"begin":3517,"end":3524,"name":"PUSH","value":"1"},{"begin":3517,"end":3536,"name":"PUSH","value":"20"},{"begin":3517,"end":3536,"name":"SWAP1"},{"begin":3517,"end":3536,"name":"DUP2"},{"begin":3517,"end":3536,"name":"MSTORE"},{"begin":3517,"end":3536,"name":"PUSH","value":"40"},{"begin":3517,"end":3536,"name":"DUP1"},{"begin":3517,"end":3536,"name":"DUP4"},{"begin":3517,"end":3536,"name":"KECCAK256"},{"begin":-1,"end":-1,"name":"PUSH","value":"1"},{"begin":-1,"end":-1,"name":"PUSH","value":"A0"},{"begin":-1,"end":-1,"name":"PUSH","value":"2"},{"begin":-1,"end":-1,"name":"EXP"},{"begin":-1,"end":-1,"name":"SUB"},{"begin":3517,"end":3546,"name":"DUP8"},{"begin":3517,"end":3546,"name":"AND"},{"begin":3517,"end":3546,"name":"DUP1"},{"begin":3517,"end":3546,"name":"DUP6"},{"begin":3517,"end":3546,"name":"MSTORE"},{"begin":3517,"end":3546,"name":"SWAP1"},{"begin":3517,"end":3546,"name":"DUP4"},{"begin":3517,"end":3546,"name":"MSTORE"},{"begin":3517,"end":3546,"name":"DUP2"},{"begin":3517,"end":3546,"name":"DUP5"},{"begin":3517,"end":3546,"name":"KECCAK256"},{"begin":3517,"end":3555,"name":"DUP7"},{"begin":3517,"end":3555,"name":"SWAP1"},{"begin":3517,"end":3555,"name":"SSTORE"},{"begin":3570,"end":3608,"name":"DUP2"},{"begin":3570,"end":3608,"name":"MLOAD"},{"begin":3570,"end":3608,"name":"DUP7"},{"begin":3570,"end":3608,"name":"DUP2"},{"begin":3570,"end":3608,"name":"MSTORE"},{"begin":3570,"end":3608,"name":"SWAP2"},{"begin":3570,"end":3608,"name":"MLOAD"},{"begin":3501,"end":3505,"name":"SWAP4"},{"begin":3501,"end":3505,"name":"SWAP5"},{"begin":3517,"end":3546,"name":"SWAP1"},{"begin":3517,"end":3546,"name":"SWAP4"},{"begin":3525,"end":3535,"name":"SWAP1"},{"begin":3525,"end":3535,"name":"SWAP3"},{"begin":3570,"end":3608,"name":"PUSH","value":"8C5BE1E5EBEC7D5BD14F71427D1E84F3DD0314C0F7B2291E5B200AC8C7C3B925"},{"begin":3570,"end":3608,"name":"SWAP3"},{"begin":3570,"end":3608,"name":"DUP3"},{"begin":3570,"end":3608,"name":"SWAP1"},{"begin":3570,"end":3608,"name":"SUB"},{"begin":3570,"end":3608,"name":"ADD"},{"begin":3570,"end":3608,"name":"SWAP1"},{"begin":3570,"end":3608,"name":"LOG3"},{"begin":-1,"end":-1,"name":"POP"},{"begin":3625,"end":3629,"name":"PUSH","value":"1"},{"begin":3444,"end":3636,"name":"tag","value":"46"},{"begin":3444,"end":3636,"name":"JUMPDEST"},{"begin":3444,"end":3636,"name":"SWAP3"},{"begin":3444,"end":3636,"name":"SWAP2"},{"begin":3444,"end":3636,"name":"POP"},{"begin":3444,"end":3636,"name":"POP"},{"begin":3444,"end":3636,"name":"JUMP","value":"[out]"},{"begin":3985,"end":4017,"name":"tag","value":"23"},{"begin":3985,"end":4017,"name":"JUMPDEST"},{"begin":3985,"end":4017,"name":"PUSH","value":"3"},{"begin":3985,"end":4017,"name":"SLOAD"},{"begin":3985,"end":4017,"name":"DUP2"},{"begin":3985,"end":4017,"name":"JUMP","value":"[out]"},{"begin":4555,"end":5169,"name":"tag","value":"26"},{"begin":4555,"end":5169,"name":"JUMPDEST"},{"begin":-1,"end":-1,"name":"PUSH","value":"1"},{"begin":-1,"end":-1,"name":"PUSH","value":"A0"},{"begin":-1,"end":-1,"name":"PUSH","value":"2"},{"begin":-1,"end":-1,"name":"EXP"},{"begin":-1,"end":-1,"name":"SUB"},{"begin":4687,"end":4701,"name":"DUP4"},{"begin":4687,"end":4701,"name":"AND"},{"begin":4650,"end":4654,"name":"PUSH","value":"0"},{"begin":4687,"end":4701,"name":"DUP2"},{"begin":4687,"end":4701,"name":"DUP2"},{"begin":4687,"end":4701,"name":"MSTORE"},{"begin":4687,"end":4694,"name":"PUSH","value":"1"},{"begin":4687,"end":4701,"name":"PUSH","value":"20"},{"begin":4687,"end":4701,"name":"SWAP1"},{"begin":4687,"end":4701,"name":"DUP2"},{"begin":4687,"end":4701,"name":"MSTORE"},{"begin":4687,"end":4701,"name":"PUSH","value":"40"},{"begin":4687,"end":4701,"name":"DUP1"},{"begin":4687,"end":4701,"name":"DUP4"},{"begin":4687,"end":4701,"name":"KECCAK256"},{"begin":4702,"end":4712,"name":"CALLER"},{"begin":4687,"end":4713,"name":"DUP5"},{"begin":4687,"end":4713,"name":"MSTORE"},{"begin":4687,"end":4713,"name":"DUP3"},{"begin":4687,"end":4713,"name":"MSTORE"},{"begin":4687,"end":4713,"name":"DUP1"},{"begin":4687,"end":4713,"name":"DUP4"},{"begin":4687,"end":4713,"name":"KECCAK256"},{"begin":4687,"end":4713,"name":"SLOAD"},{"begin":4727,"end":4742,"name":"SWAP4"},{"begin":4727,"end":4742,"name":"DUP4"},{"begin":4727,"end":4742,"name":"MSTORE"},{"begin":4727,"end":4742,"name":"SWAP1"},{"begin":4727,"end":4742,"name":"DUP3"},{"begin":4727,"end":4742,"name":"SWAP1"},{"begin":4727,"end":4742,"name":"MSTORE"},{"begin":4727,"end":4742,"name":"DUP2"},{"begin":4727,"end":4742,"name":"KECCAK256"},{"begin":4727,"end":4742,"name":"SLOAD"},{"begin":4650,"end":4654,"name":"SWAP1"},{"begin":4650,"end":4654,"name":"SWAP2"},{"begin":4687,"end":4713,"name":"SWAP1"},{"begin":4727,"end":4752,"name":"DUP4"},{"begin":-1,"end":-1,"name":"GT"},{"begin":4727,"end":4752,"name":"DUP1"},{"begin":4727,"end":4752,"name":"ISZERO"},{"begin":4727,"end":4752,"name":"SWAP1"},{"begin":4727,"end":4787,"name":"PUSH [tag]","value":"48"},{"begin":4727,"end":4787,"name":"JUMPI"},{"begin":4727,"end":4787,"name":"POP"},{"begin":4781,"end":4787,"name":"DUP3"},{"begin":4768,"end":4777,"name":"DUP2"},{"begin":4768,"end":4787,"name":"LT"},{"begin":4768,"end":4787,"name":"ISZERO"},{"begin":4727,"end":4787,"name":"tag","value":"48"},{"begin":4727,"end":4787,"name":"JUMPDEST"},{"begin":4727,"end":4842,"name":"DUP1"},{"begin":4727,"end":4842,"name":"ISZERO"},{"begin":4727,"end":4842,"name":"PUSH [tag]","value":"49"},{"begin":4727,"end":4842,"name":"JUMPI"},{"begin":-1,"end":-1,"name":"POP"},{"begin":-1,"end":-1,"name":"PUSH","value":"1"},{"begin":-1,"end":-1,"name":"PUSH","value":"A0"},{"begin":-1,"end":-1,"name":"PUSH","value":"2"},{"begin":-1,"end":-1,"name":"EXP"},{"begin":-1,"end":-1,"name":"SUB"},{"begin":4829,"end":4842,"name":"DUP5"},{"begin":4829,"end":4842,"name":"AND"},{"begin":4829,"end":4837,"name":"PUSH","value":"0"},{"begin":4829,"end":4842,"name":"SWAP1"},{"begin":4829,"end":4842,"name":"DUP2"},{"begin":4829,"end":4842,"name":"MSTORE"},{"begin":4829,"end":4842,"name":"PUSH","value":"20"},{"begin":4829,"end":4842,"name":"DUP2"},{"begin":4829,"end":4842,"name":"SWAP1"},{"begin":4829,"end":4842,"name":"MSTORE"},{"begin":4829,"end":4842,"name":"PUSH","value":"40"},{"begin":4829,"end":4842,"name":"SWAP1"},{"begin":4829,"end":4842,"name":"KECCAK256"},{"begin":4829,"end":4842,"name":"SLOAD"},{"begin":4803,"end":4825,"name":"DUP4"},{"begin":4803,"end":4825,"name":"DUP2"},{"begin":4803,"end":4825,"name":"ADD"},{"begin":4803,"end":4842,"name":"LT"},{"begin":4803,"end":4842,"name":"ISZERO"},{"begin":4727,"end":4842,"name":"tag","value":"49"},{"begin":4727,"end":4842,"name":"JUMPDEST"},{"begin":4723,"end":5163,"name":"ISZERO"},{"begin":4723,"end":5163,"name":"PUSH [tag]","value":"50"},{"begin":4723,"end":5163,"name":"JUMPI"},{"begin":-1,"end":-1,"name":"PUSH","value":"1"},{"begin":-1,"end":-1,"name":"PUSH","value":"A0"},{"begin":-1,"end":-1,"name":"PUSH","value":"2"},{"begin":-1,"end":-1,"name":"EXP"},{"begin":-1,"end":-1,"name":"SUB"},{"begin":4867,"end":4880,"name":"DUP1"},{"begin":4867,"end":4880,"name":"DUP6"},{"begin":4867,"end":4880,"name":"AND"},{"begin":4867,"end":4875,"name":"PUSH","value":"0"},{"begin":4867,"end":4880,"name":"SWAP1"},{"begin":4867,"end":4880,"name":"DUP2"},{"begin":4867,"end":4880,"name":"MSTORE"},{"begin":4867,"end":4880,"name":"PUSH","value":"20"},{"begin":4867,"end":4880,"name":"DUP2"},{"begin":4867,"end":4880,"name":"SWAP1"},{"begin":4867,"end":4880,"name":"MSTORE"},{"begin":4867,"end":4880,"name":"PUSH","value":"40"},{"begin":4867,"end":4880,"name":"DUP1"},{"begin":4867,"end":4880,"name":"DUP3"},{"begin":4867,"end":4880,"name":"KECCAK256"},{"begin":4867,"end":4890,"name":"DUP1"},{"begin":4867,"end":4890,"name":"SLOAD"},{"begin":4867,"end":4890,"name":"DUP8"},{"begin":4867,"end":4890,"name":"ADD"},{"begin":4867,"end":4890,"name":"SWAP1"},{"begin":4867,"end":4890,"name":"SSTORE"},{"begin":4904,"end":4919,"name":"SWAP2"},{"begin":4904,"end":4919,"name":"DUP8"},{"begin":4904,"end":4919,"name":"AND"},{"begin":4904,"end":4919,"name":"DUP2"},{"begin":4904,"end":4919,"name":"MSTORE"},{"begin":4904,"end":4919,"name":"KECCAK256"},{"begin":4904,"end":4929,"name":"DUP1"},{"begin":4904,"end":4929,"name":"SLOAD"},{"begin":4904,"end":4929,"name":"DUP5"},{"begin":4904,"end":4929,"name":"SWAP1"},{"begin":4904,"end":4929,"name":"SUB"},{"begin":4904,"end":4929,"name":"SWAP1"},{"begin":4904,"end":4929,"name":"SSTORE"},{"begin":-1,"end":-1,"name":"PUSH","value":"0"},{"begin":-1,"end":-1,"name":"NOT"},{"begin":4947,"end":4967,"name":"DUP2"},{"begin":4947,"end":4967,"name":"LT"},{"begin":4943,"end":5038,"name":"ISZERO"},{"begin":4943,"end":5038,"name":"PUSH [tag]","value":"51"},{"begin":4943,"end":5038,"name":"JUMPI"},{"begin":-1,"end":-1,"name":"PUSH","value":"1"},{"begin":-1,"end":-1,"name":"PUSH","value":"A0"},{"begin":-1,"end":-1,"name":"PUSH","value":"2"},{"begin":-1,"end":-1,"name":"EXP"},{"begin":-1,"end":-1,"name":"SUB"},{"begin":4987,"end":5001,"name":"DUP6"},{"begin":4987,"end":5001,"name":"AND"},{"begin":4987,"end":5001,"name":"PUSH","value":"0"},{"begin":4987,"end":5001,"name":"SWAP1"},{"begin":4987,"end":5001,"name":"DUP2"},{"begin":4987,"end":5001,"name":"MSTORE"},{"begin":4987,"end":4994,"name":"PUSH","value":"1"},{"begin":4987,"end":5001,"name":"PUSH","value":"20"},{"begin":4987,"end":5001,"name":"SWAP1"},{"begin":4987,"end":5001,"name":"DUP2"},{"begin":4987,"end":5001,"name":"MSTORE"},{"begin":4987,"end":5001,"name":"PUSH","value":"40"},{"begin":4987,"end":5001,"name":"DUP1"},{"begin":4987,"end":5001,"name":"DUP4"},{"begin":4987,"end":5001,"name":"KECCAK256"},{"begin":5002,"end":5012,"name":"CALLER"},{"begin":4987,"end":5013,"name":"DUP5"},{"begin":4987,"end":5013,"name":"MSTORE"},{"begin":4987,"end":5013,"name":"SWAP1"},{"begin":4987,"end":5013,"name":"SWAP2"},{"begin":4987,"end":5013,"name":"MSTORE"},{"begin":4987,"end":5013,"name":"SWAP1"},{"begin":4987,"end":5013,"name":"KECCAK256"},{"begin":4987,"end":5023,"name":"DUP1"},{"begin":4987,"end":5023,"name":"SLOAD"},{"begin":4987,"end":5023,"name":"DUP5"},{"begin":4987,"end":5023,"name":"SWAP1"},{"begin":4987,"end":5023,"name":"SUB"},{"begin":4987,"end":5023,"name":"SWAP1"},{"begin":4987,"end":5023,"name":"SSTORE"},{"begin":4943,"end":5038,"name":"tag","value":"51"},{"begin":4943,"end":5038,"name":"JUMPDEST"},{"begin":5072,"end":5075,"name":"DUP4"},{"begin":-1,"end":-1,"name":"PUSH","value":"1"},{"begin":-1,"end":-1,"name":"PUSH","value":"A0"},{"begin":-1,"end":-1,"name":"PUSH","value":"2"},{"begin":-1,"end":-1,"name":"EXP"},{"begin":-1,"end":-1,"name":"SUB"},{"begin":5056,"end":5084,"name":"AND"},{"begin":5065,"end":5070,"name":"DUP6"},{"begin":-1,"end":-1,"name":"PUSH","value":"1"},{"begin":-1,"end":-1,"name":"PUSH","value":"A0"},{"begin":-1,"end":-1,"name":"PUSH","value":"2"},{"begin":-1,"end":-1,"name":"EXP"},{"begin":-1,"end":-1,"name":"SUB"},{"begin":5056,"end":5084,"name":"AND"},{"begin":5056,"end":5084,"name":"PUSH","value":"DDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF"},{"begin":5077,"end":5083,"name":"DUP6"},{"begin":5056,"end":5084,"name":"PUSH","value":"40"},{"begin":5056,"end":5084,"name":"MLOAD"},{"begin":5056,"end":5084,"name":"DUP1"},{"begin":5056,"end":5084,"name":"DUP3"},{"begin":5056,"end":5084,"name":"DUP2"},{"begin":5056,"end":5084,"name":"MSTORE"},{"begin":5056,"end":5084,"name":"PUSH","value":"20"},{"begin":5056,"end":5084,"name":"ADD"},{"begin":5056,"end":5084,"name":"SWAP2"},{"begin":5056,"end":5084,"name":"POP"},{"begin":5056,"end":5084,"name":"POP"},{"begin":5056,"end":5084,"name":"PUSH","value":"40"},{"begin":5056,"end":5084,"name":"MLOAD"},{"begin":5056,"end":5084,"name":"DUP1"},{"begin":5056,"end":5084,"name":"SWAP2"},{"begin":5056,"end":5084,"name":"SUB"},{"begin":5056,"end":5084,"name":"SWAP1"},{"begin":5056,"end":5084,"name":"LOG3"},{"begin":5105,"end":5109,"name":"PUSH","value":"1"},{"begin":5098,"end":5109,"name":"SWAP2"},{"begin":5098,"end":5109,"name":"POP"},{"begin":5098,"end":5109,"name":"PUSH [tag]","value":"52"},{"begin":5098,"end":5109,"name":"JUMP"},{"begin":4723,"end":5163,"name":"tag","value":"50"},{"begin":4723,"end":5163,"name":"JUMPDEST"},{"begin":5147,"end":5152,"name":"PUSH","value":"0"},{"begin":5140,"end":5152,"name":"SWAP2"},{"begin":5140,"end":5152,"name":"POP"},{"begin":4723,"end":5163,"name":"tag","value":"52"},{"begin":4723,"end":5163,"name":"JUMPDEST"},{"begin":4555,"end":5169,"name":"POP"},{"begin":4555,"end":5169,"name":"SWAP4"},{"begin":4555,"end":5169,"name":"SWAP3"},{"begin":4555,"end":5169,"name":"POP"},{"begin":4555,"end":5169,"name":"POP"},{"begin":4555,"end":5169,"name":"POP"},{"begin":4555,"end":5169,"name":"JUMP","value":"[out]"},{"begin":3944,"end":3979,"name":"tag","value":"29"},{"begin":3944,"end":3979,"name":"JUMPDEST"},{"begin":3977,"end":3979,"name":"PUSH","value":"12"},{"begin":3944,"end":3979,"name":"DUP2"},{"begin":3944,"end":3979,"name":"JUMP","value":"[out]"},{"begin":3339,"end":3438,"name":"tag","value":"32"},{"begin":3339,"end":3438,"name":"JUMPDEST"},{"begin":-1,"end":-1,"name":"PUSH","value":"1"},{"begin":-1,"end":-1,"name":"PUSH","value":"A0"},{"begin":-1,"end":-1,"name":"PUSH","value":"2"},{"begin":-1,"end":-1,"name":"EXP"},{"begin":-1,"end":-1,"name":"SUB"},{"begin":3415,"end":3431,"name":"AND"},{"begin":3392,"end":3396,"name":"PUSH","value":"0"},{"begin":3415,"end":3431,"name":"SWAP1"},{"begin":3415,"end":3431,"name":"DUP2"},{"begin":3415,"end":3431,"name":"MSTORE"},{"begin":3415,"end":3431,"name":"PUSH","value":"20"},{"begin":3415,"end":3431,"name":"DUP2"},{"begin":3415,"end":3431,"name":"SWAP1"},{"begin":3415,"end":3431,"name":"MSTORE"},{"begin":3415,"end":3431,"name":"PUSH","value":"40"},{"begin":3415,"end":3431,"name":"SWAP1"},{"begin":3415,"end":3431,"name":"KECCAK256"},{"begin":3415,"end":3431,"name":"SLOAD"},{"begin":3415,"end":3431,"name":"SWAP1"},{"begin":3339,"end":3438,"name":"JUMP","value":"[out]"},{"begin":4117,"end":4154,"name":"tag","value":"35"},{"begin":4117,"end":4154,"name":"JUMPDEST"},{"begin":4117,"end":4154,"name":"PUSH","value":"40"},{"begin":4117,"end":4154,"name":"DUP1"},{"begin":4117,"end":4154,"name":"MLOAD"},{"begin":4117,"end":4154,"name":"DUP1"},{"begin":4117,"end":4154,"name":"DUP3"},{"begin":4117,"end":4154,"name":"ADD"},{"begin":4117,"end":4154,"name":"SWAP1"},{"begin":4117,"end":4154,"name":"SWAP2"},{"begin":4117,"end":4154,"name":"MSTORE"},{"begin":4117,"end":4154,"name":"PUSH","value":"3"},{"begin":4117,"end":4154,"name":"DUP2"},{"begin":4117,"end":4154,"name":"MSTORE"},{"begin":4117,"end":4154,"name":"PUSH","value":"5A52580000000000000000000000000000000000000000000000000000000000"},{"begin":4117,"end":4154,"name":"PUSH","value":"20"},{"begin":4117,"end":4154,"name":"DUP3"},{"begin":4117,"end":4154,"name":"ADD"},{"begin":4117,"end":4154,"name":"MSTORE"},{"begin":4117,"end":4154,"name":"DUP2"},{"begin":4117,"end":4154,"name":"JUMP","value":"[out]"},{"begin":2472,"end":2887,"name":"tag","value":"42"},{"begin":2472,"end":2887,"name":"JUMPDEST"},{"begin":2623,"end":2633,"name":"CALLER"},{"begin":2525,"end":2529,"name":"PUSH","value":"0"},{"begin":2614,"end":2634,"name":"SWAP1"},{"begin":2614,"end":2634,"name":"DUP2"},{"begin":2614,"end":2634,"name":"MSTORE"},{"begin":2614,"end":2634,"name":"PUSH","value":"20"},{"begin":2614,"end":2634,"name":"DUP2"},{"begin":2614,"end":2634,"name":"SWAP1"},{"begin":2614,"end":2634,"name":"MSTORE"},{"begin":2614,"end":2634,"name":"PUSH","value":"40"},{"begin":2614,"end":2634,"name":"DUP2"},{"begin":2614,"end":2634,"name":"KECCAK256"},{"begin":2614,"end":2634,"name":"SLOAD"},{"begin":2614,"end":2644,"name":"DUP3"},{"begin":-1,"end":-1,"name":"GT"},{"begin":2614,"end":2644,"name":"DUP1"},{"begin":2614,"end":2644,"name":"ISZERO"},{"begin":2614,"end":2644,"name":"SWAP1"},{"begin":2614,"end":2687,"name":"PUSH [tag]","value":"55"},{"begin":2614,"end":2687,"name":"JUMPI"},{"begin":-1,"end":-1,"name":"POP"},{"begin":-1,"end":-1,"name":"PUSH","value":"1"},{"begin":-1,"end":-1,"name":"PUSH","value":"A0"},{"begin":-1,"end":-1,"name":"PUSH","value":"2"},{"begin":-1,"end":-1,"name":"EXP"},{"begin":-1,"end":-1,"name":"SUB"},{"begin":2674,"end":2687,"name":"DUP4"},{"begin":2674,"end":2687,"name":"AND"},{"begin":2674,"end":2682,"name":"PUSH","value":"0"},{"begin":2674,"end":2687,"name":"SWAP1"},{"begin":2674,"end":2687,"name":"DUP2"},{"begin":2674,"end":2687,"name":"MSTORE"},{"begin":2674,"end":2687,"name":"PUSH","value":"20"},{"begin":2674,"end":2687,"name":"DUP2"},{"begin":2674,"end":2687,"name":"SWAP1"},{"begin":2674,"end":2687,"name":"MSTORE"},{"begin":2674,"end":2687,"name":"PUSH","value":"40"},{"begin":2674,"end":2687,"name":"SWAP1"},{"begin":2674,"end":2687,"name":"KECCAK256"},{"begin":2674,"end":2687,"name":"SLOAD"},{"begin":2648,"end":2670,"name":"DUP3"},{"begin":2648,"end":2670,"name":"DUP2"},{"begin":2648,"end":2670,"name":"ADD"},{"begin":2648,"end":2687,"name":"LT"},{"begin":2648,"end":2687,"name":"ISZERO"},{"begin":2614,"end":2687,"name":"tag","value":"55"},{"begin":2614,"end":2687,"name":"JUMPDEST"},{"begin":2610,"end":2881,"name":"ISZERO"},{"begin":2610,"end":2881,"name":"PUSH [tag]","value":"56"},{"begin":2610,"end":2881,"name":"JUMPI"},{"begin":2712,"end":2722,"name":"CALLER"},{"begin":2703,"end":2711,"name":"PUSH","value":"0"},{"begin":2703,"end":2723,"name":"DUP2"},{"begin":2703,"end":2723,"name":"DUP2"},{"begin":2703,"end":2723,"name":"MSTORE"},{"begin":2703,"end":2723,"name":"PUSH","value":"20"},{"begin":2703,"end":2723,"name":"DUP2"},{"begin":2703,"end":2723,"name":"DUP2"},{"begin":2703,"end":2723,"name":"MSTORE"},{"begin":2703,"end":2723,"name":"PUSH","value":"40"},{"begin":2703,"end":2723,"name":"DUP1"},{"begin":2703,"end":2723,"name":"DUP4"},{"begin":2703,"end":2723,"name":"KECCAK256"},{"begin":2703,"end":2733,"name":"DUP1"},{"begin":2703,"end":2733,"name":"SLOAD"},{"begin":2703,"end":2733,"name":"DUP8"},{"begin":2703,"end":2733,"name":"SWAP1"},{"begin":2703,"end":2733,"name":"SUB"},{"begin":2703,"end":2733,"name":"SWAP1"},{"begin":2703,"end":2733,"name":"SSTORE"},{"begin":-1,"end":-1,"name":"PUSH","value":"1"},{"begin":-1,"end":-1,"name":"PUSH","value":"A0"},{"begin":-1,"end":-1,"name":"PUSH","value":"2"},{"begin":-1,"end":-1,"name":"EXP"},{"begin":-1,"end":-1,"name":"SUB"},{"begin":2747,"end":2760,"name":"DUP8"},{"begin":2747,"end":2760,"name":"AND"},{"begin":2747,"end":2760,"name":"DUP1"},{"begin":2747,"end":2760,"name":"DUP5"},{"begin":2747,"end":2760,"name":"MSTORE"},{"begin":2747,"end":2760,"name":"SWAP3"},{"begin":2747,"end":2760,"name":"DUP2"},{"begin":2747,"end":2760,"name":"SWAP1"},{"begin":2747,"end":2760,"name":"KECCAK256"},{"begin":2747,"end":2770,"name":"DUP1"},{"begin":2747,"end":2770,"name":"SLOAD"},{"begin":2747,"end":2770,"name":"DUP8"},{"begin":2747,"end":2770,"name":"ADD"},{"begin":2747,"end":2770,"name":"SWAP1"},{"begin":2747,"end":2770,"name":"SSTORE"},{"begin":2789,"end":2822,"name":"DUP1"},{"begin":2789,"end":2822,"name":"MLOAD"},{"begin":2789,"end":2822,"name":"DUP7"},{"begin":2789,"end":2822,"name":"DUP2"},{"begin":2789,"end":2822,"name":"MSTORE"},{"begin":2789,"end":2822,"name":"SWAP1"},{"begin":2789,"end":2822,"name":"MLOAD"},{"begin":2747,"end":2760,"name":"SWAP3"},{"begin":2747,"end":2760,"name":"SWAP4"},{"begin":2712,"end":2722,"name":"SWAP3"},{"begin":2789,"end":2822,"name":"PUSH","value":"DDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF"},{"begin":2789,"end":2822,"name":"SWAP3"},{"begin":2789,"end":2822,"name":"SWAP2"},{"begin":2789,"end":2822,"name":"DUP2"},{"begin":2789,"end":2822,"name":"SWAP1"},{"begin":2789,"end":2822,"name":"SUB"},{"begin":2789,"end":2822,"name":"SWAP1"},{"begin":2789,"end":2822,"name":"SWAP2"},{"begin":2789,"end":2822,"name":"ADD"},{"begin":2789,"end":2822,"name":"SWAP1"},{"begin":2789,"end":2822,"name":"LOG3"},{"begin":-1,"end":-1,"name":"POP"},{"begin":2843,"end":2847,"name":"PUSH","value":"1"},{"begin":2836,"end":2847,"name":"PUSH [tag]","value":"46"},{"begin":2836,"end":2847,"name":"JUMP"},{"begin":2610,"end":2881,"name":"tag","value":"56"},{"begin":2610,"end":2881,"name":"JUMPDEST"},{"begin":-1,"end":-1,"name":"POP"},{"begin":2873,"end":2878,"name":"PUSH","value":"0"},{"begin":2866,"end":2878,"name":"PUSH [tag]","value":"46"},{"begin":2866,"end":2878,"name":"JUMP"},{"begin":3642,"end":3768,"name":"tag","value":"45"},{"begin":3642,"end":3768,"name":"JUMPDEST"},{"begin":-1,"end":-1,"name":"PUSH","value":"1"},{"begin":-1,"end":-1,"name":"PUSH","value":"A0"},{"begin":-1,"end":-1,"name":"PUSH","value":"2"},{"begin":-1,"end":-1,"name":"EXP"},{"begin":-1,"end":-1,"name":"SUB"},{"begin":3736,"end":3751,"name":"SWAP2"},{"begin":3736,"end":3751,"name":"DUP3"},{"begin":3736,"end":3751,"name":"AND"},{"begin":3713,"end":3717,"name":"PUSH","value":"0"},{"begin":3736,"end":3751,"name":"SWAP1"},{"begin":3736,"end":3751,"name":"DUP2"},{"begin":3736,"end":3751,"name":"MSTORE"},{"begin":3736,"end":3743,"name":"PUSH","value":"1"},{"begin":3736,"end":3751,"name":"PUSH","value":"20"},{"begin":3736,"end":3751,"name":"SWAP1"},{"begin":3736,"end":3751,"name":"DUP2"},{"begin":3736,"end":3751,"name":"MSTORE"},{"begin":3736,"end":3751,"name":"PUSH","value":"40"},{"begin":3736,"end":3751,"name":"DUP1"},{"begin":3736,"end":3751,"name":"DUP4"},{"begin":3736,"end":3751,"name":"KECCAK256"},{"begin":3736,"end":3761,"name":"SWAP4"},{"begin":3736,"end":3761,"name":"SWAP1"},{"begin":3736,"end":3761,"name":"SWAP5"},{"begin":3736,"end":3761,"name":"AND"},{"begin":3736,"end":3761,"name":"DUP3"},{"begin":3736,"end":3761,"name":"MSTORE"},{"begin":3736,"end":3761,"name":"SWAP2"},{"begin":3736,"end":3761,"name":"SWAP1"},{"begin":3736,"end":3761,"name":"SWAP2"},{"begin":3736,"end":3761,"name":"MSTORE"},{"begin":3736,"end":3761,"name":"KECCAK256"},{"begin":3736,"end":3761,"name":"SLOAD"},{"begin":3736,"end":3761,"name":"SWAP1"},{"begin":3642,"end":3768,"name":"JUMP","value":"[out]"}]}}},"bytecode":"60806040526b033b2e3c9fd0803ce800000060035534801561002057600080fd5b506003543360009081526020819052604090205561058d806100436000396000f3006080604052600436106100985763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166306fdde03811461009d578063095ea7b31461012757806318160ddd1461015f57806323b872dd14610186578063313ce567146101b057806370a08231146101db57806395d89b41146101fc578063a9059cbb14610211578063dd62ed3e14610235575b600080fd5b3480156100a957600080fd5b506100b261025c565b6040805160208082528351818301528351919283929083019185019080838360005b838110156100ec5781810151838201526020016100d4565b50505050905090810190601f1680156101195780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561013357600080fd5b5061014b600160a060020a0360043516602435610293565b604080519115158252519081900360200190f35b34801561016b57600080fd5b506101746102fa565b60408051918252519081900360200190f35b34801561019257600080fd5b5061014b600160a060020a0360043581169060243516604435610300565b3480156101bc57600080fd5b506101c561042e565b6040805160ff9092168252519081900360200190f35b3480156101e757600080fd5b50610174600160a060020a0360043516610433565b34801561020857600080fd5b506100b261044e565b34801561021d57600080fd5b5061014b600160a060020a0360043516602435610485565b34801561024157600080fd5b50610174600160a060020a0360043581169060243516610536565b60408051808201909152601181527f30782050726f746f636f6c20546f6b656e000000000000000000000000000000602082015281565b336000818152600160209081526040808320600160a060020a038716808552908352818420869055815186815291519394909390927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925928290030190a35060015b92915050565b60035481565b600160a060020a03831660008181526001602090815260408083203384528252808320549383529082905281205490919083118015906103405750828110155b80156103665750600160a060020a03841660009081526020819052604090205483810110155b1561042157600160a060020a03808516600090815260208190526040808220805487019055918716815220805484900390556000198110156103cd57600160a060020a03851660009081526001602090815260408083203384529091529020805484900390555b83600160a060020a031685600160a060020a03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef856040518082815260200191505060405180910390a360019150610426565b600091505b509392505050565b601281565b600160a060020a031660009081526020819052604090205490565b60408051808201909152600381527f5a52580000000000000000000000000000000000000000000000000000000000602082015281565b3360009081526020819052604081205482118015906104be5750600160a060020a03831660009081526020819052604090205482810110155b1561052e573360008181526020818152604080832080548790039055600160a060020a03871680845292819020805487019055805186815290519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a35060016102f4565b5060006102f4565b600160a060020a039182166000908152600160209081526040808320939094168252919091522054905600a165627a7a7230582076dc710b0af17d64baa89ff5ee1254f48caa2a16b273f7ba7f3bd9791573d3e60029","functionHashes":{"allowance(address,address)":"dd62ed3e","approve(address,uint256)":"095ea7b3","balanceOf(address)":"70a08231","decimals()":"313ce567","name()":"06fdde03","symbol()":"95d89b41","totalSupply()":"18160ddd","transfer(address,uint256)":"a9059cbb","transferFrom(address,address,uint256)":"23b872dd"},"gasEstimates":{"creation":[40607,284200],"external":{"allowance(address,address)":884,"approve(address,uint256)":22355,"balanceOf(address)":719,"decimals()":281,"name()":null,"symbol()":null,"totalSupply()":428,"transfer(address,uint256)":43596,"transferFrom(address,address,uint256)":64594},"internal":{}},"interface":"[{\"constant\":true,\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_spender\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_from\",\"type\":\"address\"},{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"name\":\"\",\"type\":\"uint8\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\"},{\"name\":\"_spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"_from\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"_to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"_owner\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"_spender\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"}]","metadata":"{\"compiler\":{\"version\":\"0.4.24+commit.e67f0147\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"constant\":true,\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_spender\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_from\",\"type\":\"address\"},{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"name\":\"\",\"type\":\"uint8\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"_to\",\"type\":\"address\"},{\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"_owner\",\"type\":\"address\"},{\"name\":\"_spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"_from\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"_to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"_owner\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"_spender\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"_value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"}],\"devdoc\":{\"methods\":{\"transferFrom(address,address,uint256)\":{\"details\":\"ERC20 transferFrom, modified such that an allowance of MAX_UINT represents an unlimited allowance.\",\"params\":{\"_from\":\"Address to transfer from.\",\"_to\":\"Address to transfer to.\",\"_value\":\"Amount to transfer.\"},\"return\":\"Success of transfer.\"}}},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"\":\"ZRXToken\"},\"evmVersion\":\"byzantium\",\"libraries\":{},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"\":{\"keccak256\":\"0xa43f09e8282c8c894ffed80e272588f25612001144d26ced8837aedc08391e5a\",\"urls\":[\"bzzr://bad61243882b61eff208298cd3205615133f2e6586df741f4f8047e5bf9d7935\"]}},\"version\":1}","opcodes":"PUSH1 0x80 PUSH1 0x40 MSTORE PUSH12 0x33B2E3C9FD0803CE8000000 PUSH1 0x3 SSTORE CALLVALUE DUP1 ISZERO PUSH2 0x20 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x3 SLOAD CALLER PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x20 DUP2 SWAP1 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SSTORE PUSH2 0x58D DUP1 PUSH2 0x43 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN STOP PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x4 CALLDATASIZE LT PUSH2 0x98 JUMPI PUSH4 0xFFFFFFFF PUSH29 0x100000000000000000000000000000000000000000000000000000000 PUSH1 0x0 CALLDATALOAD DIV AND PUSH4 0x6FDDE03 DUP2 EQ PUSH2 0x9D JUMPI DUP1 PUSH4 0x95EA7B3 EQ PUSH2 0x127 JUMPI DUP1 PUSH4 0x18160DDD EQ PUSH2 0x15F JUMPI DUP1 PUSH4 0x23B872DD EQ PUSH2 0x186 JUMPI DUP1 PUSH4 0x313CE567 EQ PUSH2 0x1B0 JUMPI DUP1 PUSH4 0x70A08231 EQ PUSH2 0x1DB JUMPI DUP1 PUSH4 0x95D89B41 EQ PUSH2 0x1FC JUMPI DUP1 PUSH4 0xA9059CBB EQ PUSH2 0x211 JUMPI DUP1 PUSH4 0xDD62ED3E EQ PUSH2 0x235 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0xA9 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0xB2 PUSH2 0x25C JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH1 0x20 DUP1 DUP3 MSTORE DUP4 MLOAD DUP2 DUP4 ADD MSTORE DUP4 MLOAD SWAP2 SWAP3 DUP4 SWAP3 SWAP1 DUP4 ADD SWAP2 DUP6 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0xEC JUMPI DUP2 DUP2 ADD MLOAD DUP4 DUP3 ADD MSTORE PUSH1 0x20 ADD PUSH2 0xD4 JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0x119 JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP SWAP3 POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x133 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x14B PUSH1 0x1 PUSH1 0xA0 PUSH1 0x2 EXP SUB PUSH1 0x4 CALLDATALOAD AND PUSH1 0x24 CALLDATALOAD PUSH2 0x293 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD SWAP2 ISZERO ISZERO DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x16B JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x174 PUSH2 0x2FA JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD SWAP2 DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x192 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x14B PUSH1 0x1 PUSH1 0xA0 PUSH1 0x2 EXP SUB PUSH1 0x4 CALLDATALOAD DUP2 AND SWAP1 PUSH1 0x24 CALLDATALOAD AND PUSH1 0x44 CALLDATALOAD PUSH2 0x300 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x1BC JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x1C5 PUSH2 0x42E JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD PUSH1 0xFF SWAP1 SWAP3 AND DUP3 MSTORE MLOAD SWAP1 DUP2 SWAP1 SUB PUSH1 0x20 ADD SWAP1 RETURN JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x1E7 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x174 PUSH1 0x1 PUSH1 0xA0 PUSH1 0x2 EXP SUB PUSH1 0x4 CALLDATALOAD AND PUSH2 0x433 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x208 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0xB2 PUSH2 0x44E JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x21D JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x14B PUSH1 0x1 PUSH1 0xA0 PUSH1 0x2 EXP SUB PUSH1 0x4 CALLDATALOAD AND PUSH1 0x24 CALLDATALOAD PUSH2 0x485 JUMP JUMPDEST CALLVALUE DUP1 ISZERO PUSH2 0x241 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH2 0x174 PUSH1 0x1 PUSH1 0xA0 PUSH1 0x2 EXP SUB PUSH1 0x4 CALLDATALOAD DUP2 AND SWAP1 PUSH1 0x24 CALLDATALOAD AND PUSH2 0x536 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD DUP1 DUP3 ADD SWAP1 SWAP2 MSTORE PUSH1 0x11 DUP2 MSTORE PUSH32 0x30782050726F746F636F6C20546F6B656E000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE DUP2 JUMP JUMPDEST CALLER PUSH1 0x0 DUP2 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 SWAP1 DUP2 MSTORE PUSH1 0x40 DUP1 DUP4 KECCAK256 PUSH1 0x1 PUSH1 0xA0 PUSH1 0x2 EXP SUB DUP8 AND DUP1 DUP6 MSTORE SWAP1 DUP4 MSTORE DUP2 DUP5 KECCAK256 DUP7 SWAP1 SSTORE DUP2 MLOAD DUP7 DUP2 MSTORE SWAP2 MLOAD SWAP4 SWAP5 SWAP1 SWAP4 SWAP1 SWAP3 PUSH32 0x8C5BE1E5EBEC7D5BD14F71427D1E84F3DD0314C0F7B2291E5B200AC8C7C3B925 SWAP3 DUP3 SWAP1 SUB ADD SWAP1 LOG3 POP PUSH1 0x1 JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH1 0x3 SLOAD DUP2 JUMP JUMPDEST PUSH1 0x1 PUSH1 0xA0 PUSH1 0x2 EXP SUB DUP4 AND PUSH1 0x0 DUP2 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 SWAP1 DUP2 MSTORE PUSH1 0x40 DUP1 DUP4 KECCAK256 CALLER DUP5 MSTORE DUP3 MSTORE DUP1 DUP4 KECCAK256 SLOAD SWAP4 DUP4 MSTORE SWAP1 DUP3 SWAP1 MSTORE DUP2 KECCAK256 SLOAD SWAP1 SWAP2 SWAP1 DUP4 GT DUP1 ISZERO SWAP1 PUSH2 0x340 JUMPI POP DUP3 DUP2 LT ISZERO JUMPDEST DUP1 ISZERO PUSH2 0x366 JUMPI POP PUSH1 0x1 PUSH1 0xA0 PUSH1 0x2 EXP SUB DUP5 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x20 DUP2 SWAP1 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD DUP4 DUP2 ADD LT ISZERO JUMPDEST ISZERO PUSH2 0x421 JUMPI PUSH1 0x1 PUSH1 0xA0 PUSH1 0x2 EXP SUB DUP1 DUP6 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x20 DUP2 SWAP1 MSTORE PUSH1 0x40 DUP1 DUP3 KECCAK256 DUP1 SLOAD DUP8 ADD SWAP1 SSTORE SWAP2 DUP8 AND DUP2 MSTORE KECCAK256 DUP1 SLOAD DUP5 SWAP1 SUB SWAP1 SSTORE PUSH1 0x0 NOT DUP2 LT ISZERO PUSH2 0x3CD JUMPI PUSH1 0x1 PUSH1 0xA0 PUSH1 0x2 EXP SUB DUP6 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 SWAP1 DUP2 MSTORE PUSH1 0x40 DUP1 DUP4 KECCAK256 CALLER DUP5 MSTORE SWAP1 SWAP2 MSTORE SWAP1 KECCAK256 DUP1 SLOAD DUP5 SWAP1 SUB SWAP1 SSTORE JUMPDEST DUP4 PUSH1 0x1 PUSH1 0xA0 PUSH1 0x2 EXP SUB AND DUP6 PUSH1 0x1 PUSH1 0xA0 PUSH1 0x2 EXP SUB AND PUSH32 0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF DUP6 PUSH1 0x40 MLOAD DUP1 DUP3 DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 LOG3 PUSH1 0x1 SWAP2 POP PUSH2 0x426 JUMP JUMPDEST PUSH1 0x0 SWAP2 POP JUMPDEST POP SWAP4 SWAP3 POP POP POP JUMP JUMPDEST PUSH1 0x12 DUP2 JUMP JUMPDEST PUSH1 0x1 PUSH1 0xA0 PUSH1 0x2 EXP SUB AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x20 DUP2 SWAP1 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD SWAP1 JUMP JUMPDEST PUSH1 0x40 DUP1 MLOAD DUP1 DUP3 ADD SWAP1 SWAP2 MSTORE PUSH1 0x3 DUP2 MSTORE PUSH32 0x5A52580000000000000000000000000000000000000000000000000000000000 PUSH1 0x20 DUP3 ADD MSTORE DUP2 JUMP JUMPDEST CALLER PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x20 DUP2 SWAP1 MSTORE PUSH1 0x40 DUP2 KECCAK256 SLOAD DUP3 GT DUP1 ISZERO SWAP1 PUSH2 0x4BE JUMPI POP PUSH1 0x1 PUSH1 0xA0 PUSH1 0x2 EXP SUB DUP4 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x20 DUP2 SWAP1 MSTORE PUSH1 0x40 SWAP1 KECCAK256 SLOAD DUP3 DUP2 ADD LT ISZERO JUMPDEST ISZERO PUSH2 0x52E JUMPI CALLER PUSH1 0x0 DUP2 DUP2 MSTORE PUSH1 0x20 DUP2 DUP2 MSTORE PUSH1 0x40 DUP1 DUP4 KECCAK256 DUP1 SLOAD DUP8 SWAP1 SUB SWAP1 SSTORE PUSH1 0x1 PUSH1 0xA0 PUSH1 0x2 EXP SUB DUP8 AND DUP1 DUP5 MSTORE SWAP3 DUP2 SWAP1 KECCAK256 DUP1 SLOAD DUP8 ADD SWAP1 SSTORE DUP1 MLOAD DUP7 DUP2 MSTORE SWAP1 MLOAD SWAP3 SWAP4 SWAP3 PUSH32 0xDDF252AD1BE2C89B69C2B068FC378DAA952BA7F163C4A11628F55A4DF523B3EF SWAP3 SWAP2 DUP2 SWAP1 SUB SWAP1 SWAP2 ADD SWAP1 LOG3 POP PUSH1 0x1 PUSH2 0x2F4 JUMP JUMPDEST POP PUSH1 0x0 PUSH2 0x2F4 JUMP JUMPDEST PUSH1 0x1 PUSH1 0xA0 PUSH1 0x2 EXP SUB SWAP2 DUP3 AND PUSH1 0x0 SWAP1 DUP2 MSTORE PUSH1 0x1 PUSH1 0x20 SWAP1 DUP2 MSTORE PUSH1 0x40 DUP1 DUP4 KECCAK256 SWAP4 SWAP1 SWAP5 AND DUP3 MSTORE SWAP2 SWAP1 SWAP2 MSTORE KECCAK256 SLOAD SWAP1 JUMP STOP LOG1 PUSH6 0x627A7A723058 KECCAK256 PUSH23 0xDC710B0AF17D64BAA89FF5EE1254F48CAA2A16B273F7BA PUSH32 0x3BD9791573D3E600290000000000000000000000000000000000000000000000 ","runtimeBytecode":"6080604052600436106100985763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166306fdde03811461009d578063095ea7b31461012757806318160ddd1461015f57806323b872dd14610186578063313ce567146101b057806370a08231146101db57806395d89b41146101fc578063a9059cbb14610211578063dd62ed3e14610235575b600080fd5b3480156100a957600080fd5b506100b261025c565b6040805160208082528351818301528351919283929083019185019080838360005b838110156100ec5781810151838201526020016100d4565b50505050905090810190601f1680156101195780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561013357600080fd5b5061014b600160a060020a0360043516602435610293565b604080519115158252519081900360200190f35b34801561016b57600080fd5b506101746102fa565b60408051918252519081900360200190f35b34801561019257600080fd5b5061014b600160a060020a0360043581169060243516604435610300565b3480156101bc57600080fd5b506101c561042e565b6040805160ff9092168252519081900360200190f35b3480156101e757600080fd5b50610174600160a060020a0360043516610433565b34801561020857600080fd5b506100b261044e565b34801561021d57600080fd5b5061014b600160a060020a0360043516602435610485565b34801561024157600080fd5b50610174600160a060020a0360043581169060243516610536565b60408051808201909152601181527f30782050726f746f636f6c20546f6b656e000000000000000000000000000000602082015281565b336000818152600160209081526040808320600160a060020a038716808552908352818420869055815186815291519394909390927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925928290030190a35060015b92915050565b60035481565b600160a060020a03831660008181526001602090815260408083203384528252808320549383529082905281205490919083118015906103405750828110155b80156103665750600160a060020a03841660009081526020819052604090205483810110155b1561042157600160a060020a03808516600090815260208190526040808220805487019055918716815220805484900390556000198110156103cd57600160a060020a03851660009081526001602090815260408083203384529091529020805484900390555b83600160a060020a031685600160a060020a03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef856040518082815260200191505060405180910390a360019150610426565b600091505b509392505050565b601281565b600160a060020a031660009081526020819052604090205490565b60408051808201909152600381527f5a52580000000000000000000000000000000000000000000000000000000000602082015281565b3360009081526020819052604081205482118015906104be5750600160a060020a03831660009081526020819052604090205482810110155b1561052e573360008181526020818152604080832080548790039055600160a060020a03871680845292819020805487019055805186815290519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a35060016102f4565b5060006102f4565b600160a060020a039182166000908152600160209081526040808320939094168252919091522054905600a165627a7a7230582076dc710b0af17d64baa89ff5ee1254f48caa2a16b273f7ba7f3bd9791573d3e60029","srcmap":"3902:1269:0:-;;;4011:6;3985:32;;4203:65;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;4250:11:0;;4236:10;4227:8;:20;;;;;;;;;;:34;3902:1269;;;;;;","srcmapRuntime":"3902:1269:0:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4062:49;;8:9:-1;5:2;;;30:1;27;20:12;5:2;4062:49:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8:100:-1;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;4062:49:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3444:192;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;3444:192:0;-1:-1:-1;;;;;3444:192:0;;;;;;;;;;;;;;;;;;;;;;;;;3985:32;;8:9:-1;5:2;;;30:1;27;20:12;5:2;3985:32:0;;;;;;;;;;;;;;;;;;;;4555:614;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;4555:614:0;-1:-1:-1;;;;;4555:614:0;;;;;;;;;;;;3944:35;;8:9:-1;5:2;;;30:1;27;20:12;5:2;3944:35:0;;;;;;;;;;;;;;;;;;;;;;;3339:99;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;3339:99:0;-1:-1:-1;;;;;3339:99:0;;;;;4117:37;;8:9:-1;5:2;;;30:1;27;20:12;5:2;4117:37:0;;;;2472:415;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;2472:415:0;-1:-1:-1;;;;;2472:415:0;;;;;;;3642:126;;8:9:-1;5:2;;;30:1;27;20:12;5:2;-1:-1;3642:126:0;-1:-1:-1;;;;;3642:126:0;;;;;;;;;;4062:49;;;;;;;;;;;;;;;;;;;:::o;3444:192::-;3525:10;3501:4;3517:19;;;:7;:19;;;;;;;;-1:-1:-1;;;;;3517:29:0;;;;;;;;;;;:38;;;3570;;;;;;;3501:4;;3517:29;;3525:10;;3570:38;;;;;;;;-1:-1:-1;3625:4:0;3444:192;;;;;:::o;3985:32::-;;;;:::o;4555:614::-;-1:-1:-1;;;;;4687:14:0;;4650:4;4687:14;;;:7;:14;;;;;;;;4702:10;4687:26;;;;;;;;4727:15;;;;;;;;;;4650:4;;4687:26;4727:25;-1:-1:-1;4727:25:0;;;:60;;;4781:6;4768:9;:19;;4727:60;:115;;;;-1:-1:-1;;;;;;4829:13:0;;:8;:13;;;;;;;;;;;4803:22;;;:39;;4727:115;4723:440;;;-1:-1:-1;;;;;4867:13:0;;;:8;:13;;;;;;;;;;;:23;;;;;;4904:15;;;;;;:25;;;;;;;-1:-1:-1;;4947:20:0;;4943:95;;;-1:-1:-1;;;;;4987:14:0;;;;;;:7;:14;;;;;;;;5002:10;4987:26;;;;;;;:36;;;;;;;4943:95;5072:3;-1:-1:-1;;;;;5056:28:0;5065:5;-1:-1:-1;;;;;5056:28:0;;5077:6;5056:28;;;;;;;;;;;;;;;;;;5105:4;5098:11;;;;4723:440;5147:5;5140:12;;4723:440;4555:614;;;;;;:::o;3944:35::-;3977:2;3944:35;:::o;3339:99::-;-1:-1:-1;;;;;3415:16:0;3392:4;3415:16;;;;;;;;;;;;3339:99::o;4117:37::-;;;;;;;;;;;;;;;;;;;:::o;2472:415::-;2623:10;2525:4;2614:20;;;;;;;;;;;:30;-1:-1:-1;2614:30:0;;;:73;;-1:-1:-1;;;;;;2674:13:0;;:8;:13;;;;;;;;;;;2648:22;;;:39;;2614:73;2610:271;;;2712:10;2703:8;:20;;;;;;;;;;;:30;;;;;;;-1:-1:-1;;;;;2747:13:0;;;;;;;;;:23;;;;;;2789:33;;;;;;;2747:13;;2712:10;2789:33;;;;;;;;;;;-1:-1:-1;2843:4:0;2836:11;;2610:271;-1:-1:-1;2873:5:0;2866:12;;3642:126;-1:-1:-1;;;;;3736:15:0;;;3713:4;3736:15;;;:7;:15;;;;;;;;:25;;;;;;;;;;;;;3642:126::o"} --------------------------------------------------------------------------------