├── .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"}
--------------------------------------------------------------------------------