Merge branch 'development' into breakUp0xjs
* development: (38 commits) Add fallback image support to relayer grid tile Clear relayer grid state when fetching Configure the compiler to generate artifacts with deployedBytecode Implement loading and error state for relayer grid Fallback image for relayer grid tile Change relayer grid tile to link on header Display top tokens from backend Remove overflowZ property from portal Suggestions and fix bad merge Fix typo Only show untracked tokens Make wallet scrollable Add token flow Update The Ocean logo Fix artifacts paths Create an artifacts folder Introduce a var Add removeHexPrefix util method CHeck if ABI exists Improve the readability of the check for should compile ... # Conflicts: # .gitignore # packages/contracts/test/multi_sig_with_time_lock.ts # packages/contracts/test/multi_sig_with_time_lock_except_remove_auth_addr.ts # packages/contracts/util/artifacts.ts
This commit is contained in:
commit
cd5f00ac4d
2
.gitignore
vendored
2
.gitignore
vendored
@ -79,7 +79,7 @@ packages/metacoin/artifacts
|
|||||||
packages/0x.js/test/artifacts
|
packages/0x.js/test/artifacts
|
||||||
packages/order-watcher/test/artifacts
|
packages/order-watcher/test/artifacts
|
||||||
packages/contract-wrappers/test/artifacts
|
packages/contract-wrappers/test/artifacts
|
||||||
packages/migrations/src/artifacts
|
packages/migrations/artifacts
|
||||||
|
|
||||||
# generated contract wrappers
|
# generated contract wrappers
|
||||||
packages/0x.js/src/contract_wrappers/generated/
|
packages/0x.js/src/contract_wrappers/generated/
|
||||||
|
@ -2,6 +2,6 @@ lib
|
|||||||
.nyc_output
|
.nyc_output
|
||||||
/packages/contracts/src/artifacts
|
/packages/contracts/src/artifacts
|
||||||
/packages/metacoin/artifacts
|
/packages/metacoin/artifacts
|
||||||
/packages/migrations/src/artifacts
|
/packages/migrations/artifacts/1.0.0
|
||||||
package.json
|
package.json
|
||||||
scripts/postpublish_utils.js
|
scripts/postpublish_utils.js
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
"test": "run-s clean test:commonjs",
|
"test": "run-s clean test:commonjs",
|
||||||
"test:coverage": "nyc npm run test --all && yarn coverage:report:lcov",
|
"test:coverage": "nyc npm run test --all && yarn coverage:report:lcov",
|
||||||
"coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info",
|
"coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info",
|
||||||
"update_artifacts": "for i in ${npm_package_config_contracts}; do copyfiles -u 4 ../migrations/src/artifacts/$i.json test/artifacts; done;",
|
"update_artifacts": "for i in ${npm_package_config_contracts}; do copyfiles -u 4 ../migrations/artifacts/1.0.0/$i.json test/artifacts; done;",
|
||||||
"clean": "shx rm -rf _bundles lib test_temp scripts",
|
"clean": "shx rm -rf _bundles lib test_temp scripts",
|
||||||
"build:umd:prod": "NODE_ENV=production webpack",
|
"build:umd:prod": "NODE_ENV=production webpack",
|
||||||
"build:commonjs": "tsc && yarn update_artifacts && copyfiles -u 2 './src/compact_artifacts/**/*.json' ./lib/src/compact_artifacts && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts",
|
"build:commonjs": "tsc && yarn update_artifacts && copyfiles -u 2 './src/compact_artifacts/**/*.json' ./lib/src/compact_artifacts && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts",
|
||||||
|
@ -108,8 +108,8 @@ for (const abiFileName of abiFileNames) {
|
|||||||
ABI = parsedContent; // ABI file
|
ABI = parsedContent; // ABI file
|
||||||
} else if (!_.isUndefined(parsedContent.abi)) {
|
} else if (!_.isUndefined(parsedContent.abi)) {
|
||||||
ABI = parsedContent.abi; // Truffle artifact
|
ABI = parsedContent.abi; // Truffle artifact
|
||||||
} else if (!_.isUndefined(parsedContent.networks) && !_.isUndefined(parsedContent.networks[args.networkId])) {
|
} else if (!_.isUndefined(parsedContent.compilerOutput.abi)) {
|
||||||
ABI = parsedContent.networks[args.networkId].abi; // 0x contracts package artifact
|
ABI = parsedContent.compilerOutput.abi; // 0x artifact
|
||||||
}
|
}
|
||||||
if (_.isUndefined(ABI)) {
|
if (_.isUndefined(ABI)) {
|
||||||
logUtils.log(`${chalk.red(`ABI not found in ${abiFileName}.`)}`);
|
logUtils.log(`${chalk.red(`ABI not found in ${abiFileName}.`)}`);
|
||||||
|
33
packages/contracts/compiler.json
Normal file
33
packages/contracts/compiler.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"artifactsDir": "../migrations/artifacts/1.0.0",
|
||||||
|
"contractsDir": "src/contracts",
|
||||||
|
"compilerSettings": {
|
||||||
|
"outputSelection": {
|
||||||
|
"*": {
|
||||||
|
"*": [
|
||||||
|
"abi",
|
||||||
|
"evm.bytecode.object",
|
||||||
|
"evm.bytecode.sourceMap",
|
||||||
|
"evm.deployedBytecode.object",
|
||||||
|
"evm.deployedBytecode.sourceMap"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contracts": [
|
||||||
|
"Exchange",
|
||||||
|
"DummyToken",
|
||||||
|
"ZRXToken",
|
||||||
|
"Token",
|
||||||
|
"WETH9",
|
||||||
|
"TokenTransferProxy",
|
||||||
|
"MultiSigWallet",
|
||||||
|
"MultiSigWalletWithTimeLock",
|
||||||
|
"MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress",
|
||||||
|
"MaliciousToken",
|
||||||
|
"TokenRegistry",
|
||||||
|
"Arbitrage",
|
||||||
|
"EtherDelta",
|
||||||
|
"AccountLevels"
|
||||||
|
]
|
||||||
|
}
|
@ -10,13 +10,13 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build:watch": "tsc -w",
|
"build:watch": "tsc -w",
|
||||||
"prebuild": "run-s clean compile copy_artifacts generate_contract_wrappers",
|
"prebuild": "run-s clean compile copy_artifacts generate_contract_wrappers",
|
||||||
"copy_artifacts": "copyfiles -u 4 '../migrations/src/artifacts/**/*' ./lib/src/artifacts;",
|
"copy_artifacts": "copyfiles -u 4 '../migrations/artifacts/1.0.0/**/*' ./lib/src/artifacts;",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"test": "run-s build run_mocha",
|
"test": "run-s build run_mocha",
|
||||||
"test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov",
|
"test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov",
|
||||||
"run_mocha": "mocha 'lib/test/**/*.js' --timeout 100000 --bail --exit",
|
"run_mocha": "mocha 'lib/test/**/*.js' --timeout 100000 --bail --exit",
|
||||||
"compile:comment": "Yarn workspaces do not link binaries correctly so we need to reference them directly https://github.com/yarnpkg/yarn/issues/3846",
|
"compile:comment": "Yarn workspaces do not link binaries correctly so we need to reference them directly https://github.com/yarnpkg/yarn/issues/3846",
|
||||||
"compile": "node ../deployer/lib/src/cli.js compile --contracts ${npm_package_config_contracts} --contracts-dir src/contracts --artifacts-dir ../migrations/src/artifacts",
|
"compile": "node ../deployer/lib/src/cli.js compile",
|
||||||
"clean": "shx rm -rf ./lib",
|
"clean": "shx rm -rf ./lib",
|
||||||
"generate_contract_wrappers": "node ../abi-gen/lib/index.js --abis ${npm_package_config_abis} --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated --backend ethers && prettier --write 'src/contract_wrappers/generated/**.ts'",
|
"generate_contract_wrappers": "node ../abi-gen/lib/index.js --abis ${npm_package_config_abis} --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/contract_wrappers/generated --backend ethers && prettier --write 'src/contract_wrappers/generated/**.ts'",
|
||||||
"lint": "tslint --project . 'migrations/**/*.ts' 'test/**/*.ts' 'util/**/*.ts' 'deploy/**/*.ts'",
|
"lint": "tslint --project . 'migrations/**/*.ts' 'test/**/*.ts' 'util/**/*.ts' 'deploy/**/*.ts'",
|
||||||
@ -26,8 +26,7 @@
|
|||||||
"test:circleci": "yarn test:coverage"
|
"test:circleci": "yarn test:coverage"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"abis": "../migrations/src/artifacts/@(DummyToken|TokenTransferProxy|Exchange|TokenRegistry|MultiSigWallet|MultiSigWalletWithTimeLock|MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress|TokenRegistry|ZRXToken|Arbitrage|EtherDelta|AccountLevels).json",
|
"abis": "../migrations/artifacts/1.0.0/@(DummyToken|TokenTransferProxy|Exchange|TokenRegistry|MultiSigWallet|MultiSigWalletWithTimeLock|MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress|TokenRegistry|ZRXToken|Arbitrage|EtherDelta|AccountLevels).json"
|
||||||
"contracts": "Exchange,DummyToken,ZRXToken,Token,WETH9,TokenTransferProxy,MultiSigWallet,MultiSigWalletWithTimeLock,MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress,MaliciousToken,TokenRegistry,Arbitrage,EtherDelta,AccountLevels"
|
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -17,7 +17,7 @@ import { chaiSetup } from './utils/chai_setup';
|
|||||||
import { deployer } from './utils/deployer';
|
import { deployer } from './utils/deployer';
|
||||||
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
||||||
|
|
||||||
const MULTI_SIG_ABI = artifacts.MultiSigWalletWithTimeLock.networks[constants.TESTRPC_NETWORK_ID].abi;
|
const MULTI_SIG_ABI = artifacts.MultiSigWalletWithTimeLock.compilerOutput.abi;
|
||||||
chaiSetup.configure();
|
chaiSetup.configure();
|
||||||
const expect = chai.expect;
|
const expect = chai.expect;
|
||||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||||
|
@ -17,10 +17,9 @@ import { ContractName, SubmissionContractEventArgs, TransactionDataParams } from
|
|||||||
import { chaiSetup } from './utils/chai_setup';
|
import { chaiSetup } from './utils/chai_setup';
|
||||||
import { deployer } from './utils/deployer';
|
import { deployer } from './utils/deployer';
|
||||||
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
import { provider, web3Wrapper } from './utils/web3_wrapper';
|
||||||
const PROXY_ABI = artifacts.TokenTransferProxy.networks[constants.TESTRPC_NETWORK_ID].abi;
|
const PROXY_ABI = artifacts.TokenTransferProxy.compilerOutput.abi;
|
||||||
const MUTISIG_WALLET_WITH_TIME_LOCK_EXCEPT_REMOVE_AUTHORIZED_ADDRESS_ABI =
|
const MUTISIG_WALLET_WITH_TIME_LOCK_EXCEPT_REMOVE_AUTHORIZED_ADDRESS_ABI =
|
||||||
artifacts.MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress.networks[constants.TESTRPC_NETWORK_ID]
|
artifacts.MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress.compilerOutput.abi;
|
||||||
.abi;
|
|
||||||
|
|
||||||
chaiSetup.configure();
|
chaiSetup.configure();
|
||||||
const expect = chai.expect;
|
const expect = chai.expect;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { ContractArtifact } from '@0xproject/deployer';
|
||||||
|
|
||||||
import * as DummyToken from '../src/artifacts/DummyToken.json';
|
import * as DummyToken from '../src/artifacts/DummyToken.json';
|
||||||
import * as Exchange from '../src/artifacts/Exchange.json';
|
import * as Exchange from '../src/artifacts/Exchange.json';
|
||||||
import * as MaliciousToken from '../src/artifacts/MaliciousToken.json';
|
import * as MaliciousToken from '../src/artifacts/MaliciousToken.json';
|
||||||
@ -9,17 +11,15 @@ import * as TokenTransferProxy from '../src/artifacts/TokenTransferProxy.json';
|
|||||||
import * as EtherToken from '../src/artifacts/WETH9.json';
|
import * as EtherToken from '../src/artifacts/WETH9.json';
|
||||||
import * as ZRX from '../src/artifacts/ZRXToken.json';
|
import * as ZRX from '../src/artifacts/ZRXToken.json';
|
||||||
|
|
||||||
import { Artifact } from './types';
|
|
||||||
|
|
||||||
export const artifacts = {
|
export const artifacts = {
|
||||||
ZRX: (ZRX as any) as Artifact,
|
ZRX: (ZRX as any) as ContractArtifact,
|
||||||
DummyToken: (DummyToken as any) as Artifact,
|
DummyToken: (DummyToken as any) as ContractArtifact,
|
||||||
Token: (Token as any) as Artifact,
|
Token: (Token as any) as ContractArtifact,
|
||||||
Exchange: (Exchange as any) as Artifact,
|
Exchange: (Exchange as any) as ContractArtifact,
|
||||||
EtherToken: (EtherToken as any) as Artifact,
|
EtherToken: (EtherToken as any) as ContractArtifact,
|
||||||
TokenRegistry: (TokenRegistry as any) as Artifact,
|
TokenRegistry: (TokenRegistry as any) as ContractArtifact,
|
||||||
MaliciousToken: (MaliciousToken as any) as Artifact,
|
MaliciousToken: (MaliciousToken as any) as ContractArtifact,
|
||||||
TokenTransferProxy: (TokenTransferProxy as any) as Artifact,
|
TokenTransferProxy: (TokenTransferProxy as any) as ContractArtifact,
|
||||||
MultiSigWalletWithTimeLock: (MultiSigWalletWithTimeLock as any) as Artifact,
|
MultiSigWalletWithTimeLock: (MultiSigWalletWithTimeLock as any) as ContractArtifact,
|
||||||
MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress: (MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress as any) as Artifact,
|
MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress: (MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress as any) as ContractArtifact,
|
||||||
};
|
};
|
||||||
|
@ -100,19 +100,3 @@ export enum ContractName {
|
|||||||
EtherDelta = 'EtherDelta',
|
EtherDelta = 'EtherDelta',
|
||||||
Arbitrage = 'Arbitrage',
|
Arbitrage = 'Arbitrage',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Artifact {
|
|
||||||
contract_name: ContractName;
|
|
||||||
networks: {
|
|
||||||
[networkId: number]: {
|
|
||||||
abi: ContractAbi;
|
|
||||||
solc_version: string;
|
|
||||||
keccak256: string;
|
|
||||||
optimizer_enabled: number;
|
|
||||||
unlinked_binary: string;
|
|
||||||
updated_at: number;
|
|
||||||
address: string;
|
|
||||||
constructor_args: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
@ -13,13 +13,13 @@ import { constants } from './utils/constants';
|
|||||||
import { consoleReporter } from './utils/error_reporter';
|
import { consoleReporter } from './utils/error_reporter';
|
||||||
import { CliOptions, CompilerOptions, DeployerOptions } from './utils/types';
|
import { CliOptions, CompilerOptions, DeployerOptions } from './utils/types';
|
||||||
|
|
||||||
const DEFAULT_OPTIMIZER_ENABLED = false;
|
|
||||||
const DEFAULT_CONTRACTS_DIR = path.resolve('src/contracts');
|
const DEFAULT_CONTRACTS_DIR = path.resolve('src/contracts');
|
||||||
const DEFAULT_ARTIFACTS_DIR = path.resolve('src/artifacts');
|
const DEFAULT_ARTIFACTS_DIR = path.resolve('src/artifacts');
|
||||||
const DEFAULT_NETWORK_ID = 50;
|
const DEFAULT_NETWORK_ID = 50;
|
||||||
const DEFAULT_JSONRPC_URL = 'http://localhost:8545';
|
const DEFAULT_JSONRPC_URL = 'http://localhost:8545';
|
||||||
const DEFAULT_GAS_PRICE = (10 ** 9 * 2).toString();
|
const DEFAULT_GAS_PRICE = (10 ** 9 * 2).toString();
|
||||||
const DEFAULT_CONTRACTS_LIST = '*';
|
const DEFAULT_CONTRACTS_LIST = '*';
|
||||||
|
const SEPARATOR = ',';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compiles all contracts with options passed in through CLI.
|
* Compiles all contracts with options passed in through CLI.
|
||||||
@ -28,10 +28,8 @@ const DEFAULT_CONTRACTS_LIST = '*';
|
|||||||
async function onCompileCommandAsync(argv: CliOptions): Promise<void> {
|
async function onCompileCommandAsync(argv: CliOptions): Promise<void> {
|
||||||
const opts: CompilerOptions = {
|
const opts: CompilerOptions = {
|
||||||
contractsDir: argv.contractsDir,
|
contractsDir: argv.contractsDir,
|
||||||
networkId: argv.networkId,
|
|
||||||
optimizerEnabled: argv.shouldOptimize,
|
|
||||||
artifactsDir: argv.artifactsDir,
|
artifactsDir: argv.artifactsDir,
|
||||||
specifiedContracts: getContractsSetFromList(argv.contracts),
|
contracts: argv.contracts === DEFAULT_CONTRACTS_LIST ? DEFAULT_CONTRACTS_LIST : argv.contracts.split(SEPARATOR),
|
||||||
};
|
};
|
||||||
await commands.compileAsync(opts);
|
await commands.compileAsync(opts);
|
||||||
}
|
}
|
||||||
@ -46,10 +44,8 @@ async function onDeployCommandAsync(argv: CliOptions): Promise<void> {
|
|||||||
const networkId = await web3Wrapper.getNetworkIdAsync();
|
const networkId = await web3Wrapper.getNetworkIdAsync();
|
||||||
const compilerOpts: CompilerOptions = {
|
const compilerOpts: CompilerOptions = {
|
||||||
contractsDir: argv.contractsDir,
|
contractsDir: argv.contractsDir,
|
||||||
networkId,
|
|
||||||
optimizerEnabled: argv.shouldOptimize,
|
|
||||||
artifactsDir: argv.artifactsDir,
|
artifactsDir: argv.artifactsDir,
|
||||||
specifiedContracts: getContractsSetFromList(argv.contracts),
|
contracts: argv.contracts === DEFAULT_CONTRACTS_LIST ? DEFAULT_CONTRACTS_LIST : argv.contracts.split(SEPARATOR),
|
||||||
};
|
};
|
||||||
await commands.compileAsync(compilerOpts);
|
await commands.compileAsync(compilerOpts);
|
||||||
|
|
||||||
@ -58,45 +54,63 @@ async function onDeployCommandAsync(argv: CliOptions): Promise<void> {
|
|||||||
from: argv.account,
|
from: argv.account,
|
||||||
};
|
};
|
||||||
const deployerOpts: DeployerOptions = {
|
const deployerOpts: DeployerOptions = {
|
||||||
artifactsDir: argv.artifactsDir,
|
artifactsDir: argv.artifactsDir || DEFAULT_ARTIFACTS_DIR,
|
||||||
jsonrpcUrl: argv.jsonrpcUrl,
|
jsonrpcUrl: argv.jsonrpcUrl,
|
||||||
networkId,
|
networkId,
|
||||||
defaults,
|
defaults,
|
||||||
};
|
};
|
||||||
const deployerArgsString = argv.args as string;
|
const deployerArgsString = argv.constructorArgs as string;
|
||||||
const deployerArgs = deployerArgsString.split(',');
|
const deployerArgs = deployerArgsString.split(SEPARATOR);
|
||||||
await commands.deployAsync(argv.contract as string, deployerArgs, deployerOpts);
|
await commands.deployAsync(argv.contract as string, deployerArgs, deployerOpts);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Creates a set of contracts to compile.
|
* Adds additional required options for when the user is calling the deploy command.
|
||||||
* @param contracts Comma separated list of contracts to compile
|
|
||||||
*/
|
|
||||||
function getContractsSetFromList(contracts: string): Set<string> {
|
|
||||||
const specifiedContracts = new Set();
|
|
||||||
if (contracts === '*') {
|
|
||||||
return new Set(['*']);
|
|
||||||
}
|
|
||||||
const contractsArray = contracts.split(',');
|
|
||||||
_.forEach(contractsArray, contractName => {
|
|
||||||
specifiedContracts.add(contractName);
|
|
||||||
});
|
|
||||||
return specifiedContracts;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Provides extra required options for deploy command.
|
|
||||||
* @param yargsInstance yargs instance provided in builder function callback.
|
* @param yargsInstance yargs instance provided in builder function callback.
|
||||||
*/
|
*/
|
||||||
function deployCommandBuilder(yargsInstance: any) {
|
function deployCommandBuilder(yargsInstance: any) {
|
||||||
return yargsInstance
|
return yargsInstance
|
||||||
|
.option('network-id', {
|
||||||
|
type: 'number',
|
||||||
|
default: DEFAULT_NETWORK_ID,
|
||||||
|
description: 'mainnet=1, kovan=42, testrpc=50',
|
||||||
|
})
|
||||||
.option('contract', {
|
.option('contract', {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'name of contract to deploy, exluding .sol extension',
|
description: 'name of contract to deploy, excluding .sol extension',
|
||||||
})
|
})
|
||||||
.option('args', {
|
.option('constructor-args', {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'comma separated list of constructor args to deploy contract with',
|
description: 'comma separated list of constructor args to deploy contract with',
|
||||||
})
|
})
|
||||||
.demandOption(['contract', 'args'])
|
.option('jsonrpc-url', {
|
||||||
|
type: 'string',
|
||||||
|
default: DEFAULT_JSONRPC_URL,
|
||||||
|
description: 'url of JSON RPC',
|
||||||
|
})
|
||||||
|
.option('account', {
|
||||||
|
type: 'string',
|
||||||
|
description: 'account to use for deploying contracts',
|
||||||
|
})
|
||||||
|
.option('gas-price', {
|
||||||
|
type: 'string',
|
||||||
|
default: DEFAULT_GAS_PRICE,
|
||||||
|
description: 'gasPrice to be used for transactions',
|
||||||
|
})
|
||||||
|
.demandOption(['contract', 'args', 'account'])
|
||||||
|
.help().argv;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds additional required options for when the user is calling the compile command.
|
||||||
|
* @param yargsInstance yargs instance provided in builder function callback.
|
||||||
|
*/
|
||||||
|
function compileCommandBuilder(yargsInstance: any) {
|
||||||
|
return yargsInstance
|
||||||
|
.option('contracts', {
|
||||||
|
type: 'string',
|
||||||
|
default: DEFAULT_CONTRACTS_LIST,
|
||||||
|
description: 'comma separated list of contracts to compile',
|
||||||
|
})
|
||||||
.help().argv;
|
.help().argv;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,44 +119,14 @@ function deployCommandBuilder(yargsInstance: any) {
|
|||||||
return yargs
|
return yargs
|
||||||
.option('contracts-dir', {
|
.option('contracts-dir', {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: DEFAULT_CONTRACTS_DIR,
|
|
||||||
description: 'path of contracts directory to compile',
|
description: 'path of contracts directory to compile',
|
||||||
})
|
})
|
||||||
.option('network-id', {
|
|
||||||
type: 'number',
|
|
||||||
default: DEFAULT_NETWORK_ID,
|
|
||||||
description: 'mainnet=1, kovan=42, testrpc=50',
|
|
||||||
})
|
|
||||||
.option('should-optimize', {
|
|
||||||
type: 'boolean',
|
|
||||||
default: DEFAULT_OPTIMIZER_ENABLED,
|
|
||||||
description: 'enable optimizer',
|
|
||||||
})
|
|
||||||
.option('artifacts-dir', {
|
.option('artifacts-dir', {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: DEFAULT_ARTIFACTS_DIR,
|
|
||||||
description: 'path to write contracts artifacts to',
|
description: 'path to write contracts artifacts to',
|
||||||
})
|
})
|
||||||
.option('jsonrpc-url', {
|
.demandCommand(1)
|
||||||
type: 'string',
|
.command('compile', 'compile contracts', compileCommandBuilder, consoleReporter(onCompileCommandAsync))
|
||||||
default: DEFAULT_JSONRPC_URL,
|
|
||||||
description: 'url of JSON RPC',
|
|
||||||
})
|
|
||||||
.option('gas-price', {
|
|
||||||
type: 'string',
|
|
||||||
default: DEFAULT_GAS_PRICE,
|
|
||||||
description: 'gasPrice to be used for transactions',
|
|
||||||
})
|
|
||||||
.option('account', {
|
|
||||||
type: 'string',
|
|
||||||
description: 'account to use for deploying contracts',
|
|
||||||
})
|
|
||||||
.option('contracts', {
|
|
||||||
type: 'string',
|
|
||||||
default: DEFAULT_CONTRACTS_LIST,
|
|
||||||
description: 'comma separated list of contracts to compile',
|
|
||||||
})
|
|
||||||
.command('compile', 'compile contracts', identityCommandBuilder, consoleReporter(onCompileCommandAsync))
|
|
||||||
.command(
|
.command(
|
||||||
'deploy',
|
'deploy',
|
||||||
'deploy a single contract with provided arguments',
|
'deploy a single contract with provided arguments',
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
FSResolver,
|
FSResolver,
|
||||||
NameResolver,
|
NameResolver,
|
||||||
NPMResolver,
|
NPMResolver,
|
||||||
|
RelativeFSResolver,
|
||||||
Resolver,
|
Resolver,
|
||||||
URLResolver,
|
URLResolver,
|
||||||
} from '@0xproject/sol-resolver';
|
} from '@0xproject/sol-resolver';
|
||||||
@ -38,11 +39,30 @@ import {
|
|||||||
ContractNetworks,
|
ContractNetworks,
|
||||||
ContractSourceData,
|
ContractSourceData,
|
||||||
ContractSpecificSourceData,
|
ContractSpecificSourceData,
|
||||||
|
ContractVersionData,
|
||||||
} from './utils/types';
|
} from './utils/types';
|
||||||
import { utils } from './utils/utils';
|
import { utils } from './utils/utils';
|
||||||
|
|
||||||
|
type TYPE_ALL_FILES_IDENTIFIER = '*';
|
||||||
const ALL_CONTRACTS_IDENTIFIER = '*';
|
const ALL_CONTRACTS_IDENTIFIER = '*';
|
||||||
|
const ALL_FILES_IDENTIFIER = '*';
|
||||||
const SOLC_BIN_DIR = path.join(__dirname, '..', '..', 'solc_bin');
|
const SOLC_BIN_DIR = path.join(__dirname, '..', '..', 'solc_bin');
|
||||||
|
const DEFAULT_CONTRACTS_DIR = path.resolve('contracts');
|
||||||
|
const DEFAULT_ARTIFACTS_DIR = path.resolve('artifacts');
|
||||||
|
// Solc compiler settings cannot be configured from the commandline.
|
||||||
|
// If you need this configured, please create a `compiler.json` config file
|
||||||
|
// with your desired configurations.
|
||||||
|
const DEFAULT_COMPILER_SETTINGS: solc.CompilerSettings = {
|
||||||
|
optimizer: {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
outputSelection: {
|
||||||
|
[ALL_FILES_IDENTIFIER]: {
|
||||||
|
[ALL_CONTRACTS_IDENTIFIER]: ['abi', 'evm.bytecode.object'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const CONFIG_FILE = 'compiler.json';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Compiler facilitates compiling Solidity smart contracts and saves the results
|
* The Compiler facilitates compiling Solidity smart contracts and saves the results
|
||||||
@ -52,26 +72,28 @@ export class Compiler {
|
|||||||
private _resolver: Resolver;
|
private _resolver: Resolver;
|
||||||
private _nameResolver: NameResolver;
|
private _nameResolver: NameResolver;
|
||||||
private _contractsDir: string;
|
private _contractsDir: string;
|
||||||
private _networkId: number;
|
private _compilerSettings: solc.CompilerSettings;
|
||||||
private _optimizerEnabled: boolean;
|
|
||||||
private _artifactsDir: string;
|
private _artifactsDir: string;
|
||||||
private _specifiedContracts: Set<string> = new Set();
|
private _specifiedContracts: string[] | TYPE_ALL_FILES_IDENTIFIER;
|
||||||
/**
|
/**
|
||||||
* Instantiates a new instance of the Compiler class.
|
* Instantiates a new instance of the Compiler class.
|
||||||
* @param opts Options specifying directories, network, and optimization settings.
|
|
||||||
* @return An instance of the Compiler class.
|
* @return An instance of the Compiler class.
|
||||||
*/
|
*/
|
||||||
constructor(opts: CompilerOptions) {
|
constructor(opts: CompilerOptions) {
|
||||||
this._contractsDir = opts.contractsDir;
|
// TODO: Look for config file in parent directories if not found in current directory
|
||||||
this._networkId = opts.networkId;
|
const config: CompilerOptions = fs.existsSync(CONFIG_FILE)
|
||||||
this._optimizerEnabled = opts.optimizerEnabled;
|
? JSON.parse(fs.readFileSync(CONFIG_FILE).toString())
|
||||||
this._artifactsDir = opts.artifactsDir;
|
: {};
|
||||||
this._specifiedContracts = opts.specifiedContracts;
|
this._contractsDir = opts.contractsDir || config.contractsDir || DEFAULT_CONTRACTS_DIR;
|
||||||
|
this._compilerSettings = opts.compilerSettings || config.compilerSettings || DEFAULT_COMPILER_SETTINGS;
|
||||||
|
this._artifactsDir = opts.artifactsDir || config.artifactsDir || DEFAULT_ARTIFACTS_DIR;
|
||||||
|
this._specifiedContracts = opts.contracts || config.contracts || ALL_CONTRACTS_IDENTIFIER;
|
||||||
this._nameResolver = new NameResolver(path.resolve(this._contractsDir));
|
this._nameResolver = new NameResolver(path.resolve(this._contractsDir));
|
||||||
const resolver = new FallthroughResolver();
|
const resolver = new FallthroughResolver();
|
||||||
resolver.appendResolver(new URLResolver());
|
resolver.appendResolver(new URLResolver());
|
||||||
const packagePath = path.resolve('');
|
const packagePath = path.resolve('');
|
||||||
resolver.appendResolver(new NPMResolver(packagePath));
|
resolver.appendResolver(new NPMResolver(packagePath));
|
||||||
|
resolver.appendResolver(new RelativeFSResolver(this._contractsDir));
|
||||||
resolver.appendResolver(new FSResolver());
|
resolver.appendResolver(new FSResolver());
|
||||||
resolver.appendResolver(this._nameResolver);
|
resolver.appendResolver(this._nameResolver);
|
||||||
this._resolver = resolver;
|
this._resolver = resolver;
|
||||||
@ -83,13 +105,13 @@ export class Compiler {
|
|||||||
await createDirIfDoesNotExistAsync(this._artifactsDir);
|
await createDirIfDoesNotExistAsync(this._artifactsDir);
|
||||||
await createDirIfDoesNotExistAsync(SOLC_BIN_DIR);
|
await createDirIfDoesNotExistAsync(SOLC_BIN_DIR);
|
||||||
let contractNamesToCompile: string[] = [];
|
let contractNamesToCompile: string[] = [];
|
||||||
if (this._specifiedContracts.has(ALL_CONTRACTS_IDENTIFIER)) {
|
if (this._specifiedContracts === ALL_CONTRACTS_IDENTIFIER) {
|
||||||
const allContracts = this._nameResolver.getAll();
|
const allContracts = this._nameResolver.getAll();
|
||||||
contractNamesToCompile = _.map(allContracts, contractSource =>
|
contractNamesToCompile = _.map(allContracts, contractSource =>
|
||||||
path.basename(contractSource.path, constants.SOLIDITY_FILE_EXTENSION),
|
path.basename(contractSource.path, constants.SOLIDITY_FILE_EXTENSION),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
contractNamesToCompile = Array.from(this._specifiedContracts.values());
|
contractNamesToCompile = this._specifiedContracts;
|
||||||
}
|
}
|
||||||
for (const contractNameToCompile of contractNamesToCompile) {
|
for (const contractNameToCompile of contractNamesToCompile) {
|
||||||
await this._compileContractAsync(contractNameToCompile);
|
await this._compileContractAsync(contractNameToCompile);
|
||||||
@ -101,17 +123,18 @@ export class Compiler {
|
|||||||
*/
|
*/
|
||||||
private async _compileContractAsync(contractName: string): Promise<void> {
|
private async _compileContractAsync(contractName: string): Promise<void> {
|
||||||
const contractSource = this._resolver.resolve(contractName);
|
const contractSource = this._resolver.resolve(contractName);
|
||||||
|
const absoluteContractPath = path.join(this._contractsDir, contractSource.path);
|
||||||
const currentArtifactIfExists = await getContractArtifactIfExistsAsync(this._artifactsDir, contractName);
|
const currentArtifactIfExists = await getContractArtifactIfExistsAsync(this._artifactsDir, contractName);
|
||||||
const sourceTreeHashHex = `0x${this._getSourceTreeHash(contractSource.path).toString('hex')}`;
|
const sourceTreeHashHex = `0x${this._getSourceTreeHash(absoluteContractPath).toString('hex')}`;
|
||||||
|
|
||||||
let shouldCompile = false;
|
let shouldCompile = false;
|
||||||
if (_.isUndefined(currentArtifactIfExists)) {
|
if (_.isUndefined(currentArtifactIfExists)) {
|
||||||
shouldCompile = true;
|
shouldCompile = true;
|
||||||
} else {
|
} else {
|
||||||
const currentArtifact = currentArtifactIfExists as ContractArtifact;
|
const currentArtifact = currentArtifactIfExists as ContractArtifact;
|
||||||
shouldCompile =
|
const isUserOnLatestVersion = currentArtifact.schemaVersion === constants.LATEST_ARTIFACT_VERSION;
|
||||||
currentArtifact.networks[this._networkId].optimizer_enabled !== this._optimizerEnabled ||
|
const didCompilerSettingsChange = !_.isEqual(currentArtifact.compiler.settings, this._compilerSettings);
|
||||||
currentArtifact.networks[this._networkId].source_tree_hash !== sourceTreeHashHex;
|
const didSourceChange = currentArtifact.sourceTreeHashHex !== sourceTreeHashHex;
|
||||||
|
shouldCompile = isUserOnLatestVersion || didCompilerSettingsChange || didSourceChange;
|
||||||
}
|
}
|
||||||
if (!shouldCompile) {
|
if (!shouldCompile) {
|
||||||
return;
|
return;
|
||||||
@ -139,30 +162,14 @@ export class Compiler {
|
|||||||
|
|
||||||
logUtils.log(`Compiling ${contractName} with Solidity v${solcVersion}...`);
|
logUtils.log(`Compiling ${contractName} with Solidity v${solcVersion}...`);
|
||||||
const source = contractSource.source;
|
const source = contractSource.source;
|
||||||
const absoluteFilePath = contractSource.path;
|
|
||||||
const standardInput: solc.StandardInput = {
|
const standardInput: solc.StandardInput = {
|
||||||
language: 'Solidity',
|
language: 'Solidity',
|
||||||
sources: {
|
sources: {
|
||||||
[absoluteFilePath]: {
|
[contractSource.path]: {
|
||||||
urls: [`file://${absoluteFilePath}`],
|
content: contractSource.source,
|
||||||
},
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
optimizer: {
|
|
||||||
enabled: this._optimizerEnabled,
|
|
||||||
},
|
|
||||||
outputSelection: {
|
|
||||||
'*': {
|
|
||||||
'*': [
|
|
||||||
'abi',
|
|
||||||
'evm.bytecode.object',
|
|
||||||
'evm.bytecode.sourceMap',
|
|
||||||
'evm.deployedBytecode.object',
|
|
||||||
'evm.deployedBytecode.sourceMap',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
settings: this._compilerSettings,
|
||||||
};
|
};
|
||||||
const compiled: solc.StandardOutput = JSON.parse(
|
const compiled: solc.StandardOutput = JSON.parse(
|
||||||
solcInstance.compileStandardWrapper(JSON.stringify(standardInput), importPath => {
|
solcInstance.compileStandardWrapper(JSON.stringify(standardInput), importPath => {
|
||||||
@ -188,34 +195,28 @@ export class Compiler {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const compiledData = compiled.contracts[absoluteFilePath][contractName];
|
const compiledData = compiled.contracts[contractSource.path][contractName];
|
||||||
if (_.isUndefined(compiledData)) {
|
if (_.isUndefined(compiledData)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Contract ${contractName} not found in ${absoluteFilePath}. Please make sure your contract has the same name as it's file name`,
|
`Contract ${contractName} not found in ${
|
||||||
|
contractSource.path
|
||||||
|
}. Please make sure your contract has the same name as it's file name`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const abi: ContractAbi = compiledData.abi;
|
const sourceCodes = _.mapValues(
|
||||||
const bytecode = `0x${compiledData.evm.bytecode.object}`;
|
compiled.sources,
|
||||||
const runtimeBytecode = `0x${compiledData.evm.deployedBytecode.object}`;
|
(_1, sourceFilePath) => this._resolver.resolve(sourceFilePath).source,
|
||||||
const sourceMap = compiledData.evm.bytecode.sourceMap;
|
|
||||||
const sourceMapRuntime = compiledData.evm.deployedBytecode.sourceMap;
|
|
||||||
const unresolvedSourcePaths = _.keys(compiled.sources);
|
|
||||||
const sources = _.map(
|
|
||||||
unresolvedSourcePaths,
|
|
||||||
unresolvedSourcePath => this._resolver.resolve(unresolvedSourcePath).path,
|
|
||||||
);
|
);
|
||||||
const updated_at = Date.now();
|
const contractVersion: ContractVersionData = {
|
||||||
const contractNetworkData: ContractNetworkData = {
|
compilerOutput: compiledData,
|
||||||
solc_version: solcVersion,
|
sources: compiled.sources,
|
||||||
source_tree_hash: sourceTreeHashHex,
|
sourceCodes,
|
||||||
optimizer_enabled: this._optimizerEnabled,
|
sourceTreeHashHex,
|
||||||
abi,
|
compiler: {
|
||||||
bytecode,
|
name: 'solc',
|
||||||
runtime_bytecode: runtimeBytecode,
|
version: solcVersion,
|
||||||
updated_at,
|
settings: this._compilerSettings,
|
||||||
source_map: sourceMap,
|
},
|
||||||
source_map_runtime: sourceMapRuntime,
|
|
||||||
sources,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let newArtifact: ContractArtifact;
|
let newArtifact: ContractArtifact;
|
||||||
@ -223,17 +224,14 @@ export class Compiler {
|
|||||||
const currentArtifact = currentArtifactIfExists as ContractArtifact;
|
const currentArtifact = currentArtifactIfExists as ContractArtifact;
|
||||||
newArtifact = {
|
newArtifact = {
|
||||||
...currentArtifact,
|
...currentArtifact,
|
||||||
networks: {
|
...contractVersion,
|
||||||
...currentArtifact.networks,
|
|
||||||
[this._networkId]: contractNetworkData,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
newArtifact = {
|
newArtifact = {
|
||||||
contract_name: contractName,
|
schemaVersion: constants.LATEST_ARTIFACT_VERSION,
|
||||||
networks: {
|
contractName,
|
||||||
[this._networkId]: contractNetworkData,
|
...contractVersion,
|
||||||
},
|
networks: {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ import { AbiType, ConstructorAbi, ContractAbi, Provider, TxData } from '@0xproje
|
|||||||
import { logUtils } from '@0xproject/utils';
|
import { logUtils } from '@0xproject/utils';
|
||||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
import * as solc from 'solc';
|
||||||
import * as Web3 from 'web3';
|
import * as Web3 from 'web3';
|
||||||
|
|
||||||
import { Contract } from './utils/contract';
|
import { Contract } from './utils/contract';
|
||||||
@ -28,7 +29,20 @@ export class Deployer {
|
|||||||
private _artifactsDir: string;
|
private _artifactsDir: string;
|
||||||
private _networkId: number;
|
private _networkId: number;
|
||||||
private _defaults: Partial<TxData>;
|
private _defaults: Partial<TxData>;
|
||||||
|
/**
|
||||||
|
* Gets data for current version stored in artifact.
|
||||||
|
* @param contractArtifact The contract artifact.
|
||||||
|
* @return Version specific contract data.
|
||||||
|
*/
|
||||||
|
private static _getContractCompilerOutputFromArtifactIfExists(
|
||||||
|
contractArtifact: ContractArtifact,
|
||||||
|
): solc.StandardContractOutput {
|
||||||
|
const compilerOutputIfExists = contractArtifact.compilerOutput;
|
||||||
|
if (_.isUndefined(compilerOutputIfExists)) {
|
||||||
|
throw new Error(`Compiler output not found in artifact for contract: ${contractArtifact.contractName}`);
|
||||||
|
}
|
||||||
|
return compilerOutputIfExists;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Instantiate a new instance of the Deployer class.
|
* Instantiate a new instance of the Deployer class.
|
||||||
* @param opts Deployer options, including either an RPC url or Provider instance.
|
* @param opts Deployer options, including either an RPC url or Provider instance.
|
||||||
@ -58,10 +72,8 @@ export class Deployer {
|
|||||||
*/
|
*/
|
||||||
public async deployAsync(contractName: string, args: any[] = []): Promise<Web3.ContractInstance> {
|
public async deployAsync(contractName: string, args: any[] = []): Promise<Web3.ContractInstance> {
|
||||||
const contractArtifactIfExists: ContractArtifact = this._loadContractArtifactIfExists(contractName);
|
const contractArtifactIfExists: ContractArtifact = this._loadContractArtifactIfExists(contractName);
|
||||||
const contractNetworkDataIfExists: ContractNetworkData = this._getContractNetworkDataFromArtifactIfExists(
|
const compilerOutput = Deployer._getContractCompilerOutputFromArtifactIfExists(contractArtifactIfExists);
|
||||||
contractArtifactIfExists,
|
const data = compilerOutput.evm.bytecode.object;
|
||||||
);
|
|
||||||
const data = contractNetworkDataIfExists.bytecode;
|
|
||||||
const from = await this._getFromAddressAsync();
|
const from = await this._getFromAddressAsync();
|
||||||
const gas = await this._getAllowableGasEstimateAsync(data);
|
const gas = await this._getAllowableGasEstimateAsync(data);
|
||||||
const txData = {
|
const txData = {
|
||||||
@ -70,7 +82,10 @@ export class Deployer {
|
|||||||
data,
|
data,
|
||||||
gas,
|
gas,
|
||||||
};
|
};
|
||||||
const abi = contractNetworkDataIfExists.abi;
|
if (_.isUndefined(compilerOutput.abi)) {
|
||||||
|
throw new Error(`ABI not found in ${contractName} artifacts`);
|
||||||
|
}
|
||||||
|
const abi = compilerOutput.abi;
|
||||||
const constructorAbi = _.find(abi, { type: AbiType.Constructor }) as ConstructorAbi;
|
const constructorAbi = _.find(abi, { type: AbiType.Constructor }) as ConstructorAbi;
|
||||||
const constructorArgs = _.isUndefined(constructorAbi) ? [] : constructorAbi.inputs;
|
const constructorArgs = _.isUndefined(constructorAbi) ? [] : constructorAbi.inputs;
|
||||||
if (constructorArgs.length !== args.length) {
|
if (constructorArgs.length !== args.length) {
|
||||||
@ -138,15 +153,16 @@ export class Deployer {
|
|||||||
args: any[],
|
args: any[],
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const contractArtifactIfExists: ContractArtifact = this._loadContractArtifactIfExists(contractName);
|
const contractArtifactIfExists: ContractArtifact = this._loadContractArtifactIfExists(contractName);
|
||||||
const contractNetworkDataIfExists: ContractNetworkData = this._getContractNetworkDataFromArtifactIfExists(
|
const compilerOutput = Deployer._getContractCompilerOutputFromArtifactIfExists(contractArtifactIfExists);
|
||||||
contractArtifactIfExists,
|
if (_.isUndefined(compilerOutput.abi)) {
|
||||||
);
|
throw new Error(`ABI not found in ${contractName} artifacts`);
|
||||||
const abi = contractNetworkDataIfExists.abi;
|
}
|
||||||
|
const abi = compilerOutput.abi;
|
||||||
const encodedConstructorArgs = encoder.encodeConstructorArgsFromAbi(args, abi);
|
const encodedConstructorArgs = encoder.encodeConstructorArgsFromAbi(args, abi);
|
||||||
const newContractData = {
|
const newContractData: ContractNetworkData = {
|
||||||
...contractNetworkDataIfExists,
|
|
||||||
address: contractAddress,
|
address: contractAddress,
|
||||||
constructor_args: encodedConstructorArgs,
|
links: {},
|
||||||
|
constructorArgs: encodedConstructorArgs,
|
||||||
};
|
};
|
||||||
const newArtifact = {
|
const newArtifact = {
|
||||||
...contractArtifactIfExists,
|
...contractArtifactIfExists,
|
||||||
@ -173,18 +189,6 @@ export class Deployer {
|
|||||||
throw new Error(`Artifact not found for contract: ${contractName} at ${artifactPath}`);
|
throw new Error(`Artifact not found for contract: ${contractName} at ${artifactPath}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* Gets data for current networkId stored in artifact.
|
|
||||||
* @param contractArtifact The contract artifact.
|
|
||||||
* @return Network specific contract data.
|
|
||||||
*/
|
|
||||||
private _getContractNetworkDataFromArtifactIfExists(contractArtifact: ContractArtifact): ContractNetworkData {
|
|
||||||
const contractNetworkDataIfExists = contractArtifact.networks[this._networkId];
|
|
||||||
if (_.isUndefined(contractNetworkDataIfExists)) {
|
|
||||||
throw new Error(`Data not found in artifact for contract: ${contractArtifact.contract_name}`);
|
|
||||||
}
|
|
||||||
return contractNetworkDataIfExists;
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Gets the address to use for sending a transaction.
|
* Gets the address to use for sending a transaction.
|
||||||
* @return The default from address. If not specified, returns the first address accessible by web3.
|
* @return The default from address. If not specified, returns the first address accessible by web3.
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
export { Deployer } from './deployer';
|
export { Deployer } from './deployer';
|
||||||
export { Compiler } from './compiler';
|
export { Compiler } from './compiler';
|
||||||
|
export { ContractArtifact, ContractNetworks } from './utils/types';
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
export const constants = {
|
export const constants = {
|
||||||
SOLIDITY_FILE_EXTENSION: '.sol',
|
SOLIDITY_FILE_EXTENSION: '.sol',
|
||||||
BASE_COMPILER_URL: 'https://ethereum.github.io/solc-bin/bin/',
|
BASE_COMPILER_URL: 'https://ethereum.github.io/solc-bin/bin/',
|
||||||
|
LATEST_ARTIFACT_VERSION: '2.0.0',
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { ContractAbi, Provider, TxData } from '@0xproject/types';
|
import { ContractAbi, Provider, TxData } from '@0xproject/types';
|
||||||
|
import * as solc from 'solc';
|
||||||
import * as Web3 from 'web3';
|
import * as Web3 from 'web3';
|
||||||
import * as yargs from 'yargs';
|
import * as yargs from 'yargs';
|
||||||
|
|
||||||
@ -9,28 +10,40 @@ export enum AbiType {
|
|||||||
Fallback = 'fallback',
|
Fallback = 'fallback',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ContractArtifact {
|
export interface ContractArtifact extends ContractVersionData {
|
||||||
contract_name: string;
|
schemaVersion: string;
|
||||||
|
contractName: string;
|
||||||
networks: ContractNetworks;
|
networks: ContractNetworks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ContractVersionData {
|
||||||
|
compiler: {
|
||||||
|
name: 'solc';
|
||||||
|
version: string;
|
||||||
|
settings: solc.CompilerSettings;
|
||||||
|
};
|
||||||
|
sources: {
|
||||||
|
[sourceName: string]: {
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
sourceCodes: {
|
||||||
|
[sourceName: string]: string;
|
||||||
|
};
|
||||||
|
sourceTreeHashHex: string;
|
||||||
|
compilerOutput: solc.StandardContractOutput;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ContractNetworks {
|
export interface ContractNetworks {
|
||||||
[key: number]: ContractNetworkData;
|
[networkId: number]: ContractNetworkData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ContractNetworkData {
|
export interface ContractNetworkData {
|
||||||
solc_version: string;
|
address: string;
|
||||||
optimizer_enabled: boolean;
|
links: {
|
||||||
source_tree_hash: string;
|
[linkName: string]: string;
|
||||||
abi: ContractAbi;
|
};
|
||||||
bytecode: string;
|
constructorArgs: string;
|
||||||
runtime_bytecode: string;
|
|
||||||
address?: string;
|
|
||||||
constructor_args?: string;
|
|
||||||
updated_at: number;
|
|
||||||
source_map: string;
|
|
||||||
source_map_runtime: string;
|
|
||||||
sources: string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SolcErrors {
|
export interface SolcErrors {
|
||||||
@ -42,7 +55,6 @@ export interface CliOptions extends yargs.Arguments {
|
|||||||
contractsDir: string;
|
contractsDir: string;
|
||||||
jsonrpcUrl: string;
|
jsonrpcUrl: string;
|
||||||
networkId: number;
|
networkId: number;
|
||||||
shouldOptimize: boolean;
|
|
||||||
gasPrice: string;
|
gasPrice: string;
|
||||||
account?: string;
|
account?: string;
|
||||||
contract?: string;
|
contract?: string;
|
||||||
@ -50,11 +62,10 @@ export interface CliOptions extends yargs.Arguments {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CompilerOptions {
|
export interface CompilerOptions {
|
||||||
contractsDir: string;
|
contractsDir?: string;
|
||||||
networkId: number;
|
artifactsDir?: string;
|
||||||
optimizerEnabled: boolean;
|
compilerSettings?: solc.CompilerSettings;
|
||||||
artifactsDir: string;
|
contracts?: string[] | '*';
|
||||||
specifiedContracts: Set<string>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseDeployerOptions {
|
export interface BaseDeployerOptions {
|
||||||
|
@ -19,9 +19,7 @@ describe('#Compiler', function() {
|
|||||||
const compilerOpts: CompilerOptions = {
|
const compilerOpts: CompilerOptions = {
|
||||||
artifactsDir,
|
artifactsDir,
|
||||||
contractsDir,
|
contractsDir,
|
||||||
networkId: constants.networkId,
|
contracts: constants.contracts,
|
||||||
optimizerEnabled: constants.optimizerEnabled,
|
|
||||||
specifiedContracts: new Set(constants.specifiedContracts),
|
|
||||||
};
|
};
|
||||||
const compiler = new Compiler(compilerOpts);
|
const compiler = new Compiler(compilerOpts);
|
||||||
beforeEach((done: DoneCallback) => {
|
beforeEach((done: DoneCallback) => {
|
||||||
@ -39,9 +37,8 @@ describe('#Compiler', function() {
|
|||||||
};
|
};
|
||||||
const exchangeArtifactString = await fsWrapper.readFileAsync(exchangeArtifactPath, opts);
|
const exchangeArtifactString = await fsWrapper.readFileAsync(exchangeArtifactPath, opts);
|
||||||
const exchangeArtifact: ContractArtifact = JSON.parse(exchangeArtifactString);
|
const exchangeArtifact: ContractArtifact = JSON.parse(exchangeArtifactString);
|
||||||
const exchangeContractData: ContractNetworkData = exchangeArtifact.networks[constants.networkId];
|
|
||||||
// The last 43 bytes of the binaries are metadata which may not be equivalent
|
// The last 43 bytes of the binaries are metadata which may not be equivalent
|
||||||
const unlinkedBinaryWithoutMetadata = exchangeContractData.bytecode.slice(0, -86);
|
const unlinkedBinaryWithoutMetadata = exchangeArtifact.compilerOutput.evm.bytecode.object.slice(0, -86);
|
||||||
const exchangeBinaryWithoutMetadata = exchange_binary.slice(0, -86);
|
const exchangeBinaryWithoutMetadata = exchange_binary.slice(0, -86);
|
||||||
expect(unlinkedBinaryWithoutMetadata).to.equal(exchangeBinaryWithoutMetadata);
|
expect(unlinkedBinaryWithoutMetadata).to.equal(exchangeBinaryWithoutMetadata);
|
||||||
});
|
});
|
||||||
|
@ -20,9 +20,7 @@ describe('#Deployer', () => {
|
|||||||
const compilerOpts: CompilerOptions = {
|
const compilerOpts: CompilerOptions = {
|
||||||
artifactsDir,
|
artifactsDir,
|
||||||
contractsDir,
|
contractsDir,
|
||||||
networkId: constants.networkId,
|
contracts: constants.contracts,
|
||||||
optimizerEnabled: constants.optimizerEnabled,
|
|
||||||
specifiedContracts: new Set(constants.specifiedContracts),
|
|
||||||
};
|
};
|
||||||
const compiler = new Compiler(compilerOpts);
|
const compiler = new Compiler(compilerOpts);
|
||||||
const deployerOpts = {
|
const deployerOpts = {
|
||||||
@ -56,8 +54,7 @@ describe('#Deployer', () => {
|
|||||||
const exchangeContractData: ContractNetworkData = exchangeArtifact.networks[constants.networkId];
|
const exchangeContractData: ContractNetworkData = exchangeArtifact.networks[constants.networkId];
|
||||||
const exchangeAddress = exchangeContractInstance.address;
|
const exchangeAddress = exchangeContractInstance.address;
|
||||||
expect(exchangeAddress).to.not.equal(undefined);
|
expect(exchangeAddress).to.not.equal(undefined);
|
||||||
expect(exchangeContractData.address).to.equal(undefined);
|
expect(exchangeContractData).to.equal(undefined);
|
||||||
expect(exchangeContractData.constructor_args).to.equal(undefined);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('#deployAndSaveAsync', () => {
|
describe('#deployAndSaveAsync', () => {
|
||||||
@ -72,7 +69,7 @@ describe('#Deployer', () => {
|
|||||||
const exchangeContractData: ContractNetworkData = exchangeArtifact.networks[constants.networkId];
|
const exchangeContractData: ContractNetworkData = exchangeArtifact.networks[constants.networkId];
|
||||||
const exchangeAddress = exchangeContractInstance.address;
|
const exchangeAddress = exchangeContractInstance.address;
|
||||||
expect(exchangeAddress).to.be.equal(exchangeContractData.address);
|
expect(exchangeAddress).to.be.equal(exchangeContractData.address);
|
||||||
expect(constructor_args).to.be.equal(exchangeContractData.constructor_args);
|
expect(constructor_args).to.be.equal(exchangeContractData.constructorArgs);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
File diff suppressed because one or more lines are too long
@ -7,5 +7,5 @@ export const constants = {
|
|||||||
timeoutMs: 30000,
|
timeoutMs: 30000,
|
||||||
zrxTokenAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498',
|
zrxTokenAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498',
|
||||||
tokenTransferProxyAddress: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4',
|
tokenTransferProxyAddress: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4',
|
||||||
specifiedContracts: '*',
|
contracts: '*' as '*',
|
||||||
};
|
};
|
||||||
|
@ -13,7 +13,7 @@ export const coverage = {
|
|||||||
return coverageSubprovider;
|
return coverageSubprovider;
|
||||||
},
|
},
|
||||||
_getCoverageSubprovider(): CoverageSubprovider {
|
_getCoverageSubprovider(): CoverageSubprovider {
|
||||||
const artifactsPath = '../migrations/src/artifacts';
|
const artifactsPath = '../migrations/artifacts/1.0.0';
|
||||||
const contractsPath = 'src/contracts';
|
const contractsPath = 'src/contracts';
|
||||||
const networkId = 50;
|
const networkId = 50;
|
||||||
const defaultFromAddress = constants.TESTRPC_FIRST_ADDRESS;
|
const defaultFromAddress = constants.TESTRPC_FIRST_ADDRESS;
|
||||||
|
15
packages/metacoin/compiler.json
Normal file
15
packages/metacoin/compiler.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"compilerSettings": {
|
||||||
|
"outputSelection": {
|
||||||
|
"*": {
|
||||||
|
"*": [
|
||||||
|
"abi",
|
||||||
|
"evm.bytecode.object",
|
||||||
|
"evm.bytecode.sourceMap",
|
||||||
|
"evm.deployedBytecode.object",
|
||||||
|
"evm.deployedBytecode.sourceMap"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,13 +12,13 @@
|
|||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"test": "run-s build run_mocha",
|
"test": "run-s build run_mocha",
|
||||||
"test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov",
|
"test:coverage": "SOLIDITY_COVERAGE=true run-s build run_mocha coverage:report:text coverage:report:lcov",
|
||||||
"run_mocha": "mocha lib/test/**/*.js --bail --exit",
|
"run_mocha": "mocha lib/test/**/*_test.js lib/test/global_hooks.js --bail --exit",
|
||||||
"generate_contract_wrappers": "node ../abi-gen/lib/index.js --abis 'artifacts/Metacoin.json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/contract_wrappers --backend ethers && prettier --write 'src/contract_wrappers/**.ts'",
|
"generate_contract_wrappers": "node ../abi-gen/lib/index.js --abis 'artifacts/Metacoin.json' --template ../contract_templates/contract.handlebars --partials '../contract_templates/partials/**/*.handlebars' --output src/contract_wrappers --backend ethers && prettier --write 'src/contract_wrappers/**.ts'",
|
||||||
"coverage:report:text": "istanbul report text",
|
"coverage:report:text": "istanbul report text",
|
||||||
"coverage:report:html": "istanbul report html && open coverage/index.html",
|
"coverage:report:html": "istanbul report html && open coverage/index.html",
|
||||||
"coverage:report:lcov": "istanbul report lcov",
|
"coverage:report:lcov": "istanbul report lcov",
|
||||||
"test:circleci": "yarn test:coverage",
|
"test:circleci": "yarn test:coverage",
|
||||||
"compile": "node ../deployer/lib/src/cli.js compile --contracts Metacoin --contracts-dir contracts --artifacts-dir artifacts"
|
"compile": "node ../deployer/lib/src/cli.js compile"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
0
packages/migrations/artifacts/1.0.0/.gitkeep
vendored
Normal file
0
packages/migrations/artifacts/1.0.0/.gitkeep
vendored
Normal file
18
packages/sol-cov/compiler.json
Normal file
18
packages/sol-cov/compiler.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"contracts": ["SimpleStorage"],
|
||||||
|
"contractsDir": "test/fixtures/contracts",
|
||||||
|
"artifactsDir": "test/fixtures/artifacts",
|
||||||
|
"compilerSettings": {
|
||||||
|
"outputSelection": {
|
||||||
|
"*": {
|
||||||
|
"*": [
|
||||||
|
"abi",
|
||||||
|
"evm.bytecode.object",
|
||||||
|
"evm.bytecode.sourceMap",
|
||||||
|
"evm.deployedBytecode.object",
|
||||||
|
"evm.deployedBytecode.sourceMap"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@
|
|||||||
"run_mocha": "mocha lib/test/**/*_test.js --exit",
|
"run_mocha": "mocha lib/test/**/*_test.js --exit",
|
||||||
"clean": "shx rm -rf lib scripts",
|
"clean": "shx rm -rf lib scripts",
|
||||||
"build": "copyfiles 'test/fixtures/**/*' ./lib && tsc && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts",
|
"build": "copyfiles 'test/fixtures/**/*' ./lib && tsc && copyfiles -u 3 './lib/src/monorepo_scripts/**/*' ./scripts",
|
||||||
"compile_test": "node ../deployer/lib/src/cli.js compile --contracts SimpleStorage --contracts-dir test/fixtures/contracts --artifacts-dir test/fixtures/artifacts",
|
"compile_test": "node ../deployer/lib/src/cli.js compile",
|
||||||
"manual:postpublish": "yarn build; node ./scripts/postpublish.js",
|
"manual:postpublish": "yarn build; node ./scripts/postpublish.js",
|
||||||
"docs:stage": "yarn build && node ./scripts/stage_docs.js",
|
"docs:stage": "yarn build && node ./scripts/stage_docs.js",
|
||||||
"docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_FILES",
|
"docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_FILES",
|
||||||
|
@ -8,25 +8,24 @@ import { ContractData } from './types';
|
|||||||
export const collectContractsData = (artifactsPath: string, sourcesPath: string, networkId: number) => {
|
export const collectContractsData = (artifactsPath: string, sourcesPath: string, networkId: number) => {
|
||||||
const artifactsGlob = `${artifactsPath}/**/*.json`;
|
const artifactsGlob = `${artifactsPath}/**/*.json`;
|
||||||
const artifactFileNames = glob.sync(artifactsGlob, { absolute: true });
|
const artifactFileNames = glob.sync(artifactsGlob, { absolute: true });
|
||||||
const contractsDataIfExists: Array<ContractData | {}> = _.map(artifactFileNames, artifactFileName => {
|
const contractsData: ContractData[] = [];
|
||||||
|
_.forEach(artifactFileNames, artifactFileName => {
|
||||||
const artifact = JSON.parse(fs.readFileSync(artifactFileName).toString());
|
const artifact = JSON.parse(fs.readFileSync(artifactFileName).toString());
|
||||||
const sources = artifact.networks[networkId].sources;
|
const sources = _.keys(artifact.sources);
|
||||||
const contractName = artifact.contract_name;
|
const contractName = artifact.contractName;
|
||||||
// We don't compute coverage for dependencies
|
// We don't compute coverage for dependencies
|
||||||
const sourceCodes = _.map(sources, (source: string) => fs.readFileSync(source).toString());
|
const sourceCodes = _.map(sources, (source: string) =>
|
||||||
if (_.isUndefined(artifact.networks[networkId])) {
|
fs.readFileSync(path.join(sourcesPath, source)).toString(),
|
||||||
throw new Error(`No ${contractName} artifacts found for networkId ${networkId}`);
|
);
|
||||||
}
|
|
||||||
const contractData = {
|
const contractData = {
|
||||||
sourceCodes,
|
sourceCodes,
|
||||||
sources,
|
sources,
|
||||||
sourceMap: artifact.networks[networkId].source_map,
|
bytecode: artifact.compilerOutput.evm.bytecode.object,
|
||||||
sourceMapRuntime: artifact.networks[networkId].source_map_runtime,
|
sourceMap: artifact.compilerOutput.evm.bytecode.sourceMap,
|
||||||
runtimeBytecode: artifact.networks[networkId].runtime_bytecode,
|
runtimeBytecode: artifact.compilerOutput.evm.deployedBytecode.object,
|
||||||
bytecode: artifact.networks[networkId].bytecode,
|
sourceMapRuntime: artifact.compilerOutput.evm.deployedBytecode.sourceMap,
|
||||||
};
|
};
|
||||||
return contractData;
|
contractsData.push(contractData);
|
||||||
});
|
});
|
||||||
const contractsData = _.filter(contractsDataIfExists, contractData => !_.isEmpty(contractData)) as ContractData[];
|
|
||||||
return contractsData;
|
return contractsData;
|
||||||
};
|
};
|
||||||
|
@ -29,10 +29,31 @@ import {
|
|||||||
import { utils } from './utils';
|
import { utils } from './utils';
|
||||||
|
|
||||||
export class CoverageManager {
|
export class CoverageManager {
|
||||||
|
private _sourcesPath: string;
|
||||||
private _traceInfos: TraceInfo[] = [];
|
private _traceInfos: TraceInfo[] = [];
|
||||||
private _contractsData: ContractData[] = [];
|
private _contractsData: ContractData[] = [];
|
||||||
private _getContractCodeAsync: (address: string) => Promise<string>;
|
private _getContractCodeAsync: (address: string) => Promise<string>;
|
||||||
private static _getSingleFileCoverageForTrace(
|
constructor(
|
||||||
|
artifactsPath: string,
|
||||||
|
sourcesPath: string,
|
||||||
|
networkId: number,
|
||||||
|
getContractCodeAsync: (address: string) => Promise<string>,
|
||||||
|
) {
|
||||||
|
this._getContractCodeAsync = getContractCodeAsync;
|
||||||
|
this._sourcesPath = sourcesPath;
|
||||||
|
this._contractsData = collectContractsData(artifactsPath, this._sourcesPath, networkId);
|
||||||
|
}
|
||||||
|
public appendTraceInfo(traceInfo: TraceInfo): void {
|
||||||
|
this._traceInfos.push(traceInfo);
|
||||||
|
}
|
||||||
|
public async writeCoverageAsync(): Promise<void> {
|
||||||
|
const finalCoverage = await this._computeCoverageAsync();
|
||||||
|
const jsonReplacer: null = null;
|
||||||
|
const numberOfJsonSpaces = 4;
|
||||||
|
const stringifiedCoverage = JSON.stringify(finalCoverage, jsonReplacer, numberOfJsonSpaces);
|
||||||
|
fs.writeFileSync('coverage/coverage.json', stringifiedCoverage);
|
||||||
|
}
|
||||||
|
private _getSingleFileCoverageForTrace(
|
||||||
contractData: ContractData,
|
contractData: ContractData,
|
||||||
coveredPcs: number[],
|
coveredPcs: number[],
|
||||||
pcToSourceRange: { [programCounter: number]: SourceRange },
|
pcToSourceRange: { [programCounter: number]: SourceRange },
|
||||||
@ -94,11 +115,12 @@ export class CoverageManager {
|
|||||||
);
|
);
|
||||||
statementCoverage[modifierStatementId] = isModifierCovered;
|
statementCoverage[modifierStatementId] = isModifierCovered;
|
||||||
}
|
}
|
||||||
|
const absoluteFileName = path.join(this._sourcesPath, fileName);
|
||||||
const partialCoverage = {
|
const partialCoverage = {
|
||||||
[contractData.sources[fileIndex]]: {
|
[absoluteFileName]: {
|
||||||
...coverageEntriesDescription,
|
...coverageEntriesDescription,
|
||||||
l: {}, // It's able to derive it from statement coverage
|
l: {}, // It's able to derive it from statement coverage
|
||||||
path: fileName,
|
path: absoluteFileName,
|
||||||
f: functionCoverage,
|
f: functionCoverage,
|
||||||
s: statementCoverage,
|
s: statementCoverage,
|
||||||
b: branchCoverage,
|
b: branchCoverage,
|
||||||
@ -106,31 +128,13 @@ export class CoverageManager {
|
|||||||
};
|
};
|
||||||
return partialCoverage;
|
return partialCoverage;
|
||||||
}
|
}
|
||||||
constructor(
|
|
||||||
artifactsPath: string,
|
|
||||||
sourcesPath: string,
|
|
||||||
networkId: number,
|
|
||||||
getContractCodeAsync: (address: string) => Promise<string>,
|
|
||||||
) {
|
|
||||||
this._getContractCodeAsync = getContractCodeAsync;
|
|
||||||
this._contractsData = collectContractsData(artifactsPath, sourcesPath, networkId);
|
|
||||||
}
|
|
||||||
public appendTraceInfo(traceInfo: TraceInfo): void {
|
|
||||||
this._traceInfos.push(traceInfo);
|
|
||||||
}
|
|
||||||
public async writeCoverageAsync(): Promise<void> {
|
|
||||||
const finalCoverage = await this._computeCoverageAsync();
|
|
||||||
const jsonReplacer: null = null;
|
|
||||||
const numberOfJsonSpaces = 4;
|
|
||||||
const stringifiedCoverage = JSON.stringify(finalCoverage, jsonReplacer, numberOfJsonSpaces);
|
|
||||||
fs.writeFileSync('coverage/coverage.json', stringifiedCoverage);
|
|
||||||
}
|
|
||||||
private async _computeCoverageAsync(): Promise<Coverage> {
|
private async _computeCoverageAsync(): Promise<Coverage> {
|
||||||
const collector = new Collector();
|
const collector = new Collector();
|
||||||
for (const traceInfo of this._traceInfos) {
|
for (const traceInfo of this._traceInfos) {
|
||||||
if (traceInfo.address !== constants.NEW_CONTRACT) {
|
if (traceInfo.address !== constants.NEW_CONTRACT) {
|
||||||
// Runtime transaction
|
// Runtime transaction
|
||||||
const runtimeBytecode = (traceInfo as TraceInfoExistingContract).runtimeBytecode;
|
let runtimeBytecode = (traceInfo as TraceInfoExistingContract).runtimeBytecode;
|
||||||
|
runtimeBytecode = utils.removeHexPrefix(runtimeBytecode);
|
||||||
const contractData = _.find(this._contractsData, { runtimeBytecode }) as ContractData;
|
const contractData = _.find(this._contractsData, { runtimeBytecode }) as ContractData;
|
||||||
if (_.isUndefined(contractData)) {
|
if (_.isUndefined(contractData)) {
|
||||||
throw new Error(`Transaction to an unknown address: ${traceInfo.address}`);
|
throw new Error(`Transaction to an unknown address: ${traceInfo.address}`);
|
||||||
@ -144,7 +148,7 @@ export class CoverageManager {
|
|||||||
contractData.sources,
|
contractData.sources,
|
||||||
);
|
);
|
||||||
for (let fileIndex = 0; fileIndex < contractData.sources.length; fileIndex++) {
|
for (let fileIndex = 0; fileIndex < contractData.sources.length; fileIndex++) {
|
||||||
const singleFileCoverageForTrace = CoverageManager._getSingleFileCoverageForTrace(
|
const singleFileCoverageForTrace = this._getSingleFileCoverageForTrace(
|
||||||
contractData,
|
contractData,
|
||||||
traceInfo.coveredPcs,
|
traceInfo.coveredPcs,
|
||||||
pcToSourceRange,
|
pcToSourceRange,
|
||||||
@ -154,7 +158,8 @@ export class CoverageManager {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Contract creation transaction
|
// Contract creation transaction
|
||||||
const bytecode = (traceInfo as TraceInfoNewContract).bytecode;
|
let bytecode = (traceInfo as TraceInfoNewContract).bytecode;
|
||||||
|
bytecode = utils.removeHexPrefix(bytecode);
|
||||||
const contractData = _.find(this._contractsData, contractDataCandidate =>
|
const contractData = _.find(this._contractsData, contractDataCandidate =>
|
||||||
bytecode.startsWith(contractDataCandidate.bytecode),
|
bytecode.startsWith(contractDataCandidate.bytecode),
|
||||||
) as ContractData;
|
) as ContractData;
|
||||||
@ -170,7 +175,7 @@ export class CoverageManager {
|
|||||||
contractData.sources,
|
contractData.sources,
|
||||||
);
|
);
|
||||||
for (let fileIndex = 0; fileIndex < contractData.sources.length; fileIndex++) {
|
for (let fileIndex = 0; fileIndex < contractData.sources.length; fileIndex++) {
|
||||||
const singleFileCoverageForTrace = CoverageManager._getSingleFileCoverageForTrace(
|
const singleFileCoverageForTrace = this._getSingleFileCoverageForTrace(
|
||||||
contractData,
|
contractData,
|
||||||
traceInfo.coveredPcs,
|
traceInfo.coveredPcs,
|
||||||
pcToSourceRange,
|
pcToSourceRange,
|
||||||
|
@ -4,6 +4,10 @@ export const utils = {
|
|||||||
compareLineColumn(lhs: LineColumn, rhs: LineColumn): number {
|
compareLineColumn(lhs: LineColumn, rhs: LineColumn): number {
|
||||||
return lhs.line !== rhs.line ? lhs.line - rhs.line : lhs.column - rhs.column;
|
return lhs.line !== rhs.line ? lhs.line - rhs.line : lhs.column - rhs.column;
|
||||||
},
|
},
|
||||||
|
removeHexPrefix(hex: string): string {
|
||||||
|
const hexPrefix = '0x';
|
||||||
|
return hex.startsWith(hexPrefix) ? hex.slice(hexPrefix.length) : hex;
|
||||||
|
},
|
||||||
isRangeInside(childRange: SingleFileSourceRange, parentRange: SingleFileSourceRange): boolean {
|
isRangeInside(childRange: SingleFileSourceRange, parentRange: SingleFileSourceRange): boolean {
|
||||||
return (
|
return (
|
||||||
utils.compareLineColumn(parentRange.start, childRange.start) <= 0 &&
|
utils.compareLineColumn(parentRange.start, childRange.start) <= 0 &&
|
||||||
|
@ -3,6 +3,7 @@ export { FallthroughResolver } from './resolvers/fallthrough_resolver';
|
|||||||
export { URLResolver } from './resolvers/url_resolver';
|
export { URLResolver } from './resolvers/url_resolver';
|
||||||
export { NPMResolver } from './resolvers/npm_resolver';
|
export { NPMResolver } from './resolvers/npm_resolver';
|
||||||
export { FSResolver } from './resolvers/fs_resolver';
|
export { FSResolver } from './resolvers/fs_resolver';
|
||||||
|
export { RelativeFSResolver } from './resolvers/relative_fs_resolver';
|
||||||
export { NameResolver } from './resolvers/name_resolver';
|
export { NameResolver } from './resolvers/name_resolver';
|
||||||
export { EnumerableResolver } from './resolvers/enumerable_resolver';
|
export { EnumerableResolver } from './resolvers/enumerable_resolver';
|
||||||
export { Resolver } from './resolvers/resolver';
|
export { Resolver } from './resolvers/resolver';
|
||||||
|
@ -18,7 +18,8 @@ export class NameResolver extends EnumerableResolver {
|
|||||||
const onFile = (filePath: string) => {
|
const onFile = (filePath: string) => {
|
||||||
const contractName = path.basename(filePath, SOLIDITY_FILE_EXTENSION);
|
const contractName = path.basename(filePath, SOLIDITY_FILE_EXTENSION);
|
||||||
if (contractName === lookupContractName) {
|
if (contractName === lookupContractName) {
|
||||||
const source = fs.readFileSync(filePath).toString();
|
const absoluteContractPath = path.join(this._contractsDir, filePath);
|
||||||
|
const source = fs.readFileSync(absoluteContractPath).toString();
|
||||||
contractSource = {
|
contractSource = {
|
||||||
source,
|
source,
|
||||||
path: filePath,
|
path: filePath,
|
||||||
@ -35,7 +36,8 @@ export class NameResolver extends EnumerableResolver {
|
|||||||
const contractSources: ContractSource[] = [];
|
const contractSources: ContractSource[] = [];
|
||||||
const onFile = (filePath: string) => {
|
const onFile = (filePath: string) => {
|
||||||
const contractName = path.basename(filePath, SOLIDITY_FILE_EXTENSION);
|
const contractName = path.basename(filePath, SOLIDITY_FILE_EXTENSION);
|
||||||
const source = fs.readFileSync(filePath).toString();
|
const absoluteContractPath = path.join(this._contractsDir, filePath);
|
||||||
|
const source = fs.readFileSync(absoluteContractPath).toString();
|
||||||
const contractSource = {
|
const contractSource = {
|
||||||
source,
|
source,
|
||||||
path: filePath,
|
path: filePath,
|
||||||
@ -54,9 +56,10 @@ export class NameResolver extends EnumerableResolver {
|
|||||||
throw new Error(`No directory found at ${dirPath}`);
|
throw new Error(`No directory found at ${dirPath}`);
|
||||||
}
|
}
|
||||||
for (const fileName of dirContents) {
|
for (const fileName of dirContents) {
|
||||||
const entryPath = path.join(dirPath, fileName);
|
const absoluteEntryPath = path.join(dirPath, fileName);
|
||||||
const isDirectory = fs.lstatSync(entryPath).isDirectory();
|
const isDirectory = fs.lstatSync(absoluteEntryPath).isDirectory();
|
||||||
const isComplete = isDirectory ? this._traverseContractsDir(entryPath, onFile) : onFile(entryPath);
|
const entryPath = path.relative(this._contractsDir, absoluteEntryPath);
|
||||||
|
const isComplete = isDirectory ? this._traverseContractsDir(absoluteEntryPath, onFile) : onFile(entryPath);
|
||||||
if (isComplete) {
|
if (isComplete) {
|
||||||
return isComplete;
|
return isComplete;
|
||||||
}
|
}
|
||||||
|
26
packages/sol-resolver/src/resolvers/relative_fs_resolver.ts
Normal file
26
packages/sol-resolver/src/resolvers/relative_fs_resolver.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
import { ContractSource } from '../types';
|
||||||
|
|
||||||
|
import { Resolver } from './resolver';
|
||||||
|
|
||||||
|
export class RelativeFSResolver extends Resolver {
|
||||||
|
private _contractsDir: string;
|
||||||
|
constructor(contractsDir: string) {
|
||||||
|
super();
|
||||||
|
this._contractsDir = contractsDir;
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line:prefer-function-over-method
|
||||||
|
public resolveIfExists(importPath: string): ContractSource | undefined {
|
||||||
|
const filePath = path.join(this._contractsDir, importPath);
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
const fileContent = fs.readFileSync(filePath).toString();
|
||||||
|
return {
|
||||||
|
source: fileContent,
|
||||||
|
path: importPath,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
@ -59,32 +59,33 @@ declare module 'solc' {
|
|||||||
| 'evm.gasEstimates'
|
| 'evm.gasEstimates'
|
||||||
| 'ewasm.wast'
|
| 'ewasm.wast'
|
||||||
| 'ewasm.wasm';
|
| 'ewasm.wasm';
|
||||||
|
export interface CompilerSettings {
|
||||||
|
remappings?: string[];
|
||||||
|
optimizer?: {
|
||||||
|
enabled: boolean;
|
||||||
|
runs?: number;
|
||||||
|
};
|
||||||
|
evmVersion?: 'homestead' | 'tangerineWhistle' | 'spuriousDragon' | 'byzantium' | 'constantinople';
|
||||||
|
metadata?: {
|
||||||
|
useLiteralContent: true;
|
||||||
|
};
|
||||||
|
libraries?: {
|
||||||
|
[fileName: string]: {
|
||||||
|
[libName: string]: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
outputSelection: {
|
||||||
|
[fileName: string]: {
|
||||||
|
[contractName: string]: OutputField[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
export interface StandardInput {
|
export interface StandardInput {
|
||||||
language: 'Solidity' | 'serpent' | 'lll' | 'assembly';
|
language: 'Solidity' | 'serpent' | 'lll' | 'assembly';
|
||||||
sources: {
|
sources: {
|
||||||
[fileName: string]: Source;
|
[fileName: string]: Source;
|
||||||
};
|
};
|
||||||
settings: {
|
settings: CompilerSettings;
|
||||||
remappings?: string[];
|
|
||||||
optimizer?: {
|
|
||||||
enabled: boolean;
|
|
||||||
runs?: number;
|
|
||||||
};
|
|
||||||
evmVersion?: 'homestead' | 'tangerineWhistle' | 'spuriousDragon' | 'byzantium' | 'constantinople';
|
|
||||||
metadata?: {
|
|
||||||
useLiteralContent: true;
|
|
||||||
};
|
|
||||||
libraries?: {
|
|
||||||
[fileName: string]: {
|
|
||||||
[libName: string]: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
outputSelection: {
|
|
||||||
[fileName: string]: {
|
|
||||||
[contractName: string]: OutputField[];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
export type ErrorType =
|
export type ErrorType =
|
||||||
| 'JSONError'
|
| 'JSONError'
|
||||||
@ -114,6 +115,19 @@ declare module 'solc' {
|
|||||||
formattedMessage?: string;
|
formattedMessage?: string;
|
||||||
}
|
}
|
||||||
import { ContractAbi } from '@0xproject/types';
|
import { ContractAbi } from '@0xproject/types';
|
||||||
|
export interface StandardContractOutput {
|
||||||
|
abi: ContractAbi;
|
||||||
|
evm: {
|
||||||
|
bytecode: {
|
||||||
|
object: string;
|
||||||
|
sourceMap: string;
|
||||||
|
};
|
||||||
|
deployedBytecode: {
|
||||||
|
object: string;
|
||||||
|
sourceMap: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
export interface StandardOutput {
|
export interface StandardOutput {
|
||||||
errors: Error[];
|
errors: Error[];
|
||||||
sources: {
|
sources: {
|
||||||
@ -125,19 +139,7 @@ declare module 'solc' {
|
|||||||
};
|
};
|
||||||
contracts: {
|
contracts: {
|
||||||
[fileName: string]: {
|
[fileName: string]: {
|
||||||
[contractName: string]: {
|
[contractName: string]: StandardContractOutput;
|
||||||
abi: ContractAbi;
|
|
||||||
evm: {
|
|
||||||
bytecode: {
|
|
||||||
object: string;
|
|
||||||
sourceMap: string;
|
|
||||||
};
|
|
||||||
deployedBytecode: {
|
|
||||||
object: string;
|
|
||||||
sourceMap: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 6.2 KiB |
@ -154,7 +154,6 @@ export class LegacyPortal extends React.Component<LegacyPortalProps, LegacyPorta
|
|||||||
const updateShouldBlockchainErrDialogBeOpen = this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen.bind(
|
const updateShouldBlockchainErrDialogBeOpen = this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen.bind(
|
||||||
this.props.dispatcher,
|
this.props.dispatcher,
|
||||||
);
|
);
|
||||||
const isDevelopment = configs.ENVIRONMENT === Environments.DEVELOPMENT;
|
|
||||||
const portalStyle: React.CSSProperties = {
|
const portalStyle: React.CSSProperties = {
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -204,18 +203,6 @@ export class LegacyPortal extends React.Component<LegacyPortalProps, LegacyPorta
|
|||||||
<div className="py2" style={{ backgroundColor: colors.grey50 }}>
|
<div className="py2" style={{ backgroundColor: colors.grey50 }}>
|
||||||
{this.props.blockchainIsLoaded ? (
|
{this.props.blockchainIsLoaded ? (
|
||||||
<Switch>
|
<Switch>
|
||||||
{isDevelopment && (
|
|
||||||
<Route
|
|
||||||
path={`${WebsitePaths.Portal}/wallet`}
|
|
||||||
render={this._renderWallet.bind(this)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isDevelopment && (
|
|
||||||
<Route
|
|
||||||
path={`${WebsitePaths.Portal}/relayers`}
|
|
||||||
render={this._renderRelayers.bind(this)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Route
|
<Route
|
||||||
path={`${WebsitePaths.Portal}/weth`}
|
path={`${WebsitePaths.Portal}/weth`}
|
||||||
render={this._renderEthWrapper.bind(this)}
|
render={this._renderEthWrapper.bind(this)}
|
||||||
@ -294,40 +281,6 @@ export class LegacyPortal extends React.Component<LegacyPortalProps, LegacyPorta
|
|||||||
isLedgerDialogOpen: !this.state.isLedgerDialogOpen,
|
isLedgerDialogOpen: !this.state.isLedgerDialogOpen,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
private _renderWallet() {
|
|
||||||
const allTokens = _.values(this.props.tokenByAddress);
|
|
||||||
const trackedTokens = _.filter(allTokens, t => t.isTracked);
|
|
||||||
return (
|
|
||||||
<div className="flex flex-center">
|
|
||||||
<div className="mx-auto">
|
|
||||||
<Wallet
|
|
||||||
userAddress={this.props.userAddress}
|
|
||||||
networkId={this.props.networkId}
|
|
||||||
blockchain={this._blockchain}
|
|
||||||
blockchainIsLoaded={this.props.blockchainIsLoaded}
|
|
||||||
blockchainErr={this.props.blockchainErr}
|
|
||||||
dispatcher={this.props.dispatcher}
|
|
||||||
tokenByAddress={this.props.tokenByAddress}
|
|
||||||
trackedTokens={trackedTokens}
|
|
||||||
userEtherBalanceInWei={this.props.userEtherBalanceInWei}
|
|
||||||
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
|
|
||||||
injectedProviderName={this.props.injectedProviderName}
|
|
||||||
providerType={this.props.providerType}
|
|
||||||
onToggleLedgerDialog={this.onToggleLedgerDialog.bind(this)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
private _renderRelayers() {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-center">
|
|
||||||
<div className="mx-auto" style={{ width: 800 }}>
|
|
||||||
<RelayerIndex networkId={this.props.networkId} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
private _renderEthWrapper() {
|
private _renderEthWrapper() {
|
||||||
return (
|
return (
|
||||||
<EthWrappers
|
<EthWrappers
|
||||||
|
@ -58,26 +58,6 @@ export class LegacyPortalMenu extends React.Component<LegacyPortalMenuProps, Leg
|
|||||||
>
|
>
|
||||||
{this._renderMenuItemWithIcon('Wrap ETH', 'zmdi-circle-o')}
|
{this._renderMenuItemWithIcon('Wrap ETH', 'zmdi-circle-o')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{configs.ENVIRONMENT === Environments.DEVELOPMENT && (
|
|
||||||
<div>
|
|
||||||
<MenuItem
|
|
||||||
style={this.props.menuItemStyle}
|
|
||||||
className="py2"
|
|
||||||
to={`${WebsitePaths.Portal}/wallet`}
|
|
||||||
onClick={this.props.onClick.bind(this)}
|
|
||||||
>
|
|
||||||
{this._renderMenuItemWithIcon('Wallet', 'zmdi-balance-wallet')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem
|
|
||||||
style={this.props.menuItemStyle}
|
|
||||||
className="py2"
|
|
||||||
to={`${WebsitePaths.Portal}/relayers`}
|
|
||||||
onClick={this.props.onClick.bind(this)}
|
|
||||||
>
|
|
||||||
{this._renderMenuItemWithIcon('Relayers', 'zmdi-input-antenna')}
|
|
||||||
</MenuItem>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
263
packages/website/ts/components/portal/portal.tsx
Normal file
263
packages/website/ts/components/portal/portal.tsx
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
import { colors, Styles } from '@0xproject/react-shared';
|
||||||
|
import { BigNumber } from '@0xproject/utils';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as DocumentTitle from 'react-document-title';
|
||||||
|
|
||||||
|
import { Blockchain } from 'ts/blockchain';
|
||||||
|
import { BlockchainErrDialog } from 'ts/components/dialogs/blockchain_err_dialog';
|
||||||
|
import { LedgerConfigDialog } from 'ts/components/dialogs/ledger_config_dialog';
|
||||||
|
import { PortalDisclaimerDialog } from 'ts/components/dialogs/portal_disclaimer_dialog';
|
||||||
|
import { AssetPicker } from 'ts/components/generate_order/asset_picker';
|
||||||
|
import { RelayerIndex } from 'ts/components/relayer_index/relayer_index';
|
||||||
|
import { TopBar, TopBarDisplayType } from 'ts/components/top_bar/top_bar';
|
||||||
|
import { FlashMessage } from 'ts/components/ui/flash_message';
|
||||||
|
import { Wallet } from 'ts/components/wallet/wallet';
|
||||||
|
import { localStorage } from 'ts/local_storage/local_storage';
|
||||||
|
import { Dispatcher } from 'ts/redux/dispatcher';
|
||||||
|
import { BlockchainErrs, HashData, Order, ProviderType, ScreenWidths, TokenByAddress, TokenVisibility } from 'ts/types';
|
||||||
|
import { constants } from 'ts/utils/constants';
|
||||||
|
import { Translate } from 'ts/utils/translate';
|
||||||
|
import { utils } from 'ts/utils/utils';
|
||||||
|
|
||||||
|
export interface PortalProps {
|
||||||
|
blockchainErr: BlockchainErrs;
|
||||||
|
blockchainIsLoaded: boolean;
|
||||||
|
dispatcher: Dispatcher;
|
||||||
|
hashData: HashData;
|
||||||
|
injectedProviderName: string;
|
||||||
|
networkId: number;
|
||||||
|
nodeVersion: string;
|
||||||
|
orderFillAmount: BigNumber;
|
||||||
|
providerType: ProviderType;
|
||||||
|
screenWidth: ScreenWidths;
|
||||||
|
tokenByAddress: TokenByAddress;
|
||||||
|
userEtherBalanceInWei: BigNumber;
|
||||||
|
userAddress: string;
|
||||||
|
shouldBlockchainErrDialogBeOpen: boolean;
|
||||||
|
userSuppliedOrderCache: Order;
|
||||||
|
location: Location;
|
||||||
|
flashMessage?: string | React.ReactNode;
|
||||||
|
lastForceTokenStateRefetch: number;
|
||||||
|
translate: Translate;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PortalState {
|
||||||
|
prevNetworkId: number;
|
||||||
|
prevNodeVersion: string;
|
||||||
|
prevUserAddress: string;
|
||||||
|
prevPathname: string;
|
||||||
|
isDisclaimerDialogOpen: boolean;
|
||||||
|
isLedgerDialogOpen: boolean;
|
||||||
|
isAssetPickerDialogOpen: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const THROTTLE_TIMEOUT = 100;
|
||||||
|
const TOP_BAR_HEIGHT = TopBar.heightForDisplayType(TopBarDisplayType.Expanded);
|
||||||
|
|
||||||
|
const styles: Styles = {
|
||||||
|
root: {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
backgroundColor: colors.lightestGrey,
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
height: `calc(100vh - ${TOP_BAR_HEIGHT}px)`,
|
||||||
|
},
|
||||||
|
scrollContainer: {
|
||||||
|
height: `calc(100vh - ${TOP_BAR_HEIGHT}px)`,
|
||||||
|
WebkitOverflowScrolling: 'touch',
|
||||||
|
overflow: 'auto',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontSize: 20,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Portal extends React.Component<PortalProps, PortalState> {
|
||||||
|
private _blockchain: Blockchain;
|
||||||
|
private _throttledScreenWidthUpdate: () => void;
|
||||||
|
constructor(props: PortalProps) {
|
||||||
|
super(props);
|
||||||
|
this._throttledScreenWidthUpdate = _.throttle(this._updateScreenWidth.bind(this), THROTTLE_TIMEOUT);
|
||||||
|
const didAcceptPortalDisclaimer = localStorage.getItemIfExists(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER);
|
||||||
|
const hasAcceptedDisclaimer =
|
||||||
|
!_.isUndefined(didAcceptPortalDisclaimer) && !_.isEmpty(didAcceptPortalDisclaimer);
|
||||||
|
this.state = {
|
||||||
|
prevNetworkId: this.props.networkId,
|
||||||
|
prevNodeVersion: this.props.nodeVersion,
|
||||||
|
prevUserAddress: this.props.userAddress,
|
||||||
|
prevPathname: this.props.location.pathname,
|
||||||
|
isDisclaimerDialogOpen: !hasAcceptedDisclaimer,
|
||||||
|
isAssetPickerDialogOpen: false,
|
||||||
|
isLedgerDialogOpen: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public componentDidMount() {
|
||||||
|
window.addEventListener('resize', this._throttledScreenWidthUpdate);
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}
|
||||||
|
public componentWillMount() {
|
||||||
|
this._blockchain = new Blockchain(this.props.dispatcher);
|
||||||
|
}
|
||||||
|
public componentWillUnmount() {
|
||||||
|
this._blockchain.destroy();
|
||||||
|
window.removeEventListener('resize', this._throttledScreenWidthUpdate);
|
||||||
|
// We re-set the entire redux state when the portal is unmounted so that when it is re-rendered
|
||||||
|
// the initialization process always occurs from the same base state. This helps avoid
|
||||||
|
// initialization inconsistencies (i.e While the portal was unrendered, the user might have
|
||||||
|
// become disconnected from their backing Ethereum node, changed user accounts, etc...)
|
||||||
|
this.props.dispatcher.resetState();
|
||||||
|
}
|
||||||
|
public componentWillReceiveProps(nextProps: PortalProps) {
|
||||||
|
if (nextProps.networkId !== this.state.prevNetworkId) {
|
||||||
|
// tslint:disable-next-line:no-floating-promises
|
||||||
|
this._blockchain.networkIdUpdatedFireAndForgetAsync(nextProps.networkId);
|
||||||
|
this.setState({
|
||||||
|
prevNetworkId: nextProps.networkId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (nextProps.userAddress !== this.state.prevUserAddress) {
|
||||||
|
const newUserAddress = _.isEmpty(nextProps.userAddress) ? undefined : nextProps.userAddress;
|
||||||
|
// tslint:disable-next-line:no-floating-promises
|
||||||
|
this._blockchain.userAddressUpdatedFireAndForgetAsync(newUserAddress);
|
||||||
|
this.setState({
|
||||||
|
prevUserAddress: nextProps.userAddress,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (nextProps.nodeVersion !== this.state.prevNodeVersion) {
|
||||||
|
// tslint:disable-next-line:no-floating-promises
|
||||||
|
this._blockchain.nodeVersionUpdatedFireAndForgetAsync(nextProps.nodeVersion);
|
||||||
|
}
|
||||||
|
if (nextProps.location.pathname !== this.state.prevPathname) {
|
||||||
|
this.setState({
|
||||||
|
prevPathname: nextProps.location.pathname,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public render() {
|
||||||
|
const updateShouldBlockchainErrDialogBeOpen = this.props.dispatcher.updateShouldBlockchainErrDialogBeOpen.bind(
|
||||||
|
this.props.dispatcher,
|
||||||
|
);
|
||||||
|
const allTokens = _.values(this.props.tokenByAddress);
|
||||||
|
const trackedTokens = _.filter(allTokens, t => t.isTracked);
|
||||||
|
return (
|
||||||
|
<div style={styles.root}>
|
||||||
|
<DocumentTitle title="0x Portal DApp" />
|
||||||
|
<TopBar
|
||||||
|
userAddress={this.props.userAddress}
|
||||||
|
networkId={this.props.networkId}
|
||||||
|
injectedProviderName={this.props.injectedProviderName}
|
||||||
|
onToggleLedgerDialog={this._onToggleLedgerDialog.bind(this)}
|
||||||
|
dispatcher={this.props.dispatcher}
|
||||||
|
providerType={this.props.providerType}
|
||||||
|
blockchainIsLoaded={this.props.blockchainIsLoaded}
|
||||||
|
location={this.props.location}
|
||||||
|
blockchain={this._blockchain}
|
||||||
|
translate={this.props.translate}
|
||||||
|
displayType={TopBarDisplayType.Expanded}
|
||||||
|
style={{ backgroundColor: colors.lightestGrey }}
|
||||||
|
/>
|
||||||
|
<div id="portal" style={styles.body}>
|
||||||
|
<div className="sm-flex flex-center">
|
||||||
|
<div className="flex-last px3">
|
||||||
|
<div className="py3" style={styles.title}>
|
||||||
|
Your Account
|
||||||
|
</div>
|
||||||
|
<Wallet
|
||||||
|
userAddress={this.props.userAddress}
|
||||||
|
networkId={this.props.networkId}
|
||||||
|
blockchain={this._blockchain}
|
||||||
|
blockchainIsLoaded={this.props.blockchainIsLoaded}
|
||||||
|
blockchainErr={this.props.blockchainErr}
|
||||||
|
dispatcher={this.props.dispatcher}
|
||||||
|
tokenByAddress={this.props.tokenByAddress}
|
||||||
|
trackedTokens={trackedTokens}
|
||||||
|
userEtherBalanceInWei={this.props.userEtherBalanceInWei}
|
||||||
|
lastForceTokenStateRefetch={this.props.lastForceTokenStateRefetch}
|
||||||
|
injectedProviderName={this.props.injectedProviderName}
|
||||||
|
providerType={this.props.providerType}
|
||||||
|
onToggleLedgerDialog={this._onToggleLedgerDialog.bind(this)}
|
||||||
|
onAddToken={this._onAddToken.bind(this)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-auto px3" style={styles.scrollContainer}>
|
||||||
|
<div className="py3" style={styles.title}>
|
||||||
|
Explore 0x Ecosystem
|
||||||
|
</div>
|
||||||
|
<RelayerIndex networkId={this.props.networkId} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<BlockchainErrDialog
|
||||||
|
blockchain={this._blockchain}
|
||||||
|
blockchainErr={this.props.blockchainErr}
|
||||||
|
isOpen={this.props.shouldBlockchainErrDialogBeOpen}
|
||||||
|
userAddress={this.props.userAddress}
|
||||||
|
toggleDialogFn={updateShouldBlockchainErrDialogBeOpen}
|
||||||
|
networkId={this.props.networkId}
|
||||||
|
/>
|
||||||
|
<PortalDisclaimerDialog
|
||||||
|
isOpen={this.state.isDisclaimerDialogOpen}
|
||||||
|
onToggleDialog={this._onPortalDisclaimerAccepted.bind(this)}
|
||||||
|
/>
|
||||||
|
<FlashMessage dispatcher={this.props.dispatcher} flashMessage={this.props.flashMessage} />
|
||||||
|
{this.props.blockchainIsLoaded && (
|
||||||
|
<LedgerConfigDialog
|
||||||
|
providerType={this.props.providerType}
|
||||||
|
networkId={this.props.networkId}
|
||||||
|
blockchain={this._blockchain}
|
||||||
|
dispatcher={this.props.dispatcher}
|
||||||
|
toggleDialogFn={this._onToggleLedgerDialog.bind(this)}
|
||||||
|
isOpen={this.state.isLedgerDialogOpen}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<AssetPicker
|
||||||
|
userAddress={this.props.userAddress}
|
||||||
|
networkId={this.props.networkId}
|
||||||
|
blockchain={this._blockchain}
|
||||||
|
dispatcher={this.props.dispatcher}
|
||||||
|
isOpen={this.state.isAssetPickerDialogOpen}
|
||||||
|
currentTokenAddress={''}
|
||||||
|
onTokenChosen={this._onTokenChosen.bind(this)}
|
||||||
|
tokenByAddress={this.props.tokenByAddress}
|
||||||
|
tokenVisibility={TokenVisibility.UNTRACKED}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
private _onTokenChosen(tokenAddress: string) {
|
||||||
|
if (_.isEmpty(tokenAddress)) {
|
||||||
|
this.setState({
|
||||||
|
isAssetPickerDialogOpen: false,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const token = this.props.tokenByAddress[tokenAddress];
|
||||||
|
this.props.dispatcher.updateTokenByAddress([token]);
|
||||||
|
this.setState({
|
||||||
|
isAssetPickerDialogOpen: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
private _onToggleLedgerDialog() {
|
||||||
|
this.setState({
|
||||||
|
isLedgerDialogOpen: !this.state.isLedgerDialogOpen,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
private _onAddToken() {
|
||||||
|
this.setState({
|
||||||
|
isAssetPickerDialogOpen: !this.state.isAssetPickerDialogOpen,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
private _onPortalDisclaimerAccepted() {
|
||||||
|
localStorage.setItem(constants.LOCAL_STORAGE_KEY_ACCEPT_DISCLAIMER, 'set');
|
||||||
|
this.setState({
|
||||||
|
isDisclaimerDialogOpen: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
private _updateScreenWidth() {
|
||||||
|
const newScreenWidth = utils.getScreenWidth();
|
||||||
|
this.props.dispatcher.updateScreenWidth(newScreenWidth);
|
||||||
|
}
|
||||||
|
}
|
@ -12,37 +12,6 @@ export interface RelayerGridTileProps {
|
|||||||
networkId: number;
|
networkId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Get top tokens from remote
|
|
||||||
const topTokens = [
|
|
||||||
{
|
|
||||||
address: '0x1dad4783cf3fe3085c1426157ab175a6119a04ba',
|
|
||||||
decimals: 18,
|
|
||||||
iconUrl: '/images/token_icons/makerdao.png',
|
|
||||||
isRegistered: true,
|
|
||||||
isTracked: true,
|
|
||||||
name: 'Maker DAO',
|
|
||||||
symbol: 'MKR',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
address: '0x323b5d4c32345ced77393b3530b1eed0f346429d',
|
|
||||||
decimals: 18,
|
|
||||||
iconUrl: '/images/token_icons/melon.png',
|
|
||||||
isRegistered: true,
|
|
||||||
isTracked: true,
|
|
||||||
name: 'Melon Token',
|
|
||||||
symbol: 'MLN',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
address: '0xb18845c260f680d5b9d84649638813e342e4f8c9',
|
|
||||||
decimals: 18,
|
|
||||||
iconUrl: '/images/token_icons/augur.png',
|
|
||||||
isRegistered: true,
|
|
||||||
isTracked: true,
|
|
||||||
name: 'Augur Reputation Token',
|
|
||||||
symbol: 'REP',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const styles: Styles = {
|
const styles: Styles = {
|
||||||
root: {
|
root: {
|
||||||
backgroundColor: colors.white,
|
backgroundColor: colors.white,
|
||||||
@ -93,28 +62,63 @@ const styles: Styles = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const FALLBACK_IMG_SRC = '/images/landing/hero_chip_image.png';
|
||||||
|
|
||||||
export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = (props: RelayerGridTileProps) => {
|
export const RelayerGridTile: React.StatelessComponent<RelayerGridTileProps> = (props: RelayerGridTileProps) => {
|
||||||
const link = props.relayerInfo.appUrl || props.relayerInfo.url;
|
const link = props.relayerInfo.appUrl || props.relayerInfo.url;
|
||||||
return (
|
return (
|
||||||
<a href={link} target="_blank" style={{ textDecoration: 'none' }}>
|
<GridTile style={styles.root}>
|
||||||
<GridTile style={styles.root}>
|
<div style={styles.innerDiv}>
|
||||||
<div style={styles.innerDiv}>
|
<a href={link} target="_blank" style={{ textDecoration: 'none' }}>
|
||||||
<img src={props.relayerInfo.headerImgUrl} style={styles.header} />
|
<ImgWithFallback
|
||||||
<div style={styles.body}>
|
src={props.relayerInfo.headerImgUrl}
|
||||||
<div className="py1" style={styles.relayerNameLabel}>
|
fallbackSrc={FALLBACK_IMG_SRC}
|
||||||
{props.relayerInfo.name}
|
style={styles.header}
|
||||||
</div>
|
/>
|
||||||
<div style={styles.dailyTradeVolumeLabel}>{props.relayerInfo.dailyTxnVolume}</div>
|
</a>
|
||||||
<div className="py1" style={styles.subLabel}>
|
<div style={styles.body}>
|
||||||
Daily Trade Volume
|
<div className="py1" style={styles.relayerNameLabel}>
|
||||||
</div>
|
{props.relayerInfo.name}
|
||||||
<TopTokens tokens={topTokens} networkId={props.networkId} />
|
</div>
|
||||||
<div className="py1" style={styles.subLabel}>
|
<div style={styles.dailyTradeVolumeLabel}>{props.relayerInfo.dailyTxnVolume}</div>
|
||||||
Top tokens
|
<div className="py1" style={styles.subLabel}>
|
||||||
</div>
|
Daily Trade Volume
|
||||||
|
</div>
|
||||||
|
<TopTokens tokens={props.relayerInfo.topTokens} networkId={props.networkId} />
|
||||||
|
<div className="py1" style={styles.subLabel}>
|
||||||
|
Top tokens
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</GridTile>
|
</div>
|
||||||
</a>
|
</GridTile>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface ImgWithFallbackProps {
|
||||||
|
src?: string;
|
||||||
|
fallbackSrc: string;
|
||||||
|
style: React.CSSProperties;
|
||||||
|
}
|
||||||
|
interface ImgWithFallbackState {
|
||||||
|
imageLoadFailed: boolean;
|
||||||
|
}
|
||||||
|
class ImgWithFallback extends React.Component<ImgWithFallbackProps, ImgWithFallbackState> {
|
||||||
|
constructor(props: ImgWithFallbackProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
imageLoadFailed: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public render() {
|
||||||
|
if (this.state.imageLoadFailed || _.isUndefined(this.props.src)) {
|
||||||
|
return <img src={this.props.fallbackSrc} style={this.props.style} />;
|
||||||
|
} else {
|
||||||
|
return <img src={this.props.src} onError={this._onError.bind(this)} style={this.props.style} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private _onError() {
|
||||||
|
this.setState({
|
||||||
|
imageLoadFailed: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { colors, Styles } from '@0xproject/react-shared';
|
import { colors, Styles } from '@0xproject/react-shared';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
import CircularProgress from 'material-ui/CircularProgress';
|
||||||
|
import FlatButton from 'material-ui/FlatButton';
|
||||||
import { GridList } from 'material-ui/GridList';
|
import { GridList } from 'material-ui/GridList';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
@ -32,9 +34,9 @@ const styles: Styles = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const CELL_HEIGHT = 260;
|
const CELL_HEIGHT = 290;
|
||||||
const NUMBER_OF_COLUMNS = 4;
|
const NUMBER_OF_COLUMNS = 4;
|
||||||
const GRID_PADDING = 16;
|
const GRID_PADDING = 20;
|
||||||
|
|
||||||
export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerIndexState> {
|
export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerIndexState> {
|
||||||
private _isUnmounted: boolean;
|
private _isUnmounted: boolean;
|
||||||
@ -55,7 +57,24 @@ export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerInde
|
|||||||
}
|
}
|
||||||
public render() {
|
public render() {
|
||||||
const readyToRender = _.isUndefined(this.state.error) && !_.isUndefined(this.state.relayerInfos);
|
const readyToRender = _.isUndefined(this.state.error) && !_.isUndefined(this.state.relayerInfos);
|
||||||
if (readyToRender) {
|
if (!readyToRender) {
|
||||||
|
return (
|
||||||
|
<div className="col col-12" style={{ ...styles.root, height: '100%' }}>
|
||||||
|
<div
|
||||||
|
className="relative sm-px2 sm-pt2 sm-m1"
|
||||||
|
style={{ height: 122, top: '33%', transform: 'translateY(-50%)' }}
|
||||||
|
>
|
||||||
|
<div className="center pb2">
|
||||||
|
{_.isUndefined(this.state.error) ? (
|
||||||
|
<CircularProgress size={40} thickness={5} />
|
||||||
|
) : (
|
||||||
|
<Retry onRetry={this._fetchRelayerInfosAsync.bind(this)} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
return (
|
return (
|
||||||
<div style={styles.root}>
|
<div style={styles.root}>
|
||||||
<GridList
|
<GridList
|
||||||
@ -64,23 +83,22 @@ export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerInde
|
|||||||
padding={GRID_PADDING}
|
padding={GRID_PADDING}
|
||||||
style={styles.gridList}
|
style={styles.gridList}
|
||||||
>
|
>
|
||||||
{this.state.relayerInfos.map((relayerInfo: WebsiteBackendRelayerInfo) => (
|
{this.state.relayerInfos.map((relayerInfo: WebsiteBackendRelayerInfo, index) => (
|
||||||
<RelayerGridTile
|
<RelayerGridTile key={index} relayerInfo={relayerInfo} networkId={this.props.networkId} />
|
||||||
key={relayerInfo.name}
|
|
||||||
relayerInfo={relayerInfo}
|
|
||||||
networkId={this.props.networkId}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</GridList>
|
</GridList>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
// TODO: loading and error states with a scrolling container
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private async _fetchRelayerInfosAsync(): Promise<void> {
|
private async _fetchRelayerInfosAsync(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
if (!this._isUnmounted) {
|
||||||
|
this.setState({
|
||||||
|
relayerInfos: undefined,
|
||||||
|
error: undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
const relayerInfos = await backendClient.getRelayerInfosAsync();
|
const relayerInfos = await backendClient.getRelayerInfosAsync();
|
||||||
if (!this._isUnmounted) {
|
if (!this._isUnmounted) {
|
||||||
this.setState({
|
this.setState({
|
||||||
@ -96,3 +114,31 @@ export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerInde
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RetryProps {
|
||||||
|
onRetry: () => void;
|
||||||
|
}
|
||||||
|
const Retry = (props: RetryProps) => (
|
||||||
|
<div className="clearfix center" style={{ color: colors.black }}>
|
||||||
|
<div className="mx-auto inline-block align-middle" style={{ lineHeight: '44px', textAlign: 'center' }}>
|
||||||
|
<div className="h2" style={{ fontFamily: 'Roboto Mono' }}>
|
||||||
|
Something went wrong.
|
||||||
|
</div>
|
||||||
|
<div className="py3">
|
||||||
|
<FlatButton
|
||||||
|
label={'reload'}
|
||||||
|
backgroundColor={colors.black}
|
||||||
|
labelStyle={{
|
||||||
|
fontSize: 18,
|
||||||
|
fontFamily: 'Roboto Mono',
|
||||||
|
fontWeight: 'lighter',
|
||||||
|
color: colors.white,
|
||||||
|
textTransform: 'lowercase',
|
||||||
|
}}
|
||||||
|
style={{ width: 280, height: 62, borderRadius: 5 }}
|
||||||
|
onClick={props.onRetry}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
@ -3,10 +3,10 @@ import * as _ from 'lodash';
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { TokenIcon } from 'ts/components/ui/token_icon';
|
import { TokenIcon } from 'ts/components/ui/token_icon';
|
||||||
import { Token } from 'ts/types';
|
import { WebsiteBackendTokenInfo } from 'ts/types';
|
||||||
|
|
||||||
export interface TopTokensProps {
|
export interface TopTokensProps {
|
||||||
tokens: Token[];
|
tokens: WebsiteBackendTokenInfo[];
|
||||||
networkId: number;
|
networkId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,17 +23,17 @@ const styles: Styles = {
|
|||||||
export const TopTokens: React.StatelessComponent<TopTokensProps> = (props: TopTokensProps) => {
|
export const TopTokens: React.StatelessComponent<TopTokensProps> = (props: TopTokensProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
{_.map(props.tokens, (token: Token, index: number) => {
|
{_.map(props.tokens, (tokenInfo: WebsiteBackendTokenInfo, index: number) => {
|
||||||
const firstItemStyle = { ...styles.tokenLabel, ...styles.followingTokenLabel };
|
const firstItemStyle = { ...styles.tokenLabel, ...styles.followingTokenLabel };
|
||||||
const style = index !== 0 ? firstItemStyle : styles.tokenLabel;
|
const style = index !== 0 ? firstItemStyle : styles.tokenLabel;
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
key={token.address}
|
key={tokenInfo.address}
|
||||||
href={tokenLinkFromToken(token, props.networkId)}
|
href={tokenLinkFromToken(tokenInfo, props.networkId)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
{token.symbol}
|
{tokenInfo.symbol}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@ -41,6 +41,6 @@ export const TopTokens: React.StatelessComponent<TopTokensProps> = (props: TopTo
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function tokenLinkFromToken(token: Token, networkId: number) {
|
function tokenLinkFromToken(tokenInfo: WebsiteBackendTokenInfo, networkId: number) {
|
||||||
return sharedUtils.getEtherScanLinkIfExists(token.address, networkId, EtherscanLinkSuffixes.Address);
|
return sharedUtils.getEtherScanLinkIfExists(tokenInfo.address, networkId, EtherscanLinkSuffixes.Address);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { colors } from '@0xproject/react-shared';
|
import { colors, Styles } from '@0xproject/react-shared';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import RaisedButton from 'material-ui/RaisedButton';
|
import RaisedButton from 'material-ui/RaisedButton';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
@ -11,9 +11,9 @@ import { ProviderType } from 'ts/types';
|
|||||||
import { constants } from 'ts/utils/constants';
|
import { constants } from 'ts/utils/constants';
|
||||||
import { utils } from 'ts/utils/utils';
|
import { utils } from 'ts/utils/utils';
|
||||||
|
|
||||||
const IDENTICON_DIAMETER = 32;
|
const ROOT_HEIGHT = 24;
|
||||||
|
|
||||||
interface ProviderDisplayProps {
|
export interface ProviderDisplayProps {
|
||||||
dispatcher: Dispatcher;
|
dispatcher: Dispatcher;
|
||||||
userAddress: string;
|
userAddress: string;
|
||||||
networkId: number;
|
networkId: number;
|
||||||
@ -25,6 +25,15 @@ interface ProviderDisplayProps {
|
|||||||
|
|
||||||
interface ProviderDisplayState {}
|
interface ProviderDisplayState {}
|
||||||
|
|
||||||
|
const styles: Styles = {
|
||||||
|
root: {
|
||||||
|
height: ROOT_HEIGHT,
|
||||||
|
backgroundColor: colors.white,
|
||||||
|
borderRadius: ROOT_HEIGHT,
|
||||||
|
boxShadow: `0px 4px 6px ${colors.walletBoxShadow}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export class ProviderDisplay extends React.Component<ProviderDisplayProps, ProviderDisplayState> {
|
export class ProviderDisplay extends React.Component<ProviderDisplayProps, ProviderDisplayState> {
|
||||||
public render() {
|
public render() {
|
||||||
const isAddressAvailable = !_.isEmpty(this.props.userAddress);
|
const isAddressAvailable = !_.isEmpty(this.props.userAddress);
|
||||||
@ -40,21 +49,20 @@ export class ProviderDisplay extends React.Component<ProviderDisplayProps, Provi
|
|||||||
: 'Connect a wallet';
|
: 'Connect a wallet';
|
||||||
const providerTitle =
|
const providerTitle =
|
||||||
this.props.providerType === ProviderType.Injected ? injectedProviderName : 'Ledger Nano S';
|
this.props.providerType === ProviderType.Injected ? injectedProviderName : 'Ledger Nano S';
|
||||||
|
const isProviderMetamask = providerTitle === constants.PROVIDER_NAME_METAMASK;
|
||||||
const hoverActiveNode = (
|
const hoverActiveNode = (
|
||||||
<div className="flex right lg-pr0 md-pr2 sm-pr2" style={{ paddingTop: 16 }}>
|
<div className="flex right lg-pr0 md-pr2 sm-pr2 p1" style={styles.root}>
|
||||||
<div>
|
<div>
|
||||||
<Identicon address={this.props.userAddress} diameter={IDENTICON_DIAMETER} />
|
<Identicon address={this.props.userAddress} diameter={ROOT_HEIGHT} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginLeft: 12, paddingTop: 1 }}>
|
<div style={{ marginLeft: 12, paddingTop: 3 }}>
|
||||||
<div style={{ fontSize: 12, color: colors.amber800 }}>{providerTitle}</div>
|
<div style={{ fontSize: 16, color: colors.darkGrey }}>{displayAddress}</div>
|
||||||
<div style={{ fontSize: 14 }}>{displayAddress}</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
style={{ borderLeft: `1px solid ${colors.grey300}`, marginLeft: 17, paddingTop: 1 }}
|
|
||||||
className="px2"
|
|
||||||
>
|
|
||||||
<i style={{ fontSize: 30, color: colors.grey300 }} className="zmdi zmdi zmdi-chevron-down" />
|
|
||||||
</div>
|
</div>
|
||||||
|
{isProviderMetamask && (
|
||||||
|
<div style={{ marginLeft: 16 }}>
|
||||||
|
<img src="/images/metamask_icon.png" style={{ width: ROOT_HEIGHT, height: ROOT_HEIGHT }} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
const hasInjectedProvider =
|
const hasInjectedProvider =
|
||||||
|
@ -19,7 +19,12 @@ import { Deco, Key, ProviderType, WebsiteLegacyPaths, WebsitePaths } from 'ts/ty
|
|||||||
import { constants } from 'ts/utils/constants';
|
import { constants } from 'ts/utils/constants';
|
||||||
import { Translate } from 'ts/utils/translate';
|
import { Translate } from 'ts/utils/translate';
|
||||||
|
|
||||||
interface TopBarProps {
|
export enum TopBarDisplayType {
|
||||||
|
Default,
|
||||||
|
Expanded,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TopBarProps {
|
||||||
userAddress?: string;
|
userAddress?: string;
|
||||||
networkId?: number;
|
networkId?: number;
|
||||||
injectedProviderName?: string;
|
injectedProviderName?: string;
|
||||||
@ -34,7 +39,7 @@ interface TopBarProps {
|
|||||||
availableDocVersions?: string[];
|
availableDocVersions?: string[];
|
||||||
menu?: DocsMenu;
|
menu?: DocsMenu;
|
||||||
menuSubsectionsBySection?: MenuSubsectionsBySection;
|
menuSubsectionsBySection?: MenuSubsectionsBySection;
|
||||||
shouldFullWidth?: boolean;
|
displayType?: TopBarDisplayType;
|
||||||
docsInfo?: DocsInfo;
|
docsInfo?: DocsInfo;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
isNightVersion?: boolean;
|
isNightVersion?: boolean;
|
||||||
@ -47,17 +52,8 @@ interface TopBarState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const styles: Styles = {
|
const styles: Styles = {
|
||||||
address: {
|
|
||||||
marginRight: 12,
|
|
||||||
overflow: 'hidden',
|
|
||||||
paddingTop: 4,
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
width: 70,
|
|
||||||
},
|
|
||||||
topBar: {
|
topBar: {
|
||||||
backgroundColor: colors.white,
|
backgroundColor: colors.white,
|
||||||
height: 59,
|
|
||||||
width: '100%',
|
width: '100%',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
top: 0,
|
top: 0,
|
||||||
@ -78,12 +74,19 @@ const styles: Styles = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DEFAULT_HEIGHT = 59;
|
||||||
|
const EXPANDED_HEIGHT = 75;
|
||||||
|
|
||||||
export class TopBar extends React.Component<TopBarProps, TopBarState> {
|
export class TopBar extends React.Component<TopBarProps, TopBarState> {
|
||||||
public static defaultProps: Partial<TopBarProps> = {
|
public static defaultProps: Partial<TopBarProps> = {
|
||||||
shouldFullWidth: false,
|
displayType: TopBarDisplayType.Default,
|
||||||
style: {},
|
style: {},
|
||||||
isNightVersion: false,
|
isNightVersion: false,
|
||||||
};
|
};
|
||||||
|
public static heightForDisplayType(displayType: TopBarDisplayType) {
|
||||||
|
const result = displayType === TopBarDisplayType.Expanded ? EXPANDED_HEIGHT : DEFAULT_HEIGHT;
|
||||||
|
return result + 1;
|
||||||
|
}
|
||||||
constructor(props: TopBarProps) {
|
constructor(props: TopBarProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
@ -92,8 +95,9 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
|
|||||||
}
|
}
|
||||||
public render() {
|
public render() {
|
||||||
const isNightVersion = this.props.isNightVersion;
|
const isNightVersion = this.props.isNightVersion;
|
||||||
const isFullWidthPage = this.props.shouldFullWidth;
|
const isExpandedDisplayType = this.props.displayType === TopBarDisplayType.Expanded;
|
||||||
const parentClassNames = `flex mx-auto ${isFullWidthPage ? 'pl2' : 'max-width-4'}`;
|
const parentClassNames = `flex mx-auto ${isExpandedDisplayType ? 'pl3 py1' : 'max-width-4'}`;
|
||||||
|
const height = isExpandedDisplayType ? EXPANDED_HEIGHT : DEFAULT_HEIGHT;
|
||||||
const developerSectionMenuItems = [
|
const developerSectionMenuItems = [
|
||||||
<Link key="subMenuItem-zeroEx" to={WebsitePaths.ZeroExJs} className="text-decoration-none">
|
<Link key="subMenuItem-zeroEx" to={WebsitePaths.ZeroExJs} className="text-decoration-none">
|
||||||
<MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="0x.js" />
|
<MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="0x.js" />
|
||||||
@ -178,9 +182,11 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
|
|||||||
</a>,
|
</a>,
|
||||||
];
|
];
|
||||||
const bottomBorderStyle = this._shouldDisplayBottomBar() ? styles.bottomBar : {};
|
const bottomBorderStyle = this._shouldDisplayBottomBar() ? styles.bottomBar : {};
|
||||||
const fullWidthClasses = isFullWidthPage ? 'pr4' : '';
|
const fullWidthClasses = isExpandedDisplayType ? 'pr4' : '';
|
||||||
const logoUrl = isNightVersion ? '/images/protocol_logo_white.png' : '/images/protocol_logo_black.png';
|
const logoUrl = isNightVersion ? '/images/protocol_logo_white.png' : '/images/protocol_logo_black.png';
|
||||||
const menuClasses = `col col-${isFullWidthPage ? '4' : '5'} ${fullWidthClasses} lg-pr0 md-pr2 sm-hide xs-hide`;
|
const menuClasses = `col col-${
|
||||||
|
isExpandedDisplayType ? '4' : '5'
|
||||||
|
} ${fullWidthClasses} lg-pr0 md-pr2 sm-hide xs-hide`;
|
||||||
const menuIconStyle = {
|
const menuIconStyle = {
|
||||||
fontSize: 25,
|
fontSize: 25,
|
||||||
color: isNightVersion ? 'white' : 'black',
|
color: isNightVersion ? 'white' : 'black',
|
||||||
@ -197,15 +203,15 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
|
|||||||
);
|
);
|
||||||
const popoverContent = <Menu style={{ color: colors.darkGrey }}>{developerSectionMenuItems}</Menu>;
|
const popoverContent = <Menu style={{ color: colors.darkGrey }}>{developerSectionMenuItems}</Menu>;
|
||||||
return (
|
return (
|
||||||
<div style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style }} className="pb1">
|
<div style={{ ...styles.topBar, ...bottomBorderStyle, ...this.props.style, ...{ height } }} className="pb1">
|
||||||
<div className={parentClassNames}>
|
<div className={parentClassNames}>
|
||||||
<div className="col col-2 sm-pl1 md-pl2 lg-pl0" style={{ paddingTop: 15 }}>
|
<div className="col col-2 sm-pl1 md-pl2 lg-pl0" style={{ paddingTop: 15 }}>
|
||||||
<Link to={`${WebsitePaths.Home}`} className="text-decoration-none">
|
<Link to={`${WebsitePaths.Home}`} className="text-decoration-none">
|
||||||
<img src={logoUrl} height="30" />
|
<img src={logoUrl} height="30" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className={`col col-${isFullWidthPage ? '8' : '9'} lg-hide md-hide`} />
|
<div className={`col col-${isExpandedDisplayType ? '8' : '9'} lg-hide md-hide`} />
|
||||||
<div className={`col col-${isFullWidthPage ? '6' : '5'} sm-hide xs-hide`} />
|
<div className={`col col-${isExpandedDisplayType ? '6' : '5'} sm-hide xs-hide`} />
|
||||||
{!this._isViewingPortal() && (
|
{!this._isViewingPortal() && (
|
||||||
<div className={menuClasses}>
|
<div className={menuClasses}>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
@ -242,7 +248,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
|
|||||||
path={`${WebsitePaths.Portal}`}
|
path={`${WebsitePaths.Portal}`}
|
||||||
isPrimary={true}
|
isPrimary={true}
|
||||||
style={styles.menuItem}
|
style={styles.menuItem}
|
||||||
className={`${isFullWidthPage && 'md-hide'}`}
|
className={`${isExpandedDisplayType && 'md-hide'}`}
|
||||||
isNightVersion={isNightVersion}
|
isNightVersion={isNightVersion}
|
||||||
isExternal={false}
|
isExternal={false}
|
||||||
/>
|
/>
|
||||||
@ -250,7 +256,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{this.props.blockchainIsLoaded && (
|
{this.props.blockchainIsLoaded && (
|
||||||
<div className="sm-hide xs-hide col col-5">
|
<div className="sm-hide xs-hide col col-5" style={{ paddingTop: 8, marginRight: 36 }}>
|
||||||
<ProviderDisplay
|
<ProviderDisplay
|
||||||
dispatcher={this.props.dispatcher}
|
dispatcher={this.props.dispatcher}
|
||||||
userAddress={this.props.userAddress}
|
userAddress={this.props.userAddress}
|
||||||
@ -262,7 +268,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={`col ${isFullWidthPage ? 'col-2 pl2' : 'col-1'} md-hide lg-hide`}>
|
<div className={`col ${isExpandedDisplayType ? 'col-2 pl2' : 'col-1'} md-hide lg-hide`}>
|
||||||
<div style={menuIconStyle}>
|
<div style={menuIconStyle}>
|
||||||
<i className="zmdi zmdi-menu" onClick={this._onMenuButtonClick.bind(this)} />
|
<i className="zmdi zmdi-menu" onClick={this._onMenuButtonClick.bind(this)} />
|
||||||
</div>
|
</div>
|
||||||
@ -441,21 +447,6 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
private _renderUser() {
|
|
||||||
const userAddress = this.props.userAddress;
|
|
||||||
const identiconDiameter = 26;
|
|
||||||
return (
|
|
||||||
<div className="flex right lg-pr0 md-pr2 sm-pr2" style={{ paddingTop: 16 }}>
|
|
||||||
<div style={styles.address} data-tip={true} data-for="userAddressTooltip">
|
|
||||||
{!_.isEmpty(userAddress) ? userAddress : ''}
|
|
||||||
</div>
|
|
||||||
<ReactTooltip id="userAddressTooltip">{userAddress}</ReactTooltip>
|
|
||||||
<div>
|
|
||||||
<Identicon address={userAddress} diameter={identiconDiameter} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
private _onMenuButtonClick() {
|
private _onMenuButtonClick() {
|
||||||
this.setState({
|
this.setState({
|
||||||
isDrawerOpen: !this.state.isDrawerOpen,
|
isDrawerOpen: !this.state.isDrawerOpen,
|
||||||
@ -511,7 +502,8 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
|
|||||||
this._isViewingJsonSchemasDocs() ||
|
this._isViewingJsonSchemasDocs() ||
|
||||||
this._isViewingSolCovDocs() ||
|
this._isViewingSolCovDocs() ||
|
||||||
this._isViewingSubprovidersDocs() ||
|
this._isViewingSubprovidersDocs() ||
|
||||||
this._isViewingConnectDocs()
|
this._isViewingConnectDocs() ||
|
||||||
|
this._isViewingPortal()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} // tslint:disable:max-file-line-count
|
} // tslint:disable:max-file-line-count
|
||||||
|
@ -55,11 +55,13 @@ export interface WalletProps {
|
|||||||
injectedProviderName: string;
|
injectedProviderName: string;
|
||||||
providerType: ProviderType;
|
providerType: ProviderType;
|
||||||
onToggleLedgerDialog: () => void;
|
onToggleLedgerDialog: () => void;
|
||||||
|
onAddToken: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WalletState {
|
interface WalletState {
|
||||||
trackedTokenStateByAddress: TokenStateByAddress;
|
trackedTokenStateByAddress: TokenStateByAddress;
|
||||||
wrappedEtherDirection?: Side;
|
wrappedEtherDirection?: Side;
|
||||||
|
isHoveringSidebar: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AllowanceToggleConfig {
|
interface AllowanceToggleConfig {
|
||||||
@ -94,6 +96,9 @@ const styles: Styles = {
|
|||||||
},
|
},
|
||||||
footerItemInnerDiv: {
|
footerItemInnerDiv: {
|
||||||
paddingLeft: 24,
|
paddingLeft: 24,
|
||||||
|
borderTopColor: colors.walletBorder,
|
||||||
|
borderTopStyle: 'solid',
|
||||||
|
borderWidth: 1,
|
||||||
},
|
},
|
||||||
borderedItem: {
|
borderedItem: {
|
||||||
borderBottomColor: colors.walletBorder,
|
borderBottomColor: colors.walletBorder,
|
||||||
@ -114,7 +119,17 @@ const styles: Styles = {
|
|||||||
paddingTop: 8,
|
paddingTop: 8,
|
||||||
paddingBottom: 8,
|
paddingBottom: 8,
|
||||||
},
|
},
|
||||||
accessoryItemsContainer: { width: 150, right: 8 },
|
accessoryItemsContainer: {
|
||||||
|
width: 150,
|
||||||
|
right: 8,
|
||||||
|
},
|
||||||
|
bodyInnerDiv: {
|
||||||
|
padding: 0,
|
||||||
|
// TODO: make this completely responsive
|
||||||
|
maxHeight: 475,
|
||||||
|
overflow: 'auto',
|
||||||
|
WebkitOverflowScrolling: 'touch',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const ETHER_ICON_PATH = '/images/ether.png';
|
const ETHER_ICON_PATH = '/images/ether.png';
|
||||||
@ -139,6 +154,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
|
|||||||
this.state = {
|
this.state = {
|
||||||
trackedTokenStateByAddress: initialTrackedTokenStateByAddress,
|
trackedTokenStateByAddress: initialTrackedTokenStateByAddress,
|
||||||
wrappedEtherDirection: undefined,
|
wrappedEtherDirection: undefined,
|
||||||
|
isHoveringSidebar: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
public componentWillMount() {
|
public componentWillMount() {
|
||||||
@ -184,12 +200,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
|
|||||||
return (
|
return (
|
||||||
<List style={styles.list}>
|
<List style={styles.list}>
|
||||||
{isAddressAvailable
|
{isAddressAvailable
|
||||||
? _.concat(
|
? _.concat(this._renderConnectedHeaderRows(), this._renderBody(), this._renderFooterRows())
|
||||||
this._renderConnectedHeaderRows(),
|
|
||||||
this._renderEthRows(),
|
|
||||||
this._renderTokenRows(),
|
|
||||||
this._renderFooterRows(),
|
|
||||||
)
|
|
||||||
: _.concat(this._renderDisconnectedHeaderRows(), this._renderDisconnectedRows())}
|
: _.concat(this._renderDisconnectedHeaderRows(), this._renderDisconnectedRows())}
|
||||||
</List>
|
</List>
|
||||||
);
|
);
|
||||||
@ -230,9 +241,43 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
private _renderBody() {
|
||||||
|
const bodyStyle: React.CSSProperties = {
|
||||||
|
...styles.bodyInnerDiv,
|
||||||
|
overflow: this.state.isHoveringSidebar ? 'auto' : 'hidden',
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<ListItem
|
||||||
|
key="body"
|
||||||
|
innerDivStyle={bodyStyle}
|
||||||
|
onMouseEnter={this._onSidebarHover.bind(this)}
|
||||||
|
onMouseLeave={this._onSidebarHoverOff.bind(this)}
|
||||||
|
>
|
||||||
|
{this._renderEthRows()}
|
||||||
|
{this._renderTokenRows()}
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
private _onSidebarHover(event: React.FormEvent<HTMLInputElement>) {
|
||||||
|
this.setState({
|
||||||
|
isHoveringSidebar: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
private _onSidebarHoverOff() {
|
||||||
|
this.setState({
|
||||||
|
isHoveringSidebar: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
private _renderFooterRows() {
|
private _renderFooterRows() {
|
||||||
const primaryText = '+ other tokens';
|
const primaryText = '+ other tokens';
|
||||||
return <ListItem key={FOOTER_ITEM_KEY} primaryText={primaryText} innerDivStyle={styles.footerItemInnerDiv} />;
|
return (
|
||||||
|
<ListItem
|
||||||
|
key={FOOTER_ITEM_KEY}
|
||||||
|
primaryText={primaryText}
|
||||||
|
innerDivStyle={styles.footerItemInnerDiv}
|
||||||
|
onClick={this.props.onAddToken}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
private _renderEthRows() {
|
private _renderEthRows() {
|
||||||
const primaryText = this._renderAmount(
|
const primaryText = this._renderAmount(
|
||||||
@ -293,7 +338,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
|
|||||||
);
|
);
|
||||||
return _.map(trackedTokensStartingWithEtherToken, this._renderTokenRow.bind(this));
|
return _.map(trackedTokensStartingWithEtherToken, this._renderTokenRow.bind(this));
|
||||||
}
|
}
|
||||||
private _renderTokenRow(token: Token) {
|
private _renderTokenRow(token: Token, index: number) {
|
||||||
const tokenState = this.state.trackedTokenStateByAddress[token.address];
|
const tokenState = this.state.trackedTokenStateByAddress[token.address];
|
||||||
const tokenLink = sharedUtils.getEtherScanLinkIfExists(
|
const tokenLink = sharedUtils.getEtherScanLinkIfExists(
|
||||||
token.address,
|
token.address,
|
||||||
@ -310,12 +355,14 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
|
|||||||
tokenState,
|
tokenState,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
// if this is the last item in the list, do not render the border, it is rendered by the footer
|
||||||
|
const borderedStyle = index !== this.props.trackedTokens.length - 1 ? styles.borderedItem : {};
|
||||||
const shouldShowWrapEtherItem =
|
const shouldShowWrapEtherItem =
|
||||||
!_.isUndefined(this.state.wrappedEtherDirection) &&
|
!_.isUndefined(this.state.wrappedEtherDirection) &&
|
||||||
this.state.wrappedEtherDirection === accessoryItemConfig.wrappedEtherDirection;
|
this.state.wrappedEtherDirection === accessoryItemConfig.wrappedEtherDirection;
|
||||||
const style = shouldShowWrapEtherItem
|
const style = shouldShowWrapEtherItem
|
||||||
? { ...walletItemStyles.focusedItem, ...styles.paddedItem }
|
? { ...walletItemStyles.focusedItem, ...styles.paddedItem }
|
||||||
: { ...styles.tokenItem, ...styles.borderedItem, ...styles.paddedItem };
|
: { ...styles.tokenItem, ...borderedStyle, ...styles.paddedItem };
|
||||||
const etherToken = this._getEthToken();
|
const etherToken = this._getEthToken();
|
||||||
return (
|
return (
|
||||||
<div key={token.address}>
|
<div key={token.address}>
|
||||||
|
88
packages/website/ts/containers/portal.ts
Normal file
88
packages/website/ts/containers/portal.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { BigNumber } from '@0xproject/utils';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import { Portal as PortalComponent, PortalProps as PortalComponentProps } from 'ts/components/portal/portal';
|
||||||
|
import { Dispatcher } from 'ts/redux/dispatcher';
|
||||||
|
import { State } from 'ts/redux/reducer';
|
||||||
|
import { BlockchainErrs, HashData, Order, ProviderType, ScreenWidths, Side, TokenByAddress } from 'ts/types';
|
||||||
|
import { constants } from 'ts/utils/constants';
|
||||||
|
import { Translate } from 'ts/utils/translate';
|
||||||
|
|
||||||
|
interface ConnectedState {
|
||||||
|
blockchainErr: BlockchainErrs;
|
||||||
|
blockchainIsLoaded: boolean;
|
||||||
|
hashData: HashData;
|
||||||
|
injectedProviderName: string;
|
||||||
|
networkId: number;
|
||||||
|
nodeVersion: string;
|
||||||
|
orderFillAmount: BigNumber;
|
||||||
|
providerType: ProviderType;
|
||||||
|
tokenByAddress: TokenByAddress;
|
||||||
|
lastForceTokenStateRefetch: number;
|
||||||
|
userEtherBalanceInWei: BigNumber;
|
||||||
|
screenWidth: ScreenWidths;
|
||||||
|
shouldBlockchainErrDialogBeOpen: boolean;
|
||||||
|
userAddress: string;
|
||||||
|
userSuppliedOrderCache: Order;
|
||||||
|
flashMessage?: string | React.ReactNode;
|
||||||
|
translate: Translate;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConnectedDispatch {
|
||||||
|
dispatcher: Dispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state: State, ownProps: PortalComponentProps): ConnectedState => {
|
||||||
|
const receiveAssetToken = state.sideToAssetToken[Side.Receive];
|
||||||
|
const depositAssetToken = state.sideToAssetToken[Side.Deposit];
|
||||||
|
const receiveAddress = !_.isUndefined(receiveAssetToken.address)
|
||||||
|
? receiveAssetToken.address
|
||||||
|
: constants.NULL_ADDRESS;
|
||||||
|
const depositAddress = !_.isUndefined(depositAssetToken.address)
|
||||||
|
? depositAssetToken.address
|
||||||
|
: constants.NULL_ADDRESS;
|
||||||
|
const receiveAmount = !_.isUndefined(receiveAssetToken.amount) ? receiveAssetToken.amount : new BigNumber(0);
|
||||||
|
const depositAmount = !_.isUndefined(depositAssetToken.amount) ? depositAssetToken.amount : new BigNumber(0);
|
||||||
|
const hashData = {
|
||||||
|
depositAmount,
|
||||||
|
depositTokenContractAddr: depositAddress,
|
||||||
|
feeRecipientAddress: constants.NULL_ADDRESS,
|
||||||
|
makerFee: constants.MAKER_FEE,
|
||||||
|
orderExpiryTimestamp: state.orderExpiryTimestamp,
|
||||||
|
orderMakerAddress: state.userAddress,
|
||||||
|
orderTakerAddress: state.orderTakerAddress !== '' ? state.orderTakerAddress : constants.NULL_ADDRESS,
|
||||||
|
receiveAmount,
|
||||||
|
receiveTokenContractAddr: receiveAddress,
|
||||||
|
takerFee: constants.TAKER_FEE,
|
||||||
|
orderSalt: state.orderSalt,
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
blockchainErr: state.blockchainErr,
|
||||||
|
blockchainIsLoaded: state.blockchainIsLoaded,
|
||||||
|
hashData,
|
||||||
|
injectedProviderName: state.injectedProviderName,
|
||||||
|
networkId: state.networkId,
|
||||||
|
nodeVersion: state.nodeVersion,
|
||||||
|
orderFillAmount: state.orderFillAmount,
|
||||||
|
providerType: state.providerType,
|
||||||
|
screenWidth: state.screenWidth,
|
||||||
|
shouldBlockchainErrDialogBeOpen: state.shouldBlockchainErrDialogBeOpen,
|
||||||
|
tokenByAddress: state.tokenByAddress,
|
||||||
|
lastForceTokenStateRefetch: state.lastForceTokenStateRefetch,
|
||||||
|
userAddress: state.userAddress,
|
||||||
|
userEtherBalanceInWei: state.userEtherBalanceInWei,
|
||||||
|
userSuppliedOrderCache: state.userSuppliedOrderCache,
|
||||||
|
flashMessage: state.flashMessage,
|
||||||
|
translate: state.translate,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({
|
||||||
|
dispatcher: new Dispatcher(dispatch),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Portal: React.ComponentClass<PortalComponentProps> = connect(mapStateToProps, mapDispatchToProps)(
|
||||||
|
PortalComponent,
|
||||||
|
);
|
@ -34,9 +34,14 @@ import 'less/all.less';
|
|||||||
// cause we only want to import the module when the user navigates to the page.
|
// cause we only want to import the module when the user navigates to the page.
|
||||||
// At the same time webpack statically parses for System.import() to determine bundle chunk split points
|
// At the same time webpack statically parses for System.import() to determine bundle chunk split points
|
||||||
// so each lazy import needs it's own `System.import()` declaration.
|
// so each lazy import needs it's own `System.import()` declaration.
|
||||||
const LazyPortal = createLazyComponent('LegacyPortal', async () =>
|
const LazyPortal =
|
||||||
System.import<any>(/* webpackChunkName: "legacyPortal" */ 'ts/containers/legacy_portal'),
|
utils.isDevelopment() || utils.isStaging()
|
||||||
);
|
? createLazyComponent('Portal', async () =>
|
||||||
|
System.import<any>(/* webpackChunkName: "portal" */ 'ts/containers/portal'),
|
||||||
|
)
|
||||||
|
: createLazyComponent('LegacyPortal', async () =>
|
||||||
|
System.import<any>(/* webpackChunkName: "legacyPortal" */ 'ts/containers/legacy_portal'),
|
||||||
|
);
|
||||||
const LazyZeroExJSDocumentation = createLazyComponent('Documentation', async () =>
|
const LazyZeroExJSDocumentation = createLazyComponent('Documentation', async () =>
|
||||||
System.import<any>(/* webpackChunkName: "zeroExDocs" */ 'ts/containers/zero_ex_js_documentation'),
|
System.import<any>(/* webpackChunkName: "zeroExDocs" */ 'ts/containers/zero_ex_js_documentation'),
|
||||||
);
|
);
|
||||||
|
@ -503,19 +503,26 @@ export interface TokenState {
|
|||||||
price?: BigNumber;
|
price?: BigNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add topTokens property once available from backend
|
|
||||||
export interface WebsiteBackendRelayerInfo {
|
export interface WebsiteBackendRelayerInfo {
|
||||||
name: string;
|
name: string;
|
||||||
dailyTxnVolume: string;
|
dailyTxnVolume: string;
|
||||||
url: string;
|
url: string;
|
||||||
appUrl?: string;
|
appUrl?: string;
|
||||||
headerImgUrl: string;
|
headerImgUrl?: string;
|
||||||
|
topTokens: WebsiteBackendTokenInfo[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WebsiteBackendPriceInfo {
|
export interface WebsiteBackendPriceInfo {
|
||||||
[symbol: string]: string;
|
[symbol: string]: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface WebsiteBackendTokenInfo {
|
||||||
|
address: string;
|
||||||
|
decimals: number;
|
||||||
|
name: string;
|
||||||
|
symbol: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface WebsiteBackendGasInfo {
|
export interface WebsiteBackendGasInfo {
|
||||||
average: number;
|
average: number;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user