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/order-watcher/test/artifacts
|
||||
packages/contract-wrappers/test/artifacts
|
||||
packages/migrations/src/artifacts
|
||||
packages/migrations/artifacts
|
||||
|
||||
# generated contract wrappers
|
||||
packages/0x.js/src/contract_wrappers/generated/
|
||||
|
@ -2,6 +2,6 @@ lib
|
||||
.nyc_output
|
||||
/packages/contracts/src/artifacts
|
||||
/packages/metacoin/artifacts
|
||||
/packages/migrations/src/artifacts
|
||||
/packages/migrations/artifacts/1.0.0
|
||||
package.json
|
||||
scripts/postpublish_utils.js
|
||||
|
@ -21,7 +21,7 @@
|
||||
"test": "run-s clean test:commonjs",
|
||||
"test:coverage": "nyc npm run test --all && yarn coverage:report:lcov",
|
||||
"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",
|
||||
"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",
|
||||
|
@ -108,8 +108,8 @@ for (const abiFileName of abiFileNames) {
|
||||
ABI = parsedContent; // ABI file
|
||||
} else if (!_.isUndefined(parsedContent.abi)) {
|
||||
ABI = parsedContent.abi; // Truffle artifact
|
||||
} else if (!_.isUndefined(parsedContent.networks) && !_.isUndefined(parsedContent.networks[args.networkId])) {
|
||||
ABI = parsedContent.networks[args.networkId].abi; // 0x contracts package artifact
|
||||
} else if (!_.isUndefined(parsedContent.compilerOutput.abi)) {
|
||||
ABI = parsedContent.compilerOutput.abi; // 0x artifact
|
||||
}
|
||||
if (_.isUndefined(ABI)) {
|
||||
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": {
|
||||
"build:watch": "tsc -w",
|
||||
"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",
|
||||
"test": "run-s build run_mocha",
|
||||
"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",
|
||||
"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",
|
||||
"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'",
|
||||
@ -26,8 +26,7 @@
|
||||
"test:circleci": "yarn test:coverage"
|
||||
},
|
||||
"config": {
|
||||
"abis": "../migrations/src/artifacts/@(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"
|
||||
"abis": "../migrations/artifacts/1.0.0/@(DummyToken|TokenTransferProxy|Exchange|TokenRegistry|MultiSigWallet|MultiSigWalletWithTimeLock|MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress|TokenRegistry|ZRXToken|Arbitrage|EtherDelta|AccountLevels).json"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -17,7 +17,7 @@ import { chaiSetup } from './utils/chai_setup';
|
||||
import { deployer } from './utils/deployer';
|
||||
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();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
|
@ -17,10 +17,9 @@ import { ContractName, SubmissionContractEventArgs, TransactionDataParams } from
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { deployer } from './utils/deployer';
|
||||
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 =
|
||||
artifacts.MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress.networks[constants.TESTRPC_NETWORK_ID]
|
||||
.abi;
|
||||
artifacts.MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress.compilerOutput.abi;
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { ContractArtifact } from '@0xproject/deployer';
|
||||
|
||||
import * as DummyToken from '../src/artifacts/DummyToken.json';
|
||||
import * as Exchange from '../src/artifacts/Exchange.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 ZRX from '../src/artifacts/ZRXToken.json';
|
||||
|
||||
import { Artifact } from './types';
|
||||
|
||||
export const artifacts = {
|
||||
ZRX: (ZRX as any) as Artifact,
|
||||
DummyToken: (DummyToken as any) as Artifact,
|
||||
Token: (Token as any) as Artifact,
|
||||
Exchange: (Exchange as any) as Artifact,
|
||||
EtherToken: (EtherToken as any) as Artifact,
|
||||
TokenRegistry: (TokenRegistry as any) as Artifact,
|
||||
MaliciousToken: (MaliciousToken as any) as Artifact,
|
||||
TokenTransferProxy: (TokenTransferProxy as any) as Artifact,
|
||||
MultiSigWalletWithTimeLock: (MultiSigWalletWithTimeLock as any) as Artifact,
|
||||
MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress: (MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress as any) as Artifact,
|
||||
ZRX: (ZRX as any) as ContractArtifact,
|
||||
DummyToken: (DummyToken as any) as ContractArtifact,
|
||||
Token: (Token as any) as ContractArtifact,
|
||||
Exchange: (Exchange as any) as ContractArtifact,
|
||||
EtherToken: (EtherToken as any) as ContractArtifact,
|
||||
TokenRegistry: (TokenRegistry as any) as ContractArtifact,
|
||||
MaliciousToken: (MaliciousToken as any) as ContractArtifact,
|
||||
TokenTransferProxy: (TokenTransferProxy as any) as ContractArtifact,
|
||||
MultiSigWalletWithTimeLock: (MultiSigWalletWithTimeLock as any) as ContractArtifact,
|
||||
MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress: (MultiSigWalletWithTimeLockExceptRemoveAuthorizedAddress as any) as ContractArtifact,
|
||||
};
|
||||
|
@ -100,19 +100,3 @@ export enum ContractName {
|
||||
EtherDelta = 'EtherDelta',
|
||||
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 { CliOptions, CompilerOptions, DeployerOptions } from './utils/types';
|
||||
|
||||
const DEFAULT_OPTIMIZER_ENABLED = false;
|
||||
const DEFAULT_CONTRACTS_DIR = path.resolve('src/contracts');
|
||||
const DEFAULT_ARTIFACTS_DIR = path.resolve('src/artifacts');
|
||||
const DEFAULT_NETWORK_ID = 50;
|
||||
const DEFAULT_JSONRPC_URL = 'http://localhost:8545';
|
||||
const DEFAULT_GAS_PRICE = (10 ** 9 * 2).toString();
|
||||
const DEFAULT_CONTRACTS_LIST = '*';
|
||||
const SEPARATOR = ',';
|
||||
|
||||
/**
|
||||
* Compiles all contracts with options passed in through CLI.
|
||||
@ -28,10 +28,8 @@ const DEFAULT_CONTRACTS_LIST = '*';
|
||||
async function onCompileCommandAsync(argv: CliOptions): Promise<void> {
|
||||
const opts: CompilerOptions = {
|
||||
contractsDir: argv.contractsDir,
|
||||
networkId: argv.networkId,
|
||||
optimizerEnabled: argv.shouldOptimize,
|
||||
artifactsDir: argv.artifactsDir,
|
||||
specifiedContracts: getContractsSetFromList(argv.contracts),
|
||||
contracts: argv.contracts === DEFAULT_CONTRACTS_LIST ? DEFAULT_CONTRACTS_LIST : argv.contracts.split(SEPARATOR),
|
||||
};
|
||||
await commands.compileAsync(opts);
|
||||
}
|
||||
@ -46,10 +44,8 @@ async function onDeployCommandAsync(argv: CliOptions): Promise<void> {
|
||||
const networkId = await web3Wrapper.getNetworkIdAsync();
|
||||
const compilerOpts: CompilerOptions = {
|
||||
contractsDir: argv.contractsDir,
|
||||
networkId,
|
||||
optimizerEnabled: argv.shouldOptimize,
|
||||
artifactsDir: argv.artifactsDir,
|
||||
specifiedContracts: getContractsSetFromList(argv.contracts),
|
||||
contracts: argv.contracts === DEFAULT_CONTRACTS_LIST ? DEFAULT_CONTRACTS_LIST : argv.contracts.split(SEPARATOR),
|
||||
};
|
||||
await commands.compileAsync(compilerOpts);
|
||||
|
||||
@ -58,45 +54,63 @@ async function onDeployCommandAsync(argv: CliOptions): Promise<void> {
|
||||
from: argv.account,
|
||||
};
|
||||
const deployerOpts: DeployerOptions = {
|
||||
artifactsDir: argv.artifactsDir,
|
||||
artifactsDir: argv.artifactsDir || DEFAULT_ARTIFACTS_DIR,
|
||||
jsonrpcUrl: argv.jsonrpcUrl,
|
||||
networkId,
|
||||
defaults,
|
||||
};
|
||||
const deployerArgsString = argv.args as string;
|
||||
const deployerArgs = deployerArgsString.split(',');
|
||||
const deployerArgsString = argv.constructorArgs as string;
|
||||
const deployerArgs = deployerArgsString.split(SEPARATOR);
|
||||
await commands.deployAsync(argv.contract as string, deployerArgs, deployerOpts);
|
||||
}
|
||||
/**
|
||||
* Creates a set of contracts to compile.
|
||||
* @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.
|
||||
* Adds additional required options for when the user is calling the deploy command.
|
||||
* @param yargsInstance yargs instance provided in builder function callback.
|
||||
*/
|
||||
function deployCommandBuilder(yargsInstance: any) {
|
||||
return yargsInstance
|
||||
.option('network-id', {
|
||||
type: 'number',
|
||||
default: DEFAULT_NETWORK_ID,
|
||||
description: 'mainnet=1, kovan=42, testrpc=50',
|
||||
})
|
||||
.option('contract', {
|
||||
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',
|
||||
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;
|
||||
}
|
||||
|
||||
@ -105,44 +119,14 @@ function deployCommandBuilder(yargsInstance: any) {
|
||||
return yargs
|
||||
.option('contracts-dir', {
|
||||
type: 'string',
|
||||
default: DEFAULT_CONTRACTS_DIR,
|
||||
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', {
|
||||
type: 'string',
|
||||
default: DEFAULT_ARTIFACTS_DIR,
|
||||
description: 'path to write contracts artifacts to',
|
||||
})
|
||||
.option('jsonrpc-url', {
|
||||
type: 'string',
|
||||
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))
|
||||
.demandCommand(1)
|
||||
.command('compile', 'compile contracts', compileCommandBuilder, consoleReporter(onCompileCommandAsync))
|
||||
.command(
|
||||
'deploy',
|
||||
'deploy a single contract with provided arguments',
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
FSResolver,
|
||||
NameResolver,
|
||||
NPMResolver,
|
||||
RelativeFSResolver,
|
||||
Resolver,
|
||||
URLResolver,
|
||||
} from '@0xproject/sol-resolver';
|
||||
@ -38,11 +39,30 @@ import {
|
||||
ContractNetworks,
|
||||
ContractSourceData,
|
||||
ContractSpecificSourceData,
|
||||
ContractVersionData,
|
||||
} from './utils/types';
|
||||
import { utils } from './utils/utils';
|
||||
|
||||
type TYPE_ALL_FILES_IDENTIFIER = '*';
|
||||
const ALL_CONTRACTS_IDENTIFIER = '*';
|
||||
const ALL_FILES_IDENTIFIER = '*';
|
||||
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
|
||||
@ -52,26 +72,28 @@ export class Compiler {
|
||||
private _resolver: Resolver;
|
||||
private _nameResolver: NameResolver;
|
||||
private _contractsDir: string;
|
||||
private _networkId: number;
|
||||
private _optimizerEnabled: boolean;
|
||||
private _compilerSettings: solc.CompilerSettings;
|
||||
private _artifactsDir: string;
|
||||
private _specifiedContracts: Set<string> = new Set();
|
||||
private _specifiedContracts: string[] | TYPE_ALL_FILES_IDENTIFIER;
|
||||
/**
|
||||
* Instantiates a new instance of the Compiler class.
|
||||
* @param opts Options specifying directories, network, and optimization settings.
|
||||
* @return An instance of the Compiler class.
|
||||
*/
|
||||
constructor(opts: CompilerOptions) {
|
||||
this._contractsDir = opts.contractsDir;
|
||||
this._networkId = opts.networkId;
|
||||
this._optimizerEnabled = opts.optimizerEnabled;
|
||||
this._artifactsDir = opts.artifactsDir;
|
||||
this._specifiedContracts = opts.specifiedContracts;
|
||||
// TODO: Look for config file in parent directories if not found in current directory
|
||||
const config: CompilerOptions = fs.existsSync(CONFIG_FILE)
|
||||
? JSON.parse(fs.readFileSync(CONFIG_FILE).toString())
|
||||
: {};
|
||||
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));
|
||||
const resolver = new FallthroughResolver();
|
||||
resolver.appendResolver(new URLResolver());
|
||||
const packagePath = path.resolve('');
|
||||
resolver.appendResolver(new NPMResolver(packagePath));
|
||||
resolver.appendResolver(new RelativeFSResolver(this._contractsDir));
|
||||
resolver.appendResolver(new FSResolver());
|
||||
resolver.appendResolver(this._nameResolver);
|
||||
this._resolver = resolver;
|
||||
@ -83,13 +105,13 @@ export class Compiler {
|
||||
await createDirIfDoesNotExistAsync(this._artifactsDir);
|
||||
await createDirIfDoesNotExistAsync(SOLC_BIN_DIR);
|
||||
let contractNamesToCompile: string[] = [];
|
||||
if (this._specifiedContracts.has(ALL_CONTRACTS_IDENTIFIER)) {
|
||||
if (this._specifiedContracts === ALL_CONTRACTS_IDENTIFIER) {
|
||||
const allContracts = this._nameResolver.getAll();
|
||||
contractNamesToCompile = _.map(allContracts, contractSource =>
|
||||
path.basename(contractSource.path, constants.SOLIDITY_FILE_EXTENSION),
|
||||
);
|
||||
} else {
|
||||
contractNamesToCompile = Array.from(this._specifiedContracts.values());
|
||||
contractNamesToCompile = this._specifiedContracts;
|
||||
}
|
||||
for (const contractNameToCompile of contractNamesToCompile) {
|
||||
await this._compileContractAsync(contractNameToCompile);
|
||||
@ -101,17 +123,18 @@ export class Compiler {
|
||||
*/
|
||||
private async _compileContractAsync(contractName: string): Promise<void> {
|
||||
const contractSource = this._resolver.resolve(contractName);
|
||||
const absoluteContractPath = path.join(this._contractsDir, contractSource.path);
|
||||
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;
|
||||
if (_.isUndefined(currentArtifactIfExists)) {
|
||||
shouldCompile = true;
|
||||
} else {
|
||||
const currentArtifact = currentArtifactIfExists as ContractArtifact;
|
||||
shouldCompile =
|
||||
currentArtifact.networks[this._networkId].optimizer_enabled !== this._optimizerEnabled ||
|
||||
currentArtifact.networks[this._networkId].source_tree_hash !== sourceTreeHashHex;
|
||||
const isUserOnLatestVersion = currentArtifact.schemaVersion === constants.LATEST_ARTIFACT_VERSION;
|
||||
const didCompilerSettingsChange = !_.isEqual(currentArtifact.compiler.settings, this._compilerSettings);
|
||||
const didSourceChange = currentArtifact.sourceTreeHashHex !== sourceTreeHashHex;
|
||||
shouldCompile = isUserOnLatestVersion || didCompilerSettingsChange || didSourceChange;
|
||||
}
|
||||
if (!shouldCompile) {
|
||||
return;
|
||||
@ -139,30 +162,14 @@ export class Compiler {
|
||||
|
||||
logUtils.log(`Compiling ${contractName} with Solidity v${solcVersion}...`);
|
||||
const source = contractSource.source;
|
||||
const absoluteFilePath = contractSource.path;
|
||||
const standardInput: solc.StandardInput = {
|
||||
language: 'Solidity',
|
||||
sources: {
|
||||
[absoluteFilePath]: {
|
||||
urls: [`file://${absoluteFilePath}`],
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
optimizer: {
|
||||
enabled: this._optimizerEnabled,
|
||||
},
|
||||
outputSelection: {
|
||||
'*': {
|
||||
'*': [
|
||||
'abi',
|
||||
'evm.bytecode.object',
|
||||
'evm.bytecode.sourceMap',
|
||||
'evm.deployedBytecode.object',
|
||||
'evm.deployedBytecode.sourceMap',
|
||||
],
|
||||
},
|
||||
[contractSource.path]: {
|
||||
content: contractSource.source,
|
||||
},
|
||||
},
|
||||
settings: this._compilerSettings,
|
||||
};
|
||||
const compiled: solc.StandardOutput = JSON.parse(
|
||||
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)) {
|
||||
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 bytecode = `0x${compiledData.evm.bytecode.object}`;
|
||||
const runtimeBytecode = `0x${compiledData.evm.deployedBytecode.object}`;
|
||||
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 sourceCodes = _.mapValues(
|
||||
compiled.sources,
|
||||
(_1, sourceFilePath) => this._resolver.resolve(sourceFilePath).source,
|
||||
);
|
||||
const updated_at = Date.now();
|
||||
const contractNetworkData: ContractNetworkData = {
|
||||
solc_version: solcVersion,
|
||||
source_tree_hash: sourceTreeHashHex,
|
||||
optimizer_enabled: this._optimizerEnabled,
|
||||
abi,
|
||||
bytecode,
|
||||
runtime_bytecode: runtimeBytecode,
|
||||
updated_at,
|
||||
source_map: sourceMap,
|
||||
source_map_runtime: sourceMapRuntime,
|
||||
sources,
|
||||
const contractVersion: ContractVersionData = {
|
||||
compilerOutput: compiledData,
|
||||
sources: compiled.sources,
|
||||
sourceCodes,
|
||||
sourceTreeHashHex,
|
||||
compiler: {
|
||||
name: 'solc',
|
||||
version: solcVersion,
|
||||
settings: this._compilerSettings,
|
||||
},
|
||||
};
|
||||
|
||||
let newArtifact: ContractArtifact;
|
||||
@ -223,17 +224,14 @@ export class Compiler {
|
||||
const currentArtifact = currentArtifactIfExists as ContractArtifact;
|
||||
newArtifact = {
|
||||
...currentArtifact,
|
||||
networks: {
|
||||
...currentArtifact.networks,
|
||||
[this._networkId]: contractNetworkData,
|
||||
},
|
||||
...contractVersion,
|
||||
};
|
||||
} else {
|
||||
newArtifact = {
|
||||
contract_name: contractName,
|
||||
networks: {
|
||||
[this._networkId]: contractNetworkData,
|
||||
},
|
||||
schemaVersion: constants.LATEST_ARTIFACT_VERSION,
|
||||
contractName,
|
||||
...contractVersion,
|
||||
networks: {},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ import { AbiType, ConstructorAbi, ContractAbi, Provider, TxData } from '@0xproje
|
||||
import { logUtils } from '@0xproject/utils';
|
||||
import { Web3Wrapper } from '@0xproject/web3-wrapper';
|
||||
import * as _ from 'lodash';
|
||||
import * as solc from 'solc';
|
||||
import * as Web3 from 'web3';
|
||||
|
||||
import { Contract } from './utils/contract';
|
||||
@ -28,7 +29,20 @@ export class Deployer {
|
||||
private _artifactsDir: string;
|
||||
private _networkId: number;
|
||||
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.
|
||||
* @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> {
|
||||
const contractArtifactIfExists: ContractArtifact = this._loadContractArtifactIfExists(contractName);
|
||||
const contractNetworkDataIfExists: ContractNetworkData = this._getContractNetworkDataFromArtifactIfExists(
|
||||
contractArtifactIfExists,
|
||||
);
|
||||
const data = contractNetworkDataIfExists.bytecode;
|
||||
const compilerOutput = Deployer._getContractCompilerOutputFromArtifactIfExists(contractArtifactIfExists);
|
||||
const data = compilerOutput.evm.bytecode.object;
|
||||
const from = await this._getFromAddressAsync();
|
||||
const gas = await this._getAllowableGasEstimateAsync(data);
|
||||
const txData = {
|
||||
@ -70,7 +82,10 @@ export class Deployer {
|
||||
data,
|
||||
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 constructorArgs = _.isUndefined(constructorAbi) ? [] : constructorAbi.inputs;
|
||||
if (constructorArgs.length !== args.length) {
|
||||
@ -138,15 +153,16 @@ export class Deployer {
|
||||
args: any[],
|
||||
): Promise<void> {
|
||||
const contractArtifactIfExists: ContractArtifact = this._loadContractArtifactIfExists(contractName);
|
||||
const contractNetworkDataIfExists: ContractNetworkData = this._getContractNetworkDataFromArtifactIfExists(
|
||||
contractArtifactIfExists,
|
||||
);
|
||||
const abi = contractNetworkDataIfExists.abi;
|
||||
const compilerOutput = Deployer._getContractCompilerOutputFromArtifactIfExists(contractArtifactIfExists);
|
||||
if (_.isUndefined(compilerOutput.abi)) {
|
||||
throw new Error(`ABI not found in ${contractName} artifacts`);
|
||||
}
|
||||
const abi = compilerOutput.abi;
|
||||
const encodedConstructorArgs = encoder.encodeConstructorArgsFromAbi(args, abi);
|
||||
const newContractData = {
|
||||
...contractNetworkDataIfExists,
|
||||
const newContractData: ContractNetworkData = {
|
||||
address: contractAddress,
|
||||
constructor_args: encodedConstructorArgs,
|
||||
links: {},
|
||||
constructorArgs: encodedConstructorArgs,
|
||||
};
|
||||
const newArtifact = {
|
||||
...contractArtifactIfExists,
|
||||
@ -173,18 +189,6 @@ export class Deployer {
|
||||
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.
|
||||
* @return The default from address. If not specified, returns the first address accessible by web3.
|
||||
|
@ -1,2 +1,3 @@
|
||||
export { Deployer } from './deployer';
|
||||
export { Compiler } from './compiler';
|
||||
export { ContractArtifact, ContractNetworks } from './utils/types';
|
||||
|
@ -1,4 +1,5 @@
|
||||
export const constants = {
|
||||
SOLIDITY_FILE_EXTENSION: '.sol',
|
||||
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 * as solc from 'solc';
|
||||
import * as Web3 from 'web3';
|
||||
import * as yargs from 'yargs';
|
||||
|
||||
@ -9,28 +10,40 @@ export enum AbiType {
|
||||
Fallback = 'fallback',
|
||||
}
|
||||
|
||||
export interface ContractArtifact {
|
||||
contract_name: string;
|
||||
export interface ContractArtifact extends ContractVersionData {
|
||||
schemaVersion: string;
|
||||
contractName: string;
|
||||
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 {
|
||||
[key: number]: ContractNetworkData;
|
||||
[networkId: number]: ContractNetworkData;
|
||||
}
|
||||
|
||||
export interface ContractNetworkData {
|
||||
solc_version: string;
|
||||
optimizer_enabled: boolean;
|
||||
source_tree_hash: string;
|
||||
abi: ContractAbi;
|
||||
bytecode: string;
|
||||
runtime_bytecode: string;
|
||||
address?: string;
|
||||
constructor_args?: string;
|
||||
updated_at: number;
|
||||
source_map: string;
|
||||
source_map_runtime: string;
|
||||
sources: string[];
|
||||
address: string;
|
||||
links: {
|
||||
[linkName: string]: string;
|
||||
};
|
||||
constructorArgs: string;
|
||||
}
|
||||
|
||||
export interface SolcErrors {
|
||||
@ -42,7 +55,6 @@ export interface CliOptions extends yargs.Arguments {
|
||||
contractsDir: string;
|
||||
jsonrpcUrl: string;
|
||||
networkId: number;
|
||||
shouldOptimize: boolean;
|
||||
gasPrice: string;
|
||||
account?: string;
|
||||
contract?: string;
|
||||
@ -50,11 +62,10 @@ export interface CliOptions extends yargs.Arguments {
|
||||
}
|
||||
|
||||
export interface CompilerOptions {
|
||||
contractsDir: string;
|
||||
networkId: number;
|
||||
optimizerEnabled: boolean;
|
||||
artifactsDir: string;
|
||||
specifiedContracts: Set<string>;
|
||||
contractsDir?: string;
|
||||
artifactsDir?: string;
|
||||
compilerSettings?: solc.CompilerSettings;
|
||||
contracts?: string[] | '*';
|
||||
}
|
||||
|
||||
export interface BaseDeployerOptions {
|
||||
|
@ -19,9 +19,7 @@ describe('#Compiler', function() {
|
||||
const compilerOpts: CompilerOptions = {
|
||||
artifactsDir,
|
||||
contractsDir,
|
||||
networkId: constants.networkId,
|
||||
optimizerEnabled: constants.optimizerEnabled,
|
||||
specifiedContracts: new Set(constants.specifiedContracts),
|
||||
contracts: constants.contracts,
|
||||
};
|
||||
const compiler = new Compiler(compilerOpts);
|
||||
beforeEach((done: DoneCallback) => {
|
||||
@ -39,9 +37,8 @@ describe('#Compiler', function() {
|
||||
};
|
||||
const exchangeArtifactString = await fsWrapper.readFileAsync(exchangeArtifactPath, opts);
|
||||
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
|
||||
const unlinkedBinaryWithoutMetadata = exchangeContractData.bytecode.slice(0, -86);
|
||||
const unlinkedBinaryWithoutMetadata = exchangeArtifact.compilerOutput.evm.bytecode.object.slice(0, -86);
|
||||
const exchangeBinaryWithoutMetadata = exchange_binary.slice(0, -86);
|
||||
expect(unlinkedBinaryWithoutMetadata).to.equal(exchangeBinaryWithoutMetadata);
|
||||
});
|
||||
|
@ -20,9 +20,7 @@ describe('#Deployer', () => {
|
||||
const compilerOpts: CompilerOptions = {
|
||||
artifactsDir,
|
||||
contractsDir,
|
||||
networkId: constants.networkId,
|
||||
optimizerEnabled: constants.optimizerEnabled,
|
||||
specifiedContracts: new Set(constants.specifiedContracts),
|
||||
contracts: constants.contracts,
|
||||
};
|
||||
const compiler = new Compiler(compilerOpts);
|
||||
const deployerOpts = {
|
||||
@ -56,8 +54,7 @@ describe('#Deployer', () => {
|
||||
const exchangeContractData: ContractNetworkData = exchangeArtifact.networks[constants.networkId];
|
||||
const exchangeAddress = exchangeContractInstance.address;
|
||||
expect(exchangeAddress).to.not.equal(undefined);
|
||||
expect(exchangeContractData.address).to.equal(undefined);
|
||||
expect(exchangeContractData.constructor_args).to.equal(undefined);
|
||||
expect(exchangeContractData).to.equal(undefined);
|
||||
});
|
||||
});
|
||||
describe('#deployAndSaveAsync', () => {
|
||||
@ -72,7 +69,7 @@ describe('#Deployer', () => {
|
||||
const exchangeContractData: ContractNetworkData = exchangeArtifact.networks[constants.networkId];
|
||||
const exchangeAddress = exchangeContractInstance.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,
|
||||
zrxTokenAddress: '0xe41d2489571d322189246dafa5ebde1f4699f498',
|
||||
tokenTransferProxyAddress: '0x8da0d80f5007ef1e431dd2127178d224e32c2ef4',
|
||||
specifiedContracts: '*',
|
||||
contracts: '*' as '*',
|
||||
};
|
||||
|
@ -13,7 +13,7 @@ export const coverage = {
|
||||
return coverageSubprovider;
|
||||
},
|
||||
_getCoverageSubprovider(): CoverageSubprovider {
|
||||
const artifactsPath = '../migrations/src/artifacts';
|
||||
const artifactsPath = '../migrations/artifacts/1.0.0';
|
||||
const contractsPath = 'src/contracts';
|
||||
const networkId = 50;
|
||||
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",
|
||||
"test": "run-s build run_mocha",
|
||||
"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'",
|
||||
"coverage:report:text": "istanbul report text",
|
||||
"coverage:report:html": "istanbul report html && open coverage/index.html",
|
||||
"coverage:report:lcov": "istanbul report lcov",
|
||||
"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": "",
|
||||
"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",
|
||||
"clean": "shx rm -rf lib 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",
|
||||
"docs:stage": "yarn build && node ./scripts/stage_docs.js",
|
||||
"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) => {
|
||||
const artifactsGlob = `${artifactsPath}/**/*.json`;
|
||||
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 sources = artifact.networks[networkId].sources;
|
||||
const contractName = artifact.contract_name;
|
||||
const sources = _.keys(artifact.sources);
|
||||
const contractName = artifact.contractName;
|
||||
// We don't compute coverage for dependencies
|
||||
const sourceCodes = _.map(sources, (source: string) => fs.readFileSync(source).toString());
|
||||
if (_.isUndefined(artifact.networks[networkId])) {
|
||||
throw new Error(`No ${contractName} artifacts found for networkId ${networkId}`);
|
||||
}
|
||||
const sourceCodes = _.map(sources, (source: string) =>
|
||||
fs.readFileSync(path.join(sourcesPath, source)).toString(),
|
||||
);
|
||||
const contractData = {
|
||||
sourceCodes,
|
||||
sources,
|
||||
sourceMap: artifact.networks[networkId].source_map,
|
||||
sourceMapRuntime: artifact.networks[networkId].source_map_runtime,
|
||||
runtimeBytecode: artifact.networks[networkId].runtime_bytecode,
|
||||
bytecode: artifact.networks[networkId].bytecode,
|
||||
bytecode: artifact.compilerOutput.evm.bytecode.object,
|
||||
sourceMap: artifact.compilerOutput.evm.bytecode.sourceMap,
|
||||
runtimeBytecode: artifact.compilerOutput.evm.deployedBytecode.object,
|
||||
sourceMapRuntime: artifact.compilerOutput.evm.deployedBytecode.sourceMap,
|
||||
};
|
||||
return contractData;
|
||||
contractsData.push(contractData);
|
||||
});
|
||||
const contractsData = _.filter(contractsDataIfExists, contractData => !_.isEmpty(contractData)) as ContractData[];
|
||||
return contractsData;
|
||||
};
|
||||
|
@ -29,10 +29,31 @@ import {
|
||||
import { utils } from './utils';
|
||||
|
||||
export class CoverageManager {
|
||||
private _sourcesPath: string;
|
||||
private _traceInfos: TraceInfo[] = [];
|
||||
private _contractsData: ContractData[] = [];
|
||||
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,
|
||||
coveredPcs: number[],
|
||||
pcToSourceRange: { [programCounter: number]: SourceRange },
|
||||
@ -94,11 +115,12 @@ export class CoverageManager {
|
||||
);
|
||||
statementCoverage[modifierStatementId] = isModifierCovered;
|
||||
}
|
||||
const absoluteFileName = path.join(this._sourcesPath, fileName);
|
||||
const partialCoverage = {
|
||||
[contractData.sources[fileIndex]]: {
|
||||
[absoluteFileName]: {
|
||||
...coverageEntriesDescription,
|
||||
l: {}, // It's able to derive it from statement coverage
|
||||
path: fileName,
|
||||
path: absoluteFileName,
|
||||
f: functionCoverage,
|
||||
s: statementCoverage,
|
||||
b: branchCoverage,
|
||||
@ -106,31 +128,13 @@ export class CoverageManager {
|
||||
};
|
||||
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> {
|
||||
const collector = new Collector();
|
||||
for (const traceInfo of this._traceInfos) {
|
||||
if (traceInfo.address !== constants.NEW_CONTRACT) {
|
||||
// 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;
|
||||
if (_.isUndefined(contractData)) {
|
||||
throw new Error(`Transaction to an unknown address: ${traceInfo.address}`);
|
||||
@ -144,7 +148,7 @@ export class CoverageManager {
|
||||
contractData.sources,
|
||||
);
|
||||
for (let fileIndex = 0; fileIndex < contractData.sources.length; fileIndex++) {
|
||||
const singleFileCoverageForTrace = CoverageManager._getSingleFileCoverageForTrace(
|
||||
const singleFileCoverageForTrace = this._getSingleFileCoverageForTrace(
|
||||
contractData,
|
||||
traceInfo.coveredPcs,
|
||||
pcToSourceRange,
|
||||
@ -154,7 +158,8 @@ export class CoverageManager {
|
||||
}
|
||||
} else {
|
||||
// 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 =>
|
||||
bytecode.startsWith(contractDataCandidate.bytecode),
|
||||
) as ContractData;
|
||||
@ -170,7 +175,7 @@ export class CoverageManager {
|
||||
contractData.sources,
|
||||
);
|
||||
for (let fileIndex = 0; fileIndex < contractData.sources.length; fileIndex++) {
|
||||
const singleFileCoverageForTrace = CoverageManager._getSingleFileCoverageForTrace(
|
||||
const singleFileCoverageForTrace = this._getSingleFileCoverageForTrace(
|
||||
contractData,
|
||||
traceInfo.coveredPcs,
|
||||
pcToSourceRange,
|
||||
|
@ -4,6 +4,10 @@ export const utils = {
|
||||
compareLineColumn(lhs: LineColumn, rhs: LineColumn): number {
|
||||
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 {
|
||||
return (
|
||||
utils.compareLineColumn(parentRange.start, childRange.start) <= 0 &&
|
||||
|
@ -3,6 +3,7 @@ export { FallthroughResolver } from './resolvers/fallthrough_resolver';
|
||||
export { URLResolver } from './resolvers/url_resolver';
|
||||
export { NPMResolver } from './resolvers/npm_resolver';
|
||||
export { FSResolver } from './resolvers/fs_resolver';
|
||||
export { RelativeFSResolver } from './resolvers/relative_fs_resolver';
|
||||
export { NameResolver } from './resolvers/name_resolver';
|
||||
export { EnumerableResolver } from './resolvers/enumerable_resolver';
|
||||
export { Resolver } from './resolvers/resolver';
|
||||
|
@ -18,7 +18,8 @@ export class NameResolver extends EnumerableResolver {
|
||||
const onFile = (filePath: string) => {
|
||||
const contractName = path.basename(filePath, SOLIDITY_FILE_EXTENSION);
|
||||
if (contractName === lookupContractName) {
|
||||
const source = fs.readFileSync(filePath).toString();
|
||||
const absoluteContractPath = path.join(this._contractsDir, filePath);
|
||||
const source = fs.readFileSync(absoluteContractPath).toString();
|
||||
contractSource = {
|
||||
source,
|
||||
path: filePath,
|
||||
@ -35,7 +36,8 @@ export class NameResolver extends EnumerableResolver {
|
||||
const contractSources: ContractSource[] = [];
|
||||
const onFile = (filePath: string) => {
|
||||
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 = {
|
||||
source,
|
||||
path: filePath,
|
||||
@ -54,9 +56,10 @@ export class NameResolver extends EnumerableResolver {
|
||||
throw new Error(`No directory found at ${dirPath}`);
|
||||
}
|
||||
for (const fileName of dirContents) {
|
||||
const entryPath = path.join(dirPath, fileName);
|
||||
const isDirectory = fs.lstatSync(entryPath).isDirectory();
|
||||
const isComplete = isDirectory ? this._traverseContractsDir(entryPath, onFile) : onFile(entryPath);
|
||||
const absoluteEntryPath = path.join(dirPath, fileName);
|
||||
const isDirectory = fs.lstatSync(absoluteEntryPath).isDirectory();
|
||||
const entryPath = path.relative(this._contractsDir, absoluteEntryPath);
|
||||
const isComplete = isDirectory ? this._traverseContractsDir(absoluteEntryPath, onFile) : onFile(entryPath);
|
||||
if (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'
|
||||
| 'ewasm.wast'
|
||||
| '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 {
|
||||
language: 'Solidity' | 'serpent' | 'lll' | 'assembly';
|
||||
sources: {
|
||||
[fileName: string]: Source;
|
||||
};
|
||||
settings: {
|
||||
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[];
|
||||
};
|
||||
};
|
||||
};
|
||||
settings: CompilerSettings;
|
||||
}
|
||||
export type ErrorType =
|
||||
| 'JSONError'
|
||||
@ -114,6 +115,19 @@ declare module 'solc' {
|
||||
formattedMessage?: string;
|
||||
}
|
||||
import { ContractAbi } from '@0xproject/types';
|
||||
export interface StandardContractOutput {
|
||||
abi: ContractAbi;
|
||||
evm: {
|
||||
bytecode: {
|
||||
object: string;
|
||||
sourceMap: string;
|
||||
};
|
||||
deployedBytecode: {
|
||||
object: string;
|
||||
sourceMap: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
export interface StandardOutput {
|
||||
errors: Error[];
|
||||
sources: {
|
||||
@ -125,19 +139,7 @@ declare module 'solc' {
|
||||
};
|
||||
contracts: {
|
||||
[fileName: string]: {
|
||||
[contractName: string]: {
|
||||
abi: ContractAbi;
|
||||
evm: {
|
||||
bytecode: {
|
||||
object: string;
|
||||
sourceMap: string;
|
||||
};
|
||||
deployedBytecode: {
|
||||
object: string;
|
||||
sourceMap: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
[contractName: string]: StandardContractOutput;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
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(
|
||||
this.props.dispatcher,
|
||||
);
|
||||
const isDevelopment = configs.ENVIRONMENT === Environments.DEVELOPMENT;
|
||||
const portalStyle: React.CSSProperties = {
|
||||
minHeight: '100vh',
|
||||
display: 'flex',
|
||||
@ -204,18 +203,6 @@ export class LegacyPortal extends React.Component<LegacyPortalProps, LegacyPorta
|
||||
<div className="py2" style={{ backgroundColor: colors.grey50 }}>
|
||||
{this.props.blockchainIsLoaded ? (
|
||||
<Switch>
|
||||
{isDevelopment && (
|
||||
<Route
|
||||
path={`${WebsitePaths.Portal}/wallet`}
|
||||
render={this._renderWallet.bind(this)}
|
||||
/>
|
||||
)}
|
||||
{isDevelopment && (
|
||||
<Route
|
||||
path={`${WebsitePaths.Portal}/relayers`}
|
||||
render={this._renderRelayers.bind(this)}
|
||||
/>
|
||||
)}
|
||||
<Route
|
||||
path={`${WebsitePaths.Portal}/weth`}
|
||||
render={this._renderEthWrapper.bind(this)}
|
||||
@ -294,40 +281,6 @@ export class LegacyPortal extends React.Component<LegacyPortalProps, LegacyPorta
|
||||
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() {
|
||||
return (
|
||||
<EthWrappers
|
||||
|
@ -58,26 +58,6 @@ export class LegacyPortalMenu extends React.Component<LegacyPortalMenuProps, Leg
|
||||
>
|
||||
{this._renderMenuItemWithIcon('Wrap ETH', 'zmdi-circle-o')}
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
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;
|
||||
}
|
||||
|
||||
// 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 = {
|
||||
root: {
|
||||
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) => {
|
||||
const link = props.relayerInfo.appUrl || props.relayerInfo.url;
|
||||
return (
|
||||
<a href={link} target="_blank" style={{ textDecoration: 'none' }}>
|
||||
<GridTile style={styles.root}>
|
||||
<div style={styles.innerDiv}>
|
||||
<img src={props.relayerInfo.headerImgUrl} style={styles.header} />
|
||||
<div style={styles.body}>
|
||||
<div className="py1" style={styles.relayerNameLabel}>
|
||||
{props.relayerInfo.name}
|
||||
</div>
|
||||
<div style={styles.dailyTradeVolumeLabel}>{props.relayerInfo.dailyTxnVolume}</div>
|
||||
<div className="py1" style={styles.subLabel}>
|
||||
Daily Trade Volume
|
||||
</div>
|
||||
<TopTokens tokens={topTokens} networkId={props.networkId} />
|
||||
<div className="py1" style={styles.subLabel}>
|
||||
Top tokens
|
||||
</div>
|
||||
<GridTile style={styles.root}>
|
||||
<div style={styles.innerDiv}>
|
||||
<a href={link} target="_blank" style={{ textDecoration: 'none' }}>
|
||||
<ImgWithFallback
|
||||
src={props.relayerInfo.headerImgUrl}
|
||||
fallbackSrc={FALLBACK_IMG_SRC}
|
||||
style={styles.header}
|
||||
/>
|
||||
</a>
|
||||
<div style={styles.body}>
|
||||
<div className="py1" style={styles.relayerNameLabel}>
|
||||
{props.relayerInfo.name}
|
||||
</div>
|
||||
<div style={styles.dailyTradeVolumeLabel}>{props.relayerInfo.dailyTxnVolume}</div>
|
||||
<div className="py1" style={styles.subLabel}>
|
||||
Daily Trade Volume
|
||||
</div>
|
||||
<TopTokens tokens={props.relayerInfo.topTokens} networkId={props.networkId} />
|
||||
<div className="py1" style={styles.subLabel}>
|
||||
Top tokens
|
||||
</div>
|
||||
</div>
|
||||
</GridTile>
|
||||
</a>
|
||||
</div>
|
||||
</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 * as _ from 'lodash';
|
||||
import CircularProgress from 'material-ui/CircularProgress';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
import { GridList } from 'material-ui/GridList';
|
||||
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 GRID_PADDING = 16;
|
||||
const GRID_PADDING = 20;
|
||||
|
||||
export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerIndexState> {
|
||||
private _isUnmounted: boolean;
|
||||
@ -55,7 +57,24 @@ export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerInde
|
||||
}
|
||||
public render() {
|
||||
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 (
|
||||
<div style={styles.root}>
|
||||
<GridList
|
||||
@ -64,23 +83,22 @@ export class RelayerIndex extends React.Component<RelayerIndexProps, RelayerInde
|
||||
padding={GRID_PADDING}
|
||||
style={styles.gridList}
|
||||
>
|
||||
{this.state.relayerInfos.map((relayerInfo: WebsiteBackendRelayerInfo) => (
|
||||
<RelayerGridTile
|
||||
key={relayerInfo.name}
|
||||
relayerInfo={relayerInfo}
|
||||
networkId={this.props.networkId}
|
||||
/>
|
||||
{this.state.relayerInfos.map((relayerInfo: WebsiteBackendRelayerInfo, index) => (
|
||||
<RelayerGridTile key={index} relayerInfo={relayerInfo} networkId={this.props.networkId} />
|
||||
))}
|
||||
</GridList>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
// TODO: loading and error states with a scrolling container
|
||||
return null;
|
||||
}
|
||||
}
|
||||
private async _fetchRelayerInfosAsync(): Promise<void> {
|
||||
try {
|
||||
if (!this._isUnmounted) {
|
||||
this.setState({
|
||||
relayerInfos: undefined,
|
||||
error: undefined,
|
||||
});
|
||||
}
|
||||
const relayerInfos = await backendClient.getRelayerInfosAsync();
|
||||
if (!this._isUnmounted) {
|
||||
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 { TokenIcon } from 'ts/components/ui/token_icon';
|
||||
import { Token } from 'ts/types';
|
||||
import { WebsiteBackendTokenInfo } from 'ts/types';
|
||||
|
||||
export interface TopTokensProps {
|
||||
tokens: Token[];
|
||||
tokens: WebsiteBackendTokenInfo[];
|
||||
networkId: number;
|
||||
}
|
||||
|
||||
@ -23,17 +23,17 @@ const styles: Styles = {
|
||||
export const TopTokens: React.StatelessComponent<TopTokensProps> = (props: TopTokensProps) => {
|
||||
return (
|
||||
<div className="flex">
|
||||
{_.map(props.tokens, (token: Token, index: number) => {
|
||||
{_.map(props.tokens, (tokenInfo: WebsiteBackendTokenInfo, index: number) => {
|
||||
const firstItemStyle = { ...styles.tokenLabel, ...styles.followingTokenLabel };
|
||||
const style = index !== 0 ? firstItemStyle : styles.tokenLabel;
|
||||
return (
|
||||
<a
|
||||
key={token.address}
|
||||
href={tokenLinkFromToken(token, props.networkId)}
|
||||
key={tokenInfo.address}
|
||||
href={tokenLinkFromToken(tokenInfo, props.networkId)}
|
||||
target="_blank"
|
||||
style={style}
|
||||
>
|
||||
{token.symbol}
|
||||
{tokenInfo.symbol}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
@ -41,6 +41,6 @@ export const TopTokens: React.StatelessComponent<TopTokensProps> = (props: TopTo
|
||||
);
|
||||
};
|
||||
|
||||
function tokenLinkFromToken(token: Token, networkId: number) {
|
||||
return sharedUtils.getEtherScanLinkIfExists(token.address, networkId, EtherscanLinkSuffixes.Address);
|
||||
function tokenLinkFromToken(tokenInfo: WebsiteBackendTokenInfo, networkId: number) {
|
||||
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 RaisedButton from 'material-ui/RaisedButton';
|
||||
import * as React from 'react';
|
||||
@ -11,9 +11,9 @@ import { ProviderType } from 'ts/types';
|
||||
import { constants } from 'ts/utils/constants';
|
||||
import { utils } from 'ts/utils/utils';
|
||||
|
||||
const IDENTICON_DIAMETER = 32;
|
||||
const ROOT_HEIGHT = 24;
|
||||
|
||||
interface ProviderDisplayProps {
|
||||
export interface ProviderDisplayProps {
|
||||
dispatcher: Dispatcher;
|
||||
userAddress: string;
|
||||
networkId: number;
|
||||
@ -25,6 +25,15 @@ interface ProviderDisplayProps {
|
||||
|
||||
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> {
|
||||
public render() {
|
||||
const isAddressAvailable = !_.isEmpty(this.props.userAddress);
|
||||
@ -40,21 +49,20 @@ export class ProviderDisplay extends React.Component<ProviderDisplayProps, Provi
|
||||
: 'Connect a wallet';
|
||||
const providerTitle =
|
||||
this.props.providerType === ProviderType.Injected ? injectedProviderName : 'Ledger Nano S';
|
||||
const isProviderMetamask = providerTitle === constants.PROVIDER_NAME_METAMASK;
|
||||
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>
|
||||
<Identicon address={this.props.userAddress} diameter={IDENTICON_DIAMETER} />
|
||||
<Identicon address={this.props.userAddress} diameter={ROOT_HEIGHT} />
|
||||
</div>
|
||||
<div style={{ marginLeft: 12, paddingTop: 1 }}>
|
||||
<div style={{ fontSize: 12, color: colors.amber800 }}>{providerTitle}</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 style={{ marginLeft: 12, paddingTop: 3 }}>
|
||||
<div style={{ fontSize: 16, color: colors.darkGrey }}>{displayAddress}</div>
|
||||
</div>
|
||||
{isProviderMetamask && (
|
||||
<div style={{ marginLeft: 16 }}>
|
||||
<img src="/images/metamask_icon.png" style={{ width: ROOT_HEIGHT, height: ROOT_HEIGHT }} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
const hasInjectedProvider =
|
||||
|
@ -19,7 +19,12 @@ import { Deco, Key, ProviderType, WebsiteLegacyPaths, WebsitePaths } from 'ts/ty
|
||||
import { constants } from 'ts/utils/constants';
|
||||
import { Translate } from 'ts/utils/translate';
|
||||
|
||||
interface TopBarProps {
|
||||
export enum TopBarDisplayType {
|
||||
Default,
|
||||
Expanded,
|
||||
}
|
||||
|
||||
export interface TopBarProps {
|
||||
userAddress?: string;
|
||||
networkId?: number;
|
||||
injectedProviderName?: string;
|
||||
@ -34,7 +39,7 @@ interface TopBarProps {
|
||||
availableDocVersions?: string[];
|
||||
menu?: DocsMenu;
|
||||
menuSubsectionsBySection?: MenuSubsectionsBySection;
|
||||
shouldFullWidth?: boolean;
|
||||
displayType?: TopBarDisplayType;
|
||||
docsInfo?: DocsInfo;
|
||||
style?: React.CSSProperties;
|
||||
isNightVersion?: boolean;
|
||||
@ -47,17 +52,8 @@ interface TopBarState {
|
||||
}
|
||||
|
||||
const styles: Styles = {
|
||||
address: {
|
||||
marginRight: 12,
|
||||
overflow: 'hidden',
|
||||
paddingTop: 4,
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
width: 70,
|
||||
},
|
||||
topBar: {
|
||||
backgroundColor: colors.white,
|
||||
height: 59,
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
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> {
|
||||
public static defaultProps: Partial<TopBarProps> = {
|
||||
shouldFullWidth: false,
|
||||
displayType: TopBarDisplayType.Default,
|
||||
style: {},
|
||||
isNightVersion: false,
|
||||
};
|
||||
public static heightForDisplayType(displayType: TopBarDisplayType) {
|
||||
const result = displayType === TopBarDisplayType.Expanded ? EXPANDED_HEIGHT : DEFAULT_HEIGHT;
|
||||
return result + 1;
|
||||
}
|
||||
constructor(props: TopBarProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@ -92,8 +95,9 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
|
||||
}
|
||||
public render() {
|
||||
const isNightVersion = this.props.isNightVersion;
|
||||
const isFullWidthPage = this.props.shouldFullWidth;
|
||||
const parentClassNames = `flex mx-auto ${isFullWidthPage ? 'pl2' : 'max-width-4'}`;
|
||||
const isExpandedDisplayType = this.props.displayType === TopBarDisplayType.Expanded;
|
||||
const parentClassNames = `flex mx-auto ${isExpandedDisplayType ? 'pl3 py1' : 'max-width-4'}`;
|
||||
const height = isExpandedDisplayType ? EXPANDED_HEIGHT : DEFAULT_HEIGHT;
|
||||
const developerSectionMenuItems = [
|
||||
<Link key="subMenuItem-zeroEx" to={WebsitePaths.ZeroExJs} className="text-decoration-none">
|
||||
<MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="0x.js" />
|
||||
@ -178,9 +182,11 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
|
||||
</a>,
|
||||
];
|
||||
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 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 = {
|
||||
fontSize: 25,
|
||||
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>;
|
||||
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="col col-2 sm-pl1 md-pl2 lg-pl0" style={{ paddingTop: 15 }}>
|
||||
<Link to={`${WebsitePaths.Home}`} className="text-decoration-none">
|
||||
<img src={logoUrl} height="30" />
|
||||
</Link>
|
||||
</div>
|
||||
<div className={`col col-${isFullWidthPage ? '8' : '9'} lg-hide md-hide`} />
|
||||
<div className={`col col-${isFullWidthPage ? '6' : '5'} sm-hide xs-hide`} />
|
||||
<div className={`col col-${isExpandedDisplayType ? '8' : '9'} lg-hide md-hide`} />
|
||||
<div className={`col col-${isExpandedDisplayType ? '6' : '5'} sm-hide xs-hide`} />
|
||||
{!this._isViewingPortal() && (
|
||||
<div className={menuClasses}>
|
||||
<div className="flex justify-between">
|
||||
@ -242,7 +248,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
|
||||
path={`${WebsitePaths.Portal}`}
|
||||
isPrimary={true}
|
||||
style={styles.menuItem}
|
||||
className={`${isFullWidthPage && 'md-hide'}`}
|
||||
className={`${isExpandedDisplayType && 'md-hide'}`}
|
||||
isNightVersion={isNightVersion}
|
||||
isExternal={false}
|
||||
/>
|
||||
@ -250,7 +256,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
|
||||
</div>
|
||||
)}
|
||||
{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
|
||||
dispatcher={this.props.dispatcher}
|
||||
userAddress={this.props.userAddress}
|
||||
@ -262,7 +268,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
|
||||
/>
|
||||
</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}>
|
||||
<i className="zmdi zmdi-menu" onClick={this._onMenuButtonClick.bind(this)} />
|
||||
</div>
|
||||
@ -441,21 +447,6 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
|
||||
</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() {
|
||||
this.setState({
|
||||
isDrawerOpen: !this.state.isDrawerOpen,
|
||||
@ -511,7 +502,8 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> {
|
||||
this._isViewingJsonSchemasDocs() ||
|
||||
this._isViewingSolCovDocs() ||
|
||||
this._isViewingSubprovidersDocs() ||
|
||||
this._isViewingConnectDocs()
|
||||
this._isViewingConnectDocs() ||
|
||||
this._isViewingPortal()
|
||||
);
|
||||
}
|
||||
} // tslint:disable:max-file-line-count
|
||||
|
@ -55,11 +55,13 @@ export interface WalletProps {
|
||||
injectedProviderName: string;
|
||||
providerType: ProviderType;
|
||||
onToggleLedgerDialog: () => void;
|
||||
onAddToken: () => void;
|
||||
}
|
||||
|
||||
interface WalletState {
|
||||
trackedTokenStateByAddress: TokenStateByAddress;
|
||||
wrappedEtherDirection?: Side;
|
||||
isHoveringSidebar: boolean;
|
||||
}
|
||||
|
||||
interface AllowanceToggleConfig {
|
||||
@ -94,6 +96,9 @@ const styles: Styles = {
|
||||
},
|
||||
footerItemInnerDiv: {
|
||||
paddingLeft: 24,
|
||||
borderTopColor: colors.walletBorder,
|
||||
borderTopStyle: 'solid',
|
||||
borderWidth: 1,
|
||||
},
|
||||
borderedItem: {
|
||||
borderBottomColor: colors.walletBorder,
|
||||
@ -114,7 +119,17 @@ const styles: Styles = {
|
||||
paddingTop: 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';
|
||||
@ -139,6 +154,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
|
||||
this.state = {
|
||||
trackedTokenStateByAddress: initialTrackedTokenStateByAddress,
|
||||
wrappedEtherDirection: undefined,
|
||||
isHoveringSidebar: false,
|
||||
};
|
||||
}
|
||||
public componentWillMount() {
|
||||
@ -184,12 +200,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
|
||||
return (
|
||||
<List style={styles.list}>
|
||||
{isAddressAvailable
|
||||
? _.concat(
|
||||
this._renderConnectedHeaderRows(),
|
||||
this._renderEthRows(),
|
||||
this._renderTokenRows(),
|
||||
this._renderFooterRows(),
|
||||
)
|
||||
? _.concat(this._renderConnectedHeaderRows(), this._renderBody(), this._renderFooterRows())
|
||||
: _.concat(this._renderDisconnectedHeaderRows(), this._renderDisconnectedRows())}
|
||||
</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() {
|
||||
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() {
|
||||
const primaryText = this._renderAmount(
|
||||
@ -293,7 +338,7 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
|
||||
);
|
||||
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 tokenLink = sharedUtils.getEtherScanLinkIfExists(
|
||||
token.address,
|
||||
@ -310,12 +355,14 @@ export class Wallet extends React.Component<WalletProps, WalletState> {
|
||||
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 =
|
||||
!_.isUndefined(this.state.wrappedEtherDirection) &&
|
||||
this.state.wrappedEtherDirection === accessoryItemConfig.wrappedEtherDirection;
|
||||
const style = shouldShowWrapEtherItem
|
||||
? { ...walletItemStyles.focusedItem, ...styles.paddedItem }
|
||||
: { ...styles.tokenItem, ...styles.borderedItem, ...styles.paddedItem };
|
||||
: { ...styles.tokenItem, ...borderedStyle, ...styles.paddedItem };
|
||||
const etherToken = this._getEthToken();
|
||||
return (
|
||||
<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.
|
||||
// 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.
|
||||
const LazyPortal = createLazyComponent('LegacyPortal', async () =>
|
||||
System.import<any>(/* webpackChunkName: "legacyPortal" */ 'ts/containers/legacy_portal'),
|
||||
);
|
||||
const LazyPortal =
|
||||
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 () =>
|
||||
System.import<any>(/* webpackChunkName: "zeroExDocs" */ 'ts/containers/zero_ex_js_documentation'),
|
||||
);
|
||||
|
@ -503,19 +503,26 @@ export interface TokenState {
|
||||
price?: BigNumber;
|
||||
}
|
||||
|
||||
// TODO: Add topTokens property once available from backend
|
||||
export interface WebsiteBackendRelayerInfo {
|
||||
name: string;
|
||||
dailyTxnVolume: string;
|
||||
url: string;
|
||||
appUrl?: string;
|
||||
headerImgUrl: string;
|
||||
headerImgUrl?: string;
|
||||
topTokens: WebsiteBackendTokenInfo[];
|
||||
}
|
||||
|
||||
export interface WebsiteBackendPriceInfo {
|
||||
[symbol: string]: string;
|
||||
}
|
||||
|
||||
export interface WebsiteBackendTokenInfo {
|
||||
address: string;
|
||||
decimals: number;
|
||||
name: string;
|
||||
symbol: string;
|
||||
}
|
||||
|
||||
export interface WebsiteBackendGasInfo {
|
||||
average: number;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user