Merge branch 'development' into development

This commit is contained in:
Andrew Maurer 2018-10-13 11:09:50 -04:00 committed by GitHub
commit 69e9dbd683
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
139 changed files with 3905 additions and 1403 deletions

View File

@ -23,6 +23,7 @@ jobs:
paths:
- ~/repo
build-website:
resource_class: medium+
docker:
- image: circleci/node:9
working_directory: ~/repo
@ -161,6 +162,90 @@ jobs:
key: coverage-web3-wrapper-{{ .Environment.CIRCLE_SHA1 }}
paths:
- ~/repo/packages/web3-wrapper/coverage/lcov.info
test-python:
working_directory: ~/repo
docker:
- image: circleci/python
steps:
- checkout
- run: sudo chown -R circleci:circleci /usr/local/bin
- run: sudo chown -R circleci:circleci /usr/local/lib/python3.7/site-packages
- restore_cache:
key: deps9-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
- run:
command: |
cd python-packages/order_utils
python -m ensurepip
python -m pip install -e .[dev]
- save_cache:
key: deps9-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
paths:
- "/usr/local/bin"
- "/usr/local/lib/python3.7/site-packages"
- ".eggs"
- ".mypy_cache"
- ".pytest_cache"
- ".tox"
- run:
command: |
cd python-packages/order_utils
coverage run setup.py test
- save_cache:
key: coverage-python-order-utils-{{ .Environment.CIRCLE_SHA1 }}
paths:
- ~/repo/python-packages/order_utils/.coverage
test-rest-python:
working_directory: ~/repo
docker:
- image: circleci/python
steps:
- checkout
- run: sudo chown -R circleci:circleci /usr/local/bin
- run: sudo chown -R circleci:circleci /usr/local/lib/python3.7/site-packages
- restore_cache:
key: deps9-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
- run:
command: |
cd python-packages/order_utils
python -m ensurepip
python -m pip install -e .[dev]
- save_cache:
key: deps9-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
paths:
- "/usr/local/bin"
- "/usr/local/lib/python3.7/site-packages"
- ".eggs"
- ".mypy_cache"
- ".pytest_cache"
- ".tox"
- run:
command: |
cd python-packages/order_utils
tox
static-tests-python:
working_directory: ~/repo
docker:
- image: circleci/python
steps:
- checkout
- run: sudo chown -R circleci:circleci /usr/local/bin
- run: sudo chown -R circleci:circleci /usr/local/lib/python3.7/site-packages
- restore_cache:
key: deps9-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
- run:
command: |
cd python-packages/order_utils
python -m ensurepip
python -m pip install -e .[dev]
- save_cache:
key: deps9-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }}
paths:
- "/usr/local/bin"
- "/usr/local/lib/python3.7/site-packages"
- run:
command: |
cd python-packages/order_utils
python setup.py lint
static-tests:
working_directory: ~/repo
docker:
@ -172,7 +257,7 @@ jobs:
- run: yarn lerna run lint
- run: yarn prettier:ci
- run: cd packages/0x.js && yarn build:umd:prod
- run: yarn bundlesize
- run: yarn bundlewatch
submit-coverage:
docker:
- image: circleci/node:9
@ -232,6 +317,9 @@ jobs:
- restore_cache:
keys:
- coverage-contracts-{{ .Environment.CIRCLE_SHA1 }}
- restore_cache:
keys:
- coverage-python-order-utils-{{ .Environment.CIRCLE_SHA1 }}
- run: yarn report_coverage
workflows:
version: 2
@ -262,3 +350,8 @@ workflows:
- submit-coverage:
requires:
- test-rest
- test-python
- test-python
- static-tests-python
# skip python tox run for now, as we don't yet have multiple test environments to support.
#- test-rest-python

10
.gitignore vendored
View File

@ -72,6 +72,7 @@ TODO.md
.vscode
packages/website/public/bundle*
packages/dev-tools-pages/public/bundle*
packages/react-docs/example/public/bundle*
# server cli
@ -108,3 +109,12 @@ packages/sol-compiler/solc_bin/
# Monorepo scripts
packages/*/scripts/
# python stuff
.eggs
.mypy_cache
.tox
python-packages/*/build
__pycache__
python-packages/*/src/*.egg-info
python-packages/*/.coverage

View File

@ -11,7 +11,7 @@
"ganache": "ganache-cli -p 8545 --networkId 50 -m \"${npm_package_config_mnemonic}\"",
"prettier": "prettier --write '**/*.{ts,tsx,json,md}' --config .prettierrc",
"prettier:ci": "prettier --list-different '**/*.{ts,tsx,json,md}' --config .prettierrc",
"report_coverage": "lcov-result-merger 'packages/*/coverage/lcov.info' | coveralls",
"report_coverage": "lcov-result-merger './{packages/*/coverage/lcov.info,python-packages/*/.coverage}' | coveralls",
"test:installation": "node ./packages/monorepo-scripts/lib/test_installation.js",
"test:installation:local": "IS_LOCAL_PUBLISH=true node ./packages/monorepo-scripts/lib/test_installation.js",
"test:publish:circleci:comment": "HACK(albrow) We need an automated way to login to npm and echo+sleep piped to stdin was the only way I could find to do it.",
@ -36,27 +36,35 @@
"test": "wsrun test $PKG --fast-exit --serial --exclude-missing",
"generate_doc": "node ./packages/monorepo-scripts/lib/doc_generate_and_upload.js",
"test:generate_docs:circleci": "for i in ${npm_package_config_packagesWithDocPages}; do yarn generate_doc --package $i --shouldUpload false --isStaging true || break -1; done;",
"bundlesize": "bundlesize",
"bundlewatch": "bundlewatch",
"lint": "wsrun lint $PKG --fast-exit --parallel --exclude-missing"
},
"config": {
"mnemonic": "concert load couple harbor equip island argue ramp clarify fence smart topic",
"packagesWithDocPages": "0x.js connect json-schemas subproviders web3-wrapper contract-wrappers order-utils order-watcher sol-compiler sol-cov ethereum-types"
},
"bundlesize": [
"bundlewatch" : {
"files": [
{
"path": "packages/0x.js/_bundles/index.min.js"
"path": "packages/0x.js/_bundles/index.min.js",
"maxSize": "700kB"
},
{
"path": "packages/instant/public/main.bundle.js"
"path": "packages/instant/public/main.bundle.js",
"maxSize": "350kB"
}
],
"ci": {
"trackBranches": ["master", "development"],
"repoBranchBase": "development"
}
},
"devDependencies": {
"@0x-lerna-fork/lerna": "3.0.0-beta.25",
"async-child-process": "^1.1.1",
"bundlesize": "^0.17.0",
"bundlewatch": "^0.2.1",
"coveralls": "^3.0.0",
"ganache-cli": "6.1.3",
"ganache-cli": "6.1.8",
"lcov-result-merger": "^3.0.0",
"npm-cli-login": "^0.0.10",
"npm-run-all": "^4.1.2",

View File

@ -1,4 +1,23 @@
[
{
"version": "2.0.0",
"changes": [
{
"note": "Add support for `eth_signTypedData`.",
"pr": 1102
},
{
"note":
"Added `MetamaskSubprovider` to handle inconsistencies in Metamask's signing JSON RPC endpoints.",
"pr": 1102
},
{
"note":
"Removed `SignerType` (including `SignerType.Metamask`). Please use the `MetamaskSubprovider` to wrap `web3.currentProvider`.",
"pr": 1102
}
]
},
{
"version": "1.0.8",
"changes": [

View File

@ -84,7 +84,7 @@
"@0xproject/utils": "^2.0.2",
"@0xproject/web3-wrapper": "^3.0.3",
"ethereum-types": "^1.0.11",
"ethers": "4.0.0-beta.14",
"ethers": "~4.0.4",
"lodash": "^4.17.5",
"web3-provider-engine": "14.0.6"
},

View File

@ -53,7 +53,13 @@ export { OrderWatcher, OnOrderStateChangeCallback, OrderWatcherConfig } from '@0
export import Web3ProviderEngine = require('web3-provider-engine');
export { RPCSubprovider, Callback, JSONRPCRequestPayloadWithMethod, ErrorCallback } from '@0xproject/subproviders';
export {
RPCSubprovider,
Callback,
JSONRPCRequestPayloadWithMethod,
ErrorCallback,
MetamaskSubprovider,
} from '@0xproject/subproviders';
export { AbiDecoder } from '@0xproject/utils';
@ -68,7 +74,6 @@ export {
OrderStateInvalid,
OrderState,
AssetProxyId,
SignerType,
ERC20AssetData,
ERC721AssetData,
SignatureType,
@ -85,6 +90,7 @@ export {
JSONRPCRequestPayload,
JSONRPCResponsePayload,
JSONRPCErrorCallback,
JSONRPCResponseError,
LogEntry,
DecodedLogArgs,
LogEntryEvent,

View File

@ -1,4 +1,12 @@
[
{
"version": "2.1.0",
"changes": [
{
"note": "Add `gasLimit` and `gasPrice` as optional properties on `BuyQuoteExecutionOpts`"
}
]
},
{
"version": "2.0.0",
"changes": [

View File

@ -183,7 +183,7 @@ export class AssetBuyer {
buyQuote: BuyQuote,
options: Partial<BuyQuoteExecutionOpts> = {},
): Promise<string> {
const { ethAmount, takerAddress, feeRecipient } = {
const { ethAmount, takerAddress, feeRecipient, gasLimit, gasPrice } = {
...constants.DEFAULT_BUY_QUOTE_EXECUTION_OPTS,
...options,
};
@ -219,6 +219,10 @@ export class AssetBuyer {
feeOrders,
feePercentage,
feeRecipient,
{
gasLimit,
gasPrice,
},
);
return txHash;
}

View File

@ -8,7 +8,7 @@ const MAINNET_NETWORK_ID = 1;
const DEFAULT_ASSET_BUYER_OPTS: AssetBuyerOpts = {
networkId: MAINNET_NETWORK_ID,
orderRefreshIntervalMs: 10000, // 10 seconds
expiryBufferSeconds: 15,
expiryBufferSeconds: 300, // 5 minutes
};
const DEFAULT_BUY_QUOTE_REQUEST_OPTS: BuyQuoteRequestOpts = {

View File

@ -77,18 +77,22 @@ export interface BuyQuoteRequestOpts {
/**
* ethAmount: The desired amount of eth to spend. Defaults to buyQuote.worstCaseQuoteInfo.totalEthAmount.
* takerAddress: The address to perform the buy. Defaults to the first available address from the provider.
* gasLimit: The amount of gas to send with a transaction (in Gwei). Defaults to an eth_estimateGas rpc call.
* gasPrice: Gas price in Wei to use for a transaction
* feeRecipient: The address where affiliate fees are sent. Defaults to null address (0x000...000).
*/
export interface BuyQuoteExecutionOpts {
ethAmount?: BigNumber;
takerAddress?: string;
gasLimit?: number;
gasPrice?: BigNumber;
feeRecipient: string;
}
/**
* networkId: The ethereum network id. Defaults to 1 (mainnet).
* orderRefreshIntervalMs: The interval in ms that getBuyQuoteAsync should trigger an refresh of orders and order states. Defaults to 10000ms (10s).
* expiryBufferSeconds: The number of seconds to add when calculating whether an order is expired or not. Defaults to 15s.
* expiryBufferSeconds: The number of seconds to add when calculating whether an order is expired or not. Defaults to 300s (5m).
*/
export interface AssetBuyerOpts {
networkId: number;

View File

@ -72,7 +72,6 @@ export const buyQuoteCalculator = {
assetBuyAmount,
feePercentage,
);
return {
assetData,
orders: resultOrders,
@ -98,13 +97,14 @@ function calculateQuoteInfo(
);
// find the total eth needed to buy fees
const ethAmountToBuyFees = findEthAmountNeededToBuyFees(feeOrdersAndFillableAmounts, zrxAmountToBuyAsset);
const ethAmountBeforeAffiliateFee = ethAmountToBuyAsset.plus(ethAmountToBuyFees);
const totalEthAmount = ethAmountBeforeAffiliateFee.mul(feePercentage + 1);
const affiliateFeeEthAmount = ethAmountToBuyAsset.mul(feePercentage);
const totalEthAmountWithoutAffiliateFee = ethAmountToBuyAsset.plus(ethAmountToBuyFees);
const totalEthAmount = totalEthAmountWithoutAffiliateFee.plus(affiliateFeeEthAmount);
// divide into the assetBuyAmount in order to find rate of makerAsset / WETH
const ethPerAssetPrice = ethAmountBeforeAffiliateFee.div(assetBuyAmount);
const ethPerAssetPrice = totalEthAmountWithoutAffiliateFee.div(assetBuyAmount);
return {
totalEthAmount,
feeEthAmount: totalEthAmount.minus(ethAmountBeforeAffiliateFee),
feeEthAmount: affiliateFeeEthAmount,
ethPerAssetPrice,
};
}

View File

@ -10,7 +10,7 @@ export const orderUtils = {
willOrderExpire(order: SignedOrder, secondsFromNow: number): boolean {
const millisecondsInSecond = 1000;
const currentUnixTimestampSec = new BigNumber(Date.now() / millisecondsInSecond).round();
return order.expirationTimeSeconds.lessThan(currentUnixTimestampSec.minus(secondsFromNow));
return order.expirationTimeSeconds.lessThan(currentUnixTimestampSec.plus(secondsFromNow));
},
calculateRemainingMakerAssetAmount(order: SignedOrder, remainingTakerAssetAmount: BigNumber): BigNumber {
if (remainingTakerAssetAmount.eq(0)) {

View File

@ -103,9 +103,11 @@ describe('buyQuoteCalculator', () => {
expect(buyQuote.feeOrders).to.deep.equal([smallFeeOrderAndFillableAmount.orders[0]]);
// test if rates are correct
// 50 eth to fill the first order + 100 eth for fees
const expectedFillEthAmount = new BigNumber(150);
const expectedTotalEthAmount = expectedFillEthAmount.mul(feePercentage + 1);
const expectedFeeEthAmount = expectedTotalEthAmount.minus(expectedFillEthAmount);
const expectedEthAmountForAsset = new BigNumber(50);
const expectedEthAmountForZrxFees = new BigNumber(100);
const expectedFillEthAmount = expectedEthAmountForAsset.plus(expectedEthAmountForZrxFees);
const expectedFeeEthAmount = expectedEthAmountForAsset.mul(feePercentage);
const expectedTotalEthAmount = expectedFillEthAmount.plus(expectedFeeEthAmount);
const expectedEthPerAssetPrice = expectedFillEthAmount.div(assetBuyAmount);
expect(buyQuote.bestCaseQuoteInfo.feeEthAmount).to.bignumber.equal(expectedFeeEthAmount);
expect(buyQuote.bestCaseQuoteInfo.totalEthAmount).to.bignumber.equal(expectedTotalEthAmount);
@ -138,17 +140,21 @@ describe('buyQuoteCalculator', () => {
expect(buyQuote.feeOrders).to.deep.equal(allFeeOrdersAndFillableAmounts.orders);
// test if rates are correct
// 50 eth to fill the first order + 100 eth for fees
const expectedFillEthAmount = new BigNumber(150);
const expectedTotalEthAmount = expectedFillEthAmount.mul(feePercentage + 1);
const expectedFeeEthAmount = expectedTotalEthAmount.minus(expectedFillEthAmount);
const expectedEthAmountForAsset = new BigNumber(50);
const expectedEthAmountForZrxFees = new BigNumber(100);
const expectedFillEthAmount = expectedEthAmountForAsset.plus(expectedEthAmountForZrxFees);
const expectedFeeEthAmount = expectedEthAmountForAsset.mul(feePercentage);
const expectedTotalEthAmount = expectedFillEthAmount.plus(expectedFeeEthAmount);
const expectedEthPerAssetPrice = expectedFillEthAmount.div(assetBuyAmount);
expect(buyQuote.bestCaseQuoteInfo.feeEthAmount).to.bignumber.equal(expectedFeeEthAmount);
expect(buyQuote.bestCaseQuoteInfo.totalEthAmount).to.bignumber.equal(expectedTotalEthAmount);
expect(buyQuote.bestCaseQuoteInfo.ethPerAssetPrice).to.bignumber.equal(expectedEthPerAssetPrice);
// 100 eth to fill the first order + 200 eth for fees
const expectedWorstFillEthAmount = new BigNumber(300);
const expectedWorstTotalEthAmount = expectedWorstFillEthAmount.mul(feePercentage + 1);
const expectedWorstFeeEthAmount = expectedWorstTotalEthAmount.minus(expectedWorstFillEthAmount);
const expectedWorstEthAmountForAsset = new BigNumber(100);
const expectedWorstEthAmountForZrxFees = new BigNumber(200);
const expectedWorstFillEthAmount = expectedWorstEthAmountForAsset.plus(expectedWorstEthAmountForZrxFees);
const expectedWorstFeeEthAmount = expectedWorstEthAmountForAsset.mul(feePercentage);
const expectedWorstTotalEthAmount = expectedWorstFillEthAmount.plus(expectedWorstFeeEthAmount);
const expectedWorstEthPerAssetPrice = expectedWorstFillEthAmount.div(assetBuyAmount);
expect(buyQuote.worstCaseQuoteInfo.feeEthAmount).to.bignumber.equal(expectedWorstFeeEthAmount);
expect(buyQuote.worstCaseQuoteInfo.totalEthAmount).to.bignumber.equal(expectedWorstTotalEthAmount);

View File

@ -45,7 +45,7 @@
"@0xproject/utils": "^2.0.2",
"@0xproject/web3-wrapper": "^3.0.3",
"ethereum-types": "^1.0.11",
"ethers": "4.0.0-beta.14",
"ethers": "~4.0.4",
"lodash": "^4.17.5"
},
"publishConfig": {

View File

@ -17,7 +17,7 @@ import * as _ from 'lodash';
import { formatABIDataItem } from './utils';
export interface EthersInterfaceByFunctionSignature {
[key: string]: ethers.Interface;
[key: string]: ethers.utils.Interface;
}
const REVERT_ERROR_SELECTOR = '08c379a0';
@ -101,7 +101,7 @@ export class BaseContract {
// if it overflows the corresponding Solidity type, there is a bug in the
// encoder, or the encoder performs unsafe type coercion.
public static strictArgumentEncodingCheck(inputAbi: DataItem[], args: any[]): void {
const coder = new ethers.AbiCoder();
const coder = new ethers.utils.AbiCoder();
const params = abiUtils.parseEthersParams(inputAbi);
const rawEncoded = coder.encode(inputAbi, args);
const rawDecoded = coder.decode(inputAbi, rawEncoded);
@ -117,7 +117,7 @@ export class BaseContract {
}
}
}
protected _lookupEthersInterface(functionSignature: string): ethers.Interface {
protected _lookupEthersInterface(functionSignature: string): ethers.utils.Interface {
const ethersInterface = this._ethersInterfacesByFunctionSignature[functionSignature];
if (_.isUndefined(ethersInterface)) {
throw new Error(`Failed to lookup method with function signature '${functionSignature}'`);
@ -154,7 +154,7 @@ export class BaseContract {
this._ethersInterfacesByFunctionSignature = {};
_.each(methodAbis, methodAbi => {
const functionSignature = abiUtils.getFunctionSignature(methodAbi);
this._ethersInterfacesByFunctionSignature[functionSignature] = new ethers.Interface([methodAbi]);
this._ethersInterfacesByFunctionSignature[functionSignature] = new ethers.utils.Interface([methodAbi]);
});
}
}

View File

@ -84,7 +84,7 @@
"ethereum-types": "^1.0.11",
"ethereumjs-blockstream": "6.0.0",
"ethereumjs-util": "^5.1.1",
"ethers": "4.0.0-beta.14",
"ethers": "~4.0.4",
"js-sha3": "^0.7.0",
"lodash": "^4.17.5",
"uuid": "^3.1.0"

View File

@ -39,6 +39,7 @@ export {
JSONRPCRequestPayload,
JSONRPCResponsePayload,
JSONRPCErrorCallback,
JSONRPCResponseError,
AbiDefinition,
LogWithDecodedArgs,
FunctionAbi,

View File

@ -1,22 +1,13 @@
import { schemas } from '@0xproject/json-schemas';
import { EIP712Schema, EIP712Types, eip712Utils } from '@0xproject/order-utils';
import { eip712Utils } from '@0xproject/order-utils';
import { Order, SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import { BigNumber, signTypedDataUtils } from '@0xproject/utils';
import _ = require('lodash');
import { ExchangeContract } from '../contract_wrappers/generated/exchange';
import { assert } from './assert';
const EIP712_ZEROEX_TRANSACTION_SCHEMA: EIP712Schema = {
name: 'ZeroExTransaction',
parameters: [
{ name: 'salt', type: EIP712Types.Uint256 },
{ name: 'signerAddress', type: EIP712Types.Address },
{ name: 'data', type: EIP712Types.Bytes },
],
};
/**
* Transaction Encoder. Transaction messages exist for the purpose of calling methods on the Exchange contract
* in the context of another address. For example, UserA can encode and sign a fillOrder transaction and UserB
@ -41,12 +32,9 @@ export class TransactionEncoder {
signerAddress,
data,
};
const executeTransactionHashBuff = eip712Utils.structHash(
EIP712_ZEROEX_TRANSACTION_SCHEMA,
executeTransactionData,
);
const eip721MessageBuffer = eip712Utils.createEIP712Message(executeTransactionHashBuff, exchangeAddress);
const messageHex = `0x${eip721MessageBuffer.toString('hex')}`;
const typedData = eip712Utils.createZeroExTransactionTypedData(executeTransactionData, exchangeAddress);
const eip712MessageBuffer = signTypedDataUtils.generateTypedDataHash(typedData);
const messageHex = `0x${eip712MessageBuffer.toString('hex')}`;
return messageHex;
}
/**

View File

@ -1,7 +1,7 @@
import { BlockchainLifecycle } from '@0xproject/dev-utils';
import { FillScenarios } from '@0xproject/fill-scenarios';
import { assetDataUtils, generatePseudoRandomSalt, orderHashUtils, signatureUtils } from '@0xproject/order-utils';
import { SignedOrder, SignerType } from '@0xproject/types';
import { SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import 'mocha';
@ -80,12 +80,7 @@ describe('TransactionEncoder', () => {
): Promise<void> => {
const salt = generatePseudoRandomSalt();
const encodedTransaction = encoder.getTransactionHex(data, salt, signerAddress);
const signature = await signatureUtils.ecSignOrderHashAsync(
provider,
encodedTransaction,
signerAddress,
SignerType.Default,
);
const signature = await signatureUtils.ecSignHashAsync(provider, encodedTransaction, signerAddress);
txHash = await contractWrappers.exchange.executeTransactionAsync(
salt,
signerAddress,

View File

@ -65,7 +65,7 @@ export class {{contractName}}Contract extends BaseContract {
[{{> params inputs=ctor.inputs}}],
BaseContract._bigNumberToString,
);
const iface = new ethers.Interface(abi);
const iface = new ethers.utils.Interface(abi);
const deployInfo = iface.deployFunction;
const txData = deployInfo.encode(bytecode, [{{> params inputs=ctor.inputs}}]);
const web3Wrapper = new Web3Wrapper(provider);

View File

@ -84,7 +84,7 @@
"ethereum-types": "^1.0.11",
"ethereumjs-abi": "0.6.5",
"ethereumjs-util": "^5.1.1",
"ethers": "4.0.0-beta.14",
"ethers": "~4.0.4",
"js-combinatorics": "^0.5.3",
"lodash": "^4.17.5"
}

View File

@ -1,5 +1,5 @@
import { BlockchainLifecycle } from '@0xproject/dev-utils';
import { assetDataUtils, eip712Utils, orderHashUtils } from '@0xproject/order-utils';
import { assetDataUtils, orderHashUtils } from '@0xproject/order-utils';
import { SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
@ -126,22 +126,6 @@ describe('Exchange libs', () => {
});
describe('LibOrder', () => {
describe('getOrderSchema', () => {
it('should output the correct order schema hash', async () => {
const orderSchema = await libs.getOrderSchemaHash.callAsync();
const schemaHashBuffer = orderHashUtils._getOrderSchemaBuffer();
const schemaHashHex = `0x${schemaHashBuffer.toString('hex')}`;
expect(schemaHashHex).to.be.equal(orderSchema);
});
});
describe('getDomainSeparatorSchema', () => {
it('should output the correct domain separator schema hash', async () => {
const domainSeparatorSchema = await libs.getDomainSeparatorSchemaHash.callAsync();
const domainSchemaBuffer = eip712Utils._getDomainSeparatorSchemaBuffer();
const schemaHashHex = `0x${domainSchemaBuffer.toString('hex')}`;
expect(schemaHashHex).to.be.equal(domainSeparatorSchema);
});
});
describe('getOrderHash', () => {
it('should output the correct orderHash', async () => {
signedOrder = await orderFactory.newSignedOrderAsync();

View File

@ -1,6 +1,6 @@
import { BlockchainLifecycle } from '@0xproject/dev-utils';
import { assetDataUtils, orderHashUtils, signatureUtils } from '@0xproject/order-utils';
import { RevertReason, SignatureType, SignedOrder, SignerType } from '@0xproject/types';
import { RevertReason, SignatureType, SignedOrder } from '@0xproject/types';
import * as chai from 'chai';
import { LogWithDecodedArgs } from 'ethereum-types';
import ethUtil = require('ethereumjs-util');
@ -231,10 +231,7 @@ describe('MixinSignatureValidator', () => {
it('should return true when SignatureType=EthSign and signature is valid', async () => {
// Create EthSign signature
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
const orderHashWithEthSignPrefixHex = signatureUtils.addSignedMessagePrefix(
orderHashHex,
SignerType.Default,
);
const orderHashWithEthSignPrefixHex = signatureUtils.addSignedMessagePrefix(orderHashHex);
const orderHashWithEthSignPrefixBuffer = ethUtil.toBuffer(orderHashWithEthSignPrefixHex);
const ecSignature = ethUtil.ecsign(orderHashWithEthSignPrefixBuffer, signerPrivateKey);
// Create 0x signature from EthSign signature
@ -257,10 +254,7 @@ describe('MixinSignatureValidator', () => {
it('should return false when SignatureType=EthSign and signature is invalid', async () => {
// Create EthSign signature
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
const orderHashWithEthSignPrefixHex = signatureUtils.addSignedMessagePrefix(
orderHashHex,
SignerType.Default,
);
const orderHashWithEthSignPrefixHex = signatureUtils.addSignedMessagePrefix(orderHashHex);
const orderHashWithEthSignPrefixBuffer = ethUtil.toBuffer(orderHashWithEthSignPrefixHex);
const ecSignature = ethUtil.ecsign(orderHashWithEthSignPrefixBuffer, signerPrivateKey);
// Create 0x signature from EthSign signature

View File

@ -1,19 +1,11 @@
import { EIP712Schema, EIP712Types, eip712Utils, generatePseudoRandomSalt } from '@0xproject/order-utils';
import { eip712Utils, generatePseudoRandomSalt } from '@0xproject/order-utils';
import { SignatureType } from '@0xproject/types';
import { signTypedDataUtils } from '@0xproject/utils';
import * as ethUtil from 'ethereumjs-util';
import { signingUtils } from './signing_utils';
import { SignedTransaction } from './types';
const EIP712_ZEROEX_TRANSACTION_SCHEMA: EIP712Schema = {
name: 'ZeroExTransaction',
parameters: [
{ name: 'salt', type: EIP712Types.Uint256 },
{ name: 'signerAddress', type: EIP712Types.Address },
{ name: 'data', type: EIP712Types.Bytes },
],
};
export class TransactionFactory {
private readonly _signerBuff: Buffer;
private readonly _exchangeAddress: string;
@ -31,12 +23,10 @@ export class TransactionFactory {
signerAddress,
data,
};
const executeTransactionHashBuff = eip712Utils.structHash(
EIP712_ZEROEX_TRANSACTION_SCHEMA,
executeTransactionData,
);
const txHash = eip712Utils.createEIP712Message(executeTransactionHashBuff, this._exchangeAddress);
const signature = signingUtils.signMessage(txHash, this._privateKey, signatureType);
const typedData = eip712Utils.createZeroExTransactionTypedData(executeTransactionData, this._exchangeAddress);
const eip712MessageBuffer = signTypedDataUtils.generateTypedDataHash(typedData);
const signature = signingUtils.signMessage(eip712MessageBuffer, this._privateKey, signatureType);
const signedTx = {
exchangeAddress: this._exchangeAddress,
signature: `0x${signature.toString('hex')}`,

View File

@ -0,0 +1,88 @@
## Dev tools pages
This repository contains our dev tools pages.
## Local Dev Setup
Requires Node version 6.9.5 or higher & yarn v1.9.4
### 1. Install dependencies for monorepo:
Make sure you install Yarn v1.9.4 (npm won't work!). We rely on our `yarn.lock` file and on Yarn's support for `workspaces` in our monorepo setup.
```bash
yarn install
```
### 2. Initial setup
To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory:
```bash
PKG=@0xproject/dev-tools-pages yarn build
```
Note: Ignore the `WARNING in asset size limit` and `WARNING in entrypoint size limit` warnings.
### 3. Run dev server
```bash
cd packages/dev-tools-pages
yarn dev
```
Visit [http://localhost:3572/](http://localhost:3572/) in your browser.
The webpage will refresh when source code is changed.
### 4. Code!
There are some basic primitives we'd like you to use:
1. `<Container>Stuff</Container>`: Use containers instead of divs,spans,etc... and use it's props instead of inline styles (e.g `style={{margin: 3}}` should be `margin="3px"`
2. `<Text>Look ma, text!</Text>`: Use text components whenever rendering text. It has props for manipulating texts, so again no in-line styles. Use `fontColor="red"`, not `style={{color: 'red'}}`.
3. Styled-components: See the `ui/button.tsx` file for an example of how to use these.
4. BassCss: This library gives you access to a bunch of [classes](http://basscss.com/) that apply styles in a browser-compatible way, has affordances for responsiveness and alleviates the need for inline styles or LESS/CSS files.
With the above 4 tools and following the React paradigm, you shouldn't need CSS/LESS files. IF there are special occasions where you do, these is a `all.less` file, but this is a solution of last resort. Use it sparingly.
### Clean
```bash
yarn clean
```
### Lint
```bash
yarn lint
```
### Prettier
Run from the monorepo root directory:
```
yarn prettier
```
### Resources
##### Toolkit
* [Styled Components](https://www.styled-components.com/)
* [BassCSS](http://basscss.com/)
##### Recommended Atom packages:
* [atom-typescript](https://atom.io/packages/atom-typescript)
* [linter-tslint](https://atom.io/packages/linter-tslint)
## Contributing
We strongly recommend that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository.
Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started.

View File

View File

@ -0,0 +1,58 @@
{
"name": "@0xproject/dev-tools-pages",
"version": "0.0.1",
"engines": {
"node": ">=6.12"
},
"private": true,
"description": "0x Dev tools pages",
"scripts": {
"build": "node --max_old_space_size=8192 ../../node_modules/.bin/webpack --mode production",
"build:ci": "yarn build",
"build:dev": "../../node_modules/.bin/webpack --mode development",
"clean": "shx rm -f public/bundle*",
"lint": "tslint --project . 'ts/**/*.ts' 'ts/**/*.tsx'",
"dev": "webpack-dev-server --mode development --content-base public"
},
"license": "Apache-2.0",
"dependencies": {
"@0xproject/react-shared": "^1.0.15",
"basscss": "^8.0.3",
"bowser": "^1.9.3",
"less": "^2.7.2",
"lodash": "^4.17.5",
"polished": "^1.9.2",
"react": "^16.4.2",
"react-document-title": "^2.0.3",
"react-dom": "^16.4.2",
"react-helmet": "^5.2.0",
"styled-components": "^3.3.0"
},
"devDependencies": {
"@types/lodash": "4.14.104",
"@types/node": "*",
"@types/react": "^16.4.2",
"@types/react-dom": "^16.0.7",
"@types/react-helmet": "^5.0.6",
"@types/react-router-dom": "^4.0.4",
"@types/react-tap-event-plugin": "0.0.30",
"@types/styled-components": "^4.0.0",
"awesome-typescript-loader": "^5.2.1",
"copyfiles": "^2.0.0",
"css-loader": "0.23.x",
"less-loader": "^4.1.0",
"make-promises-safe": "^1.1.0",
"raw-loader": "^0.5.1",
"shx": "^0.2.2",
"source-map-loader": "^0.2.4",
"style-loader": "0.23.x",
"terser-webpack-plugin": "^1.1.0",
"tslint": "5.11.0",
"tslint-config-0xproject": "^0.0.2",
"typescript": "3.0.1",
"uglifyjs-webpack-plugin": "^2.0.1",
"webpack": "^4.20.2",
"webpack-cli": "3.1.2",
"webpack-dev-server": "^3.1.9"
}
}

View File

@ -0,0 +1,85 @@
/* Custom Basscss Responsive Utilities */
@media (max-width: 52em) {
.sm-center {
text-align: center;
}
.sm-align-middle {
vertical-align: middle;
}
.sm-align-top {
vertical-align: top;
}
.sm-left-align {
text-align: left;
}
.sm-right-align {
text-align: right;
}
.sm-table-cell {
display: table-cell;
}
.sm-mx-auto {
margin-left: auto;
margin-right: auto;
}
.sm-right {
float: right;
}
}
@media (min-width: 52em) {
.md-center {
text-align: center;
}
.md-align-middle {
vertical-align: middle;
}
.md-align-top {
vertical-align: top;
}
.md-left-align {
text-align: left;
}
.md-right-align {
text-align: right;
}
.md-table-cell {
display: table-cell;
}
.md-mx-auto {
margin-left: auto;
margin-right: auto;
}
.md-right {
float: right;
}
}
@media (min-width: 64em) {
.lg-center {
text-align: center;
}
.lg-align-middle {
vertical-align: middle;
}
.lg-align-top {
vertical-align: top;
}
.lg-left-align {
text-align: left;
}
.lg-right-align {
text-align: right;
}
.lg-table-cell {
display: table-cell;
}
.lg-mx-auto {
margin-left: auto;
margin-right: auto;
}
.lg-right {
float: right;
}
}

View File

@ -0,0 +1,453 @@
/* Basscss Responsive Margin */
@media (max-width: 52em) {
/* Modified by Fabio Berger to max-width from min-width */
.sm-m0 {
margin: 0;
}
.sm-mt0 {
margin-top: 0;
}
.sm-mr0 {
margin-right: 0;
}
.sm-mb0 {
margin-bottom: 0;
}
.sm-ml0 {
margin-left: 0;
}
.sm-mx0 {
margin-left: 0;
margin-right: 0;
}
.sm-my0 {
margin-top: 0;
margin-bottom: 0;
}
.sm-m1 {
margin: 0.5rem;
}
.sm-mt1 {
margin-top: 0.5rem;
}
.sm-mr1 {
margin-right: 0.5rem;
}
.sm-mb1 {
margin-bottom: 0.5rem;
}
.sm-ml1 {
margin-left: 0.5rem;
}
.sm-mx1 {
margin-left: 0.5rem;
margin-right: 0.5rem;
}
.sm-my1 {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
.sm-m2 {
margin: 1rem;
}
.sm-mt2 {
margin-top: 1rem;
}
.sm-mr2 {
margin-right: 1rem;
}
.sm-mb2 {
margin-bottom: 1rem;
}
.sm-ml2 {
margin-left: 1rem;
}
.sm-mx2 {
margin-left: 1rem;
margin-right: 1rem;
}
.sm-my2 {
margin-top: 1rem;
margin-bottom: 1rem;
}
.sm-m3 {
margin: 2rem;
}
.sm-mt3 {
margin-top: 2rem;
}
.sm-mr3 {
margin-right: 2rem;
}
.sm-mb3 {
margin-bottom: 2rem;
}
.sm-ml3 {
margin-left: 2rem;
}
.sm-mx3 {
margin-left: 2rem;
margin-right: 2rem;
}
.sm-my3 {
margin-top: 2rem;
margin-bottom: 2rem;
}
.sm-m4 {
margin: 4rem;
}
.sm-mt4 {
margin-top: 4rem;
}
.sm-mr4 {
margin-right: 4rem;
}
.sm-mb4 {
margin-bottom: 4rem;
}
.sm-ml4 {
margin-left: 4rem;
}
.sm-mx4 {
margin-left: 4rem;
margin-right: 4rem;
}
.sm-my4 {
margin-top: 4rem;
margin-bottom: 4rem;
}
.sm-mxn1 {
margin-left: -0.5rem;
margin-right: -0.5rem;
}
.sm-mxn2 {
margin-left: -1rem;
margin-right: -1rem;
}
.sm-mxn3 {
margin-left: -2rem;
margin-right: -2rem;
}
.sm-mxn4 {
margin-left: -4rem;
margin-right: -4rem;
}
.sm-ml-auto {
margin-left: auto;
}
.sm-mr-auto {
margin-right: auto;
}
.sm-mx-auto {
margin-left: auto;
margin-right: auto;
}
}
@media (min-width: 52em) {
.md-m0 {
margin: 0;
}
.md-mt0 {
margin-top: 0;
}
.md-mr0 {
margin-right: 0;
}
.md-mb0 {
margin-bottom: 0;
}
.md-ml0 {
margin-left: 0;
}
.md-mx0 {
margin-left: 0;
margin-right: 0;
}
.md-my0 {
margin-top: 0;
margin-bottom: 0;
}
.md-m1 {
margin: 0.5rem;
}
.md-mt1 {
margin-top: 0.5rem;
}
.md-mr1 {
margin-right: 0.5rem;
}
.md-mb1 {
margin-bottom: 0.5rem;
}
.md-ml1 {
margin-left: 0.5rem;
}
.md-mx1 {
margin-left: 0.5rem;
margin-right: 0.5rem;
}
.md-my1 {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
.md-m2 {
margin: 1rem;
}
.md-mt2 {
margin-top: 1rem;
}
.md-mr2 {
margin-right: 1rem;
}
.md-mb2 {
margin-bottom: 1rem;
}
.md-ml2 {
margin-left: 1rem;
}
.md-mx2 {
margin-left: 1rem;
margin-right: 1rem;
}
.md-my2 {
margin-top: 1rem;
margin-bottom: 1rem;
}
.md-m3 {
margin: 2rem;
}
.md-mt3 {
margin-top: 2rem;
}
.md-mr3 {
margin-right: 2rem;
}
.md-mb3 {
margin-bottom: 2rem;
}
.md-ml3 {
margin-left: 2rem;
}
.md-mx3 {
margin-left: 2rem;
margin-right: 2rem;
}
.md-my3 {
margin-top: 2rem;
margin-bottom: 2rem;
}
.md-m4 {
margin: 4rem;
}
.md-mt4 {
margin-top: 4rem;
}
.md-mr4 {
margin-right: 4rem;
}
.md-mb4 {
margin-bottom: 4rem;
}
.md-ml4 {
margin-left: 4rem;
}
.md-mx4 {
margin-left: 4rem;
margin-right: 4rem;
}
.md-my4 {
margin-top: 4rem;
margin-bottom: 4rem;
}
.md-mxn1 {
margin-left: -0.5rem;
margin-right: -0.5rem;
}
.md-mxn2 {
margin-left: -1rem;
margin-right: -1rem;
}
.md-mxn3 {
margin-left: -2rem;
margin-right: -2rem;
}
.md-mxn4 {
margin-left: -4rem;
margin-right: -4rem;
}
.md-ml-auto {
margin-left: auto;
}
.md-mr-auto {
margin-right: auto;
}
.md-mx-auto {
margin-left: auto;
margin-right: auto;
}
}
@media (min-width: 64em) {
.lg-m0 {
margin: 0;
}
.lg-mt0 {
margin-top: 0;
}
.lg-mr0 {
margin-right: 0;
}
.lg-mb0 {
margin-bottom: 0;
}
.lg-ml0 {
margin-left: 0;
}
.lg-mx0 {
margin-left: 0;
margin-right: 0;
}
.lg-my0 {
margin-top: 0;
margin-bottom: 0;
}
.lg-m1 {
margin: 0.5rem;
}
.lg-mt1 {
margin-top: 0.5rem;
}
.lg-mr1 {
margin-right: 0.5rem;
}
.lg-mb1 {
margin-bottom: 0.5rem;
}
.lg-ml1 {
margin-left: 0.5rem;
}
.lg-mx1 {
margin-left: 0.5rem;
margin-right: 0.5rem;
}
.lg-my1 {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
.lg-m2 {
margin: 1rem;
}
.lg-mt2 {
margin-top: 1rem;
}
.lg-mr2 {
margin-right: 1rem;
}
.lg-mb2 {
margin-bottom: 1rem;
}
.lg-ml2 {
margin-left: 1rem;
}
.lg-mx2 {
margin-left: 1rem;
margin-right: 1rem;
}
.lg-my2 {
margin-top: 1rem;
margin-bottom: 1rem;
}
.lg-m3 {
margin: 2rem;
}
.lg-mt3 {
margin-top: 2rem;
}
.lg-mr3 {
margin-right: 2rem;
}
.lg-mb3 {
margin-bottom: 2rem;
}
.lg-ml3 {
margin-left: 2rem;
}
.lg-mx3 {
margin-left: 2rem;
margin-right: 2rem;
}
.lg-my3 {
margin-top: 2rem;
margin-bottom: 2rem;
}
.lg-m4 {
margin: 4rem;
}
.lg-mt4 {
margin-top: 4rem;
}
.lg-mr4 {
margin-right: 4rem;
}
.lg-mb4 {
margin-bottom: 4rem;
}
.lg-ml4 {
margin-left: 4rem;
}
.lg-mx4 {
margin-left: 4rem;
margin-right: 4rem;
}
.lg-my4 {
margin-top: 4rem;
margin-bottom: 4rem;
}
.lg-mxn1 {
margin-left: -0.5rem;
margin-right: -0.5rem;
}
.lg-mxn2 {
margin-left: -1rem;
margin-right: -1rem;
}
.lg-mxn3 {
margin-left: -2rem;
margin-right: -2rem;
}
.lg-mxn4 {
margin-left: -4rem;
margin-right: -4rem;
}
.lg-ml-auto {
margin-left: auto;
}
.lg-mr-auto {
margin-right: auto;
}
.lg-mx-auto {
margin-left: auto;
margin-right: auto;
}
}

View File

@ -0,0 +1,134 @@
/* Basscss Responsive Padding */
/* Modified by Fabio Berger to include xs prefix */
@media (max-width: 52em) { /* Modified by Fabio Berger to max-width from min-width */
.sm-p0 { padding: 0 }
.sm-pt0 { padding-top: 0 }
.sm-pr0 { padding-right: 0 }
.sm-pb0 { padding-bottom: 0 }
.sm-pl0 { padding-left: 0 }
.sm-px0 { padding-left: 0; padding-right: 0 }
.sm-py0 { padding-top: 0; padding-bottom: 0 }
.sm-p1 { padding: .5rem }
.sm-pt1 { padding-top: .5rem }
.sm-pr1 { padding-right: .5rem }
.sm-pb1 { padding-bottom: .5rem }
.sm-pl1 { padding-left: .5rem }
.sm-px1 { padding-left: .5rem; padding-right: .5rem }
.sm-py1 { padding-top: .5rem; padding-bottom: .5rem }
.sm-p2 { padding: 1rem }
.sm-pt2 { padding-top: 1rem }
.sm-pr2 { padding-right: 1rem }
.sm-pb2 { padding-bottom: 1rem }
.sm-pl2 { padding-left: 1rem }
.sm-px2 { padding-left: 1rem; padding-right: 1rem }
.sm-py2 { padding-top: 1rem; padding-bottom: 1rem }
.sm-p3 { padding: 2rem }
.sm-pt3 { padding-top: 2rem }
.sm-pr3 { padding-right: 2rem }
.sm-pb3 { padding-bottom: 2rem }
.sm-pl3 { padding-left: 2rem }
.sm-px3 { padding-left: 2rem; padding-right: 2rem }
.sm-py3 { padding-top: 2rem; padding-bottom: 2rem }
.sm-p4 { padding: 4rem }
.sm-pt4 { padding-top: 4rem }
.sm-pr4 { padding-right: 4rem }
.sm-pb4 { padding-bottom: 4rem }
.sm-pl4 { padding-left: 4rem }
.sm-px4 { padding-left: 4rem; padding-right: 4rem }
.sm-py4 { padding-top: 4rem; padding-bottom: 4rem }
}
@media (min-width: 52em) {
.md-p0 { padding: 0 }
.md-pt0 { padding-top: 0 }
.md-pr0 { padding-right: 0 }
.md-pb0 { padding-bottom: 0 }
.md-pl0 { padding-left: 0 }
.md-px0 { padding-left: 0; padding-right: 0 }
.md-py0 { padding-top: 0; padding-bottom: 0 }
.md-p1 { padding: .5rem }
.md-pt1 { padding-top: .5rem }
.md-pr1 { padding-right: .5rem }
.md-pb1 { padding-bottom: .5rem }
.md-pl1 { padding-left: .5rem }
.md-px1 { padding-left: .5rem; padding-right: .5rem }
.md-py1 { padding-top: .5rem; padding-bottom: .5rem }
.md-p2 { padding: 1rem }
.md-pt2 { padding-top: 1rem }
.md-pr2 { padding-right: 1rem }
.md-pb2 { padding-bottom: 1rem }
.md-pl2 { padding-left: 1rem }
.md-px2 { padding-left: 1rem; padding-right: 1rem }
.md-py2 { padding-top: 1rem; padding-bottom: 1rem }
.md-p3 { padding: 2rem }
.md-pt3 { padding-top: 2rem }
.md-pr3 { padding-right: 2rem }
.md-pb3 { padding-bottom: 2rem }
.md-pl3 { padding-left: 2rem }
.md-px3 { padding-left: 2rem; padding-right: 2rem }
.md-py3 { padding-top: 2rem; padding-bottom: 2rem }
.md-p4 { padding: 4rem }
.md-pt4 { padding-top: 4rem }
.md-pr4 { padding-right: 4rem }
.md-pb4 { padding-bottom: 4rem }
.md-pl4 { padding-left: 4rem }
.md-px4 { padding-left: 4rem; padding-right: 4rem }
.md-py4 { padding-top: 4rem; padding-bottom: 4rem }
}
@media (min-width: 64em) {
.lg-p0 { padding: 0 }
.lg-pt0 { padding-top: 0 }
.lg-pr0 { padding-right: 0 }
.lg-pb0 { padding-bottom: 0 }
.lg-pl0 { padding-left: 0 }
.lg-px0 { padding-left: 0; padding-right: 0 }
.lg-py0 { padding-top: 0; padding-bottom: 0 }
.lg-p1 { padding: .5rem }
.lg-pt1 { padding-top: .5rem }
.lg-pr1 { padding-right: .5rem }
.lg-pb1 { padding-bottom: .5rem }
.lg-pl1 { padding-left: .5rem }
.lg-px1 { padding-left: .5rem; padding-right: .5rem }
.lg-py1 { padding-top: .5rem; padding-bottom: .5rem }
.lg-p2 { padding: 1rem }
.lg-pt2 { padding-top: 1rem }
.lg-pr2 { padding-right: 1rem }
.lg-pb2 { padding-bottom: 1rem }
.lg-pl2 { padding-left: 1rem }
.lg-px2 { padding-left: 1rem; padding-right: 1rem }
.lg-py2 { padding-top: 1rem; padding-bottom: 1rem }
.lg-p3 { padding: 2rem }
.lg-pt3 { padding-top: 2rem }
.lg-pr3 { padding-right: 2rem }
.lg-pb3 { padding-bottom: 2rem }
.lg-pl3 { padding-left: 2rem }
.lg-px3 { padding-left: 2rem; padding-right: 2rem }
.lg-py3 { padding-top: 2rem; padding-bottom: 2rem }
.lg-p4 { padding: 4rem }
.lg-pt4 { padding-top: 4rem }
.lg-pr4 { padding-right: 4rem }
.lg-pb4 { padding-bottom: 4rem }
.lg-pl4 { padding-left: 4rem }
.lg-px4 { padding-left: 4rem; padding-right: 4rem }
.lg-py4 { padding-top: 4rem; padding-bottom: 4rem }
}

View File

@ -0,0 +1,35 @@
/* Basscss Responsive Type Scale */
/* Modified by Fabio Berger to include xs prefix */
@media (max-width: 52em) { /* Modified by Fabio Berger to max-width from min-width */
.sm-h00 { font-size: 4rem }
.sm-h0 { font-size: 3rem }
.sm-h1 { font-size: 2rem }
.sm-h2 { font-size: 1.5rem }
.sm-h3 { font-size: 1.25rem }
.sm-h4 { font-size: 1rem }
.sm-h5 { font-size: .875rem }
.sm-h6 { font-size: .75rem }
}
@media (min-width: 52em) {
.md-h00 { font-size: 4rem }
.md-h0 { font-size: 3rem }
.md-h1 { font-size: 2rem }
.md-h2 { font-size: 1.5rem }
.md-h3 { font-size: 1.25rem }
.md-h4 { font-size: 1rem }
.md-h5 { font-size: .875rem }
.md-h6 { font-size: .75rem }
}
@media (min-width: 64em) {
.lg-h00 { font-size: 4rem }
.lg-h0 { font-size: 3rem }
.lg-h1 { font-size: 2rem }
.lg-h2 { font-size: 1.5rem }
.lg-h3 { font-size: 1.25rem }
.lg-h4 { font-size: 1rem }
.lg-h5 { font-size: .875rem }
.lg-h6 { font-size: .75rem }
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="" />
<meta property="og:type" content="website" />
<meta property="og:title" content="0x" />
<meta property="og:description" content="" />
<meta property="og:image" content="/images/og_image.png" />
<title>0x: The Protocol for Trading Tokens</title>
<link rel="icon" type="image/png" href="/images/favicon/favicon-2-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="/images/favicon/favicon-2-16x16.png" sizes="16x16" />
<link rel="stylesheet" href="/css/basscss_responsive_custom.css">
<link rel="stylesheet" href="/css/basscss_responsive_padding.css">
<link rel="stylesheet" href="/css/basscss_responsive_margin.css">
<link rel="stylesheet" href="/css/basscss_responsive_type_scale.css">
</head>
<body style="margin: 0px; min-width: 355px;">
<div id="app"></div>
<script type="text/javascript" crossorigin="anonymous" src="/bundle.js" charset="utf-8"></script>
</body>
</html>

View File

@ -0,0 +1,25 @@
import * as React from 'react';
import { Helmet } from 'react-helmet';
export interface MetaTagsProps {
title: string;
description: string;
imgSrc?: string;
}
export const MetaTags: React.StatelessComponent<MetaTagsProps> = ({ title, description, imgSrc }) => (
<Helmet>
<title>{title}</title>
<meta name="description" content={description} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:type" content="website" />
<meta property="og:image" content={imgSrc} />
<meta name="twitter:site" content="@0xproject" />
<meta name="twitter:image" content={imgSrc} />
</Helmet>
);
MetaTags.defaultProps = {
imgSrc: '/images/og_image.png',
};

View File

@ -0,0 +1,59 @@
import { darken, saturate } from 'polished';
import * as React from 'react';
import styled from 'styled-components';
/**
* AN EXAMPLE OF HOW TO CREATE A STYLED COMPONENT USING STYLED-COMPONENTS
* SEE: https://www.styled-components.com/docs/basics#coming-from-css
*/
export interface ButtonProps {
backgroundColor?: string;
borderColor?: string;
width?: string;
padding?: string;
type?: string;
isDisabled?: boolean;
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
className?: string;
}
const PlainButton: React.StatelessComponent<ButtonProps> = ({ children, isDisabled, onClick, type, className }) => (
<button type={type} className={className} onClick={isDisabled ? undefined : onClick} disabled={isDisabled}>
{children}
</button>
);
const darkenOnHoverAmount = 0.1;
const darkenOnActiveAmount = 0.2;
const saturateOnFocusAmount = 0.2;
export const Button = styled(PlainButton)`
cursor: ${props => (props.isDisabled ? 'default' : 'pointer')};
transition: background-color, opacity 0.5s ease;
padding: ${props => props.padding};
border-radius: 3px;
outline: none;
width: ${props => props.width};
background-color: ${props => (props.backgroundColor ? props.backgroundColor : 'none')};
border: ${props => (props.borderColor ? `1px solid ${props.backgroundColor}` : 'none')};
&:hover {
background-color: ${props =>
!props.isDisabled ? darken(darkenOnHoverAmount, props.backgroundColor) : ''} !important;
}
&:active {
background-color: ${props => (!props.isDisabled ? darken(darkenOnActiveAmount, props.backgroundColor) : '')};
}
&:disabled {
opacity: 0.5;
}
&:focus {
background-color: ${props => saturate(saturateOnFocusAmount, props.backgroundColor)};
}
`;
Button.defaultProps = {
backgroundColor: 'red',
width: 'auto',
isDisabled: false,
padding: '1em 2.2em',
};
Button.displayName = 'Button';

View File

@ -0,0 +1,55 @@
import * as React from 'react';
type StringOrNum = string | number;
export type ContainerTag = 'div' | 'span';
export interface ContainerProps {
marginTop?: StringOrNum;
marginBottom?: StringOrNum;
marginRight?: StringOrNum;
marginLeft?: StringOrNum;
padding?: StringOrNum;
paddingTop?: StringOrNum;
paddingBottom?: StringOrNum;
paddingRight?: StringOrNum;
paddingLeft?: StringOrNum;
backgroundColor?: string;
borderRadius?: StringOrNum;
maxWidth?: StringOrNum;
maxHeight?: StringOrNum;
width?: StringOrNum;
height?: StringOrNum;
minWidth?: StringOrNum;
minHeight?: StringOrNum;
isHidden?: boolean;
className?: string;
position?: 'absolute' | 'fixed' | 'relative' | 'unset';
display?: 'inline-block' | 'block' | 'inline-flex' | 'inline';
top?: string;
left?: string;
right?: string;
bottom?: string;
zIndex?: number;
Tag?: ContainerTag;
cursor?: string;
id?: string;
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
overflowX?: 'scroll' | 'hidden' | 'auto' | 'visible';
}
export const Container: React.StatelessComponent<ContainerProps> = props => {
const { children, className, Tag, isHidden, id, onClick, ...style } = props;
const visibility = isHidden ? 'hidden' : undefined;
return (
<Tag id={id} style={{ ...style, visibility }} className={className} onClick={onClick}>
{children}
</Tag>
);
};
Container.defaultProps = {
Tag: 'div',
};
Container.displayName = 'Container';

View File

@ -0,0 +1,74 @@
import { colors } from '@0xproject/react-shared';
import { darken } from 'polished';
import * as React from 'react';
import styled from 'styled-components';
export type TextTag = 'p' | 'div' | 'span' | 'label' | 'h1' | 'h2' | 'h3' | 'h4' | 'i';
export interface TextProps {
className?: string;
Tag?: TextTag;
fontSize?: string;
fontFamily?: string;
fontStyle?: string;
fontColor?: string;
lineHeight?: string;
minHeight?: string;
center?: boolean;
fontWeight?: number | string;
textDecorationLine?: string;
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
hoverColor?: string;
noWrap?: boolean;
display?: string;
}
const PlainText: React.StatelessComponent<TextProps> = ({ children, className, onClick, Tag }) => (
<Tag className={className} onClick={onClick}>
{children}
</Tag>
);
export const Text = styled(PlainText)`
font-family: ${props => props.fontFamily};
font-style: ${props => props.fontStyle};
font-weight: ${props => props.fontWeight};
font-size: ${props => props.fontSize};
text-decoration-line: ${props => props.textDecorationLine};
${props => (props.lineHeight ? `line-height: ${props.lineHeight}` : '')};
${props => (props.center ? 'text-align: center' : '')};
color: ${props => props.fontColor};
${props => (props.minHeight ? `min-height: ${props.minHeight}` : '')};
${props => (props.onClick ? 'cursor: pointer' : '')};
transition: color 0.5s ease;
${props => (props.noWrap ? 'white-space: nowrap' : '')};
${props => (props.display ? `display: ${props.display}` : '')};
&:hover {
${props => (props.onClick ? `color: ${props.hoverColor || darken(0.3, props.fontColor || 'black')}` : '')};
}
`;
Text.defaultProps = {
fontFamily: 'Roboto',
fontStyle: 'normal',
fontWeight: 400,
fontColor: colors.black,
fontSize: '15px',
lineHeight: '1.5em',
textDecorationLine: 'none',
Tag: 'div',
noWrap: false,
};
Text.displayName = 'Text';
export const Title: React.StatelessComponent<TextProps> = props => <Text {...props} />;
Title.defaultProps = {
Tag: 'h2',
fontSize: '20px',
fontWeight: 600,
fontColor: colors.black,
};
Title.displayName = 'Title';

View File

@ -0,0 +1,9 @@
declare module 'whatwg-fetch';
declare module 'react-document-title';
declare module '*.json' {
const json: any;
/* tslint:disable */
export default json;
/* tslint:enable */
}

View File

@ -0,0 +1,17 @@
import * as React from 'react';
import { render } from 'react-dom';
import { MetaTags } from 'ts/components/meta_tags';
import { Landing } from 'ts/pages/landing';
import 'basscss/css/basscss.css';
const DOCUMENT_TITLE = '';
const DOCUMENT_DESCRIPTION = '';
render(
<div>
<MetaTags title={DOCUMENT_TITLE} description={DOCUMENT_DESCRIPTION} />
<Landing />
</div>,
document.getElementById('app'),
);

View File

@ -0,0 +1,27 @@
import * as _ from 'lodash';
import * as React from 'react';
import { Button } from '../components/ui/button';
import { Container } from '../components/ui/container';
import { Text } from '../components/ui/text';
interface LandingProps {}
interface LandingState {}
export class Landing extends React.Component<LandingProps, LandingState> {
constructor(props: LandingProps) {
super(props);
}
public render(): React.ReactNode {
return (
<Container id="landing" className="clearfix">
<Container className="mx-auto p4" width="200px">
<Button>
<Text fontColor="white">Click me!</Text>
</Button>
</Container>
</Container>
);
}
}

View File

@ -0,0 +1,32 @@
import * as bowser from 'bowser';
import * as _ from 'lodash';
export const utils = {
getColSize(items: number): number {
const bassCssGridSize = 12; // Source: http://basscss.com/#basscss-grid
const colSize = bassCssGridSize / items;
if (!_.isInteger(colSize)) {
throw new Error(`Number of cols must be divisible by ${bassCssGridSize}`);
}
return colSize;
},
getCurrentBaseUrl(): string {
const port = window.location.port;
const hasPort = !_.isUndefined(port);
const baseUrl = `https://${window.location.hostname}${hasPort ? `:${port}` : ''}`;
return baseUrl;
},
onPageLoadPromise: new Promise<void>((resolve, _reject) => {
if (document.readyState === 'complete') {
resolve();
return;
}
window.onload = () => resolve();
}),
openUrl(url: string): void {
window.open(url, '_blank');
},
isMobileOperatingSystem(): boolean {
return bowser.mobile;
},
};

View File

@ -0,0 +1,22 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"outDir": "./transpiled/",
"jsx": "react",
"baseUrl": "./",
"allowJs": true,
"strictNullChecks": false,
"noImplicitThis": false,
// tsconfig.json at the monorepo root contains some options required for
// project references which do not work for website. We override those
// options here.
"declaration": false,
"declarationMap": false,
"composite": false,
"paths": {
"*": ["node_modules/@types/*", "*"]
}
},
"include": ["./ts/**/*"]
}

View File

@ -0,0 +1,10 @@
{
"extends": ["@0xproject/tslint-config"],
"rules": {
"no-implicit-dependencies": false,
"no-object-literal-type-assertion": false,
"completed-docs": false,
"prefer-function-over-method": false,
"custom-no-magic-numbers": false
}
}

View File

@ -0,0 +1,86 @@
const path = require('path');
const webpack = require('webpack');
const TerserPlugin = require('terser-webpack-plugin');
const childProcess = require('child_process');
const config = {
entry: ['./ts/index.tsx'],
output: {
path: path.join(__dirname, '/public'),
filename: 'bundle.js',
chunkFilename: 'bundle-[name].js',
publicPath: '/',
},
devtool: 'source-map',
resolve: {
modules: [path.join(__dirname, '/ts'), 'node_modules'],
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
alias: {
ts: path.join(__dirname, '/ts'),
less: path.join(__dirname, '/less'),
},
},
module: {
rules: [
{
test: /\.js$/,
loader: 'source-map-loader',
exclude: [
// instead of /\/node_modules\//
path.join(process.cwd(), 'node_modules'),
path.join(process.cwd(), '../..', 'node_modules'),
],
},
{
test: /\.tsx?$/,
loader: 'awesome-typescript-loader',
},
{
test: /\.md$/,
use: 'raw-loader',
},
{
test: /\.less$/,
loader: 'style-loader!css-loader!less-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
loaders: ['style-loader', 'css-loader'],
},
],
},
optimization: {
minimizer: [
new TerserPlugin({
sourceMap: true,
}),
],
},
devServer: {
port: 3572,
disableHostCheck: true,
},
};
module.exports = (_env, argv) => {
let plugins = [];
if (argv.mode === 'development') {
config.mode = 'development';
} else {
config.mode = 'production';
plugins = plugins.concat([
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
},
}),
]);
}
console.log('i 「atl」: Mode: ', config.mode);
config.plugins = plugins;
console.log('i 「atl」: Plugin Count: ', config.plugins.length);
return config;
};

View File

@ -1,4 +1,13 @@
[
{
"version": "1.1.0",
"changes": [
{
"note": "Add `JSONRPCResponseError` and error field on `JSONRPCResponsePayload`.",
"pr": 1102
}
]
},
{
"timestamp": 1538693146,
"version": "1.0.11",

View File

@ -113,10 +113,16 @@ export interface JSONRPCRequestPayload {
jsonrpc: string;
}
export interface JSONRPCResponseError {
message: string;
code: number;
}
export interface JSONRPCResponsePayload {
result: any;
id: number;
jsonrpc: string;
error?: JSONRPCResponseError;
}
export interface AbstractBlock {

View File

@ -45,7 +45,7 @@
"@0xproject/utils": "^2.0.2",
"@0xproject/web3-wrapper": "^3.0.3",
"ethereum-types": "^1.0.11",
"ethers": "4.0.0-beta.14",
"ethers": "~4.0.4",
"lodash": "^4.17.5"
},
"publishConfig": {

View File

@ -43,15 +43,19 @@
},
"homepage": "https://github.com/0xProject/0x-monorepo/packages/instant/README.md",
"dependencies": {
"@0xproject/connect": "^2.0.4",
"@0xproject/asset-buyer": "^2.0.0",
"@0xproject/types": "^1.1.4",
"@0xproject/typescript-typings": "^2.0.2",
"@0xproject/utils": "^1.0.11",
"@0xproject/utils": "^2.0.2",
"@0xproject/web3-wrapper": "^3.0.3",
"ethereum-types": "^1.0.11",
"lodash": "^4.17.10",
"polished": "^2.2.0",
"react": "^16.5.2",
"react-dom": "^16.5.2"
"react-dom": "^16.5.2",
"react-redux": "^5.0.7",
"redux": "^4.0.0",
"styled-components": "^3.4.9"
},
"devDependencies": {
"@0xproject/tslint-config": "^1.0.8",
@ -59,8 +63,10 @@
"@types/enzyme-adapter-react-16": "^1.0.3",
"@types/lodash": "^4.14.116",
"@types/node": "*",
"@types/react": "16.4.7",
"@types/react": "^16.4.16",
"@types/react-dom": "^16.0.8",
"@types/react-redux": "^6.0.9",
"@types/redux": "^3.6.0",
"awesome-typescript-loader": "^5.2.1",
"copyfiles": "^1.2.0",
"enzyme": "^3.6.0",

View File

@ -6,6 +6,19 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>0x Instant Dev Environment</title>
<script type="text/javascript" src="/main.bundle.js" charset="utf-8"></script>
<style>
#zeroExInstantContainer {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
body {
margin: 0;
background-color: rgba(0, 0, 0, 0.2);
}
</style>
</head>
<body>

View File

@ -0,0 +1,47 @@
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import * as React from 'react';
import { ColorOption } from '../style/theme';
import { Container, Input } from './ui';
export interface AmountInputProps {
fontColor?: ColorOption;
fontSize?: string;
value?: BigNumber;
onChange?: (value?: BigNumber) => void;
}
export class AmountInput extends React.Component<AmountInputProps> {
public render(): React.ReactNode {
const { fontColor, fontSize, value } = this.props;
return (
<Container borderBottom="1px solid rgba(255,255,255,0.3)" display="inline-block">
<Input
fontColor={fontColor}
fontSize={fontSize}
onChange={this._handleChange}
value={!_.isUndefined(value) ? value.toString() : ''}
placeholder="0.00"
width="2em"
/>
</Container>
);
}
private readonly _handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
const value = event.target.value;
let bigNumberValue;
if (!_.isEmpty(value)) {
try {
bigNumberValue = new BigNumber(event.target.value);
} catch {
// We don't want to allow values that can't be a BigNumber, so don't even call onChange.
return;
}
}
if (!_.isUndefined(this.props.onChange)) {
this.props.onChange(bigNumberValue);
}
};
}

View File

@ -0,0 +1,19 @@
import * as React from 'react';
import { ColorOption } from '../style/theme';
import { Button, Container, Text } from './ui';
export interface BuyButtonProps {}
export const BuyButton: React.StatelessComponent<BuyButtonProps> = props => (
<Container padding="20px" width="100%">
<Button width="100%">
<Text fontColor={ColorOption.white} fontWeight={600} fontSize="20px">
Buy
</Text>
</Button>
</Container>
);
BuyButton.displayName = 'BuyButton';

View File

@ -0,0 +1,45 @@
import * as React from 'react';
import { SelectedAssetAmountInput } from '../containers/selected_asset_amount_input';
import { ColorOption } from '../style/theme';
import { Container, Flex, Text } from './ui';
export interface InstantHeadingProps {}
export const InstantHeading: React.StatelessComponent<InstantHeadingProps> = props => (
<Container backgroundColor={ColorOption.primaryColor} padding="20px" width="100%" borderRadius="3px 3px 0px 0px">
<Container marginBottom="5px">
<Text
letterSpacing="1px"
fontColor={ColorOption.white}
opacity={0.7}
fontWeight={500}
textTransform="uppercase"
fontSize="12px"
>
I want to buy
</Text>
</Container>
<Flex direction="row" justify="space-between">
<Container>
<SelectedAssetAmountInput fontSize="45px" />
<Container display="inline-block" marginLeft="10px">
<Text fontSize="45px" fontColor={ColorOption.white} textTransform="uppercase">
rep
</Text>
</Container>
</Container>
<Flex direction="column" justify="space-between">
<Container marginBottom="5px">
<Text fontSize="16px" fontColor={ColorOption.white} fontWeight={500}>
0 ETH
</Text>
</Container>
<Text fontSize="16px" fontColor={ColorOption.white} opacity={0.7}>
$0.00
</Text>
</Flex>
</Flex>
</Container>
);

View File

@ -0,0 +1,62 @@
import * as React from 'react';
import { ColorOption } from '../style/theme';
import { Container, Flex, Text } from './ui';
export interface OrderDetailsProps {}
export const OrderDetails: React.StatelessComponent<OrderDetailsProps> = props => (
<Container padding="20px" width="100%">
<Container marginBottom="10px">
<Text
letterSpacing="1px"
fontColor={ColorOption.primaryColor}
fontWeight={600}
textTransform="uppercase"
fontSize="14px"
>
Order Details
</Text>
</Container>
<OrderDetailsRow name="Token Price" primaryValue=".013 ETH" secondaryValue="$24.32" />
<OrderDetailsRow name="Fee" primaryValue=".005 ETH" secondaryValue="$1.04" />
<OrderDetailsRow name="Total Cost" primaryValue="1.66 ETH" secondaryValue="$589.56" shouldEmphasize={true} />
</Container>
);
OrderDetails.displayName = 'OrderDetails';
export interface OrderDetailsRowProps {
name: string;
primaryValue: string;
secondaryValue: string;
shouldEmphasize?: boolean;
}
export const OrderDetailsRow: React.StatelessComponent<OrderDetailsRowProps> = props => {
const fontWeight = props.shouldEmphasize ? 700 : 400;
return (
<Container padding="10px 0px" borderTop="1px dashed" borderColor={ColorOption.feintGrey}>
<Flex justify="space-between">
<Text fontWeight={fontWeight} fontColor={ColorOption.grey}>
{props.name}
</Text>
<Container>
<Container marginRight="3px" display="inline-block">
<Text fontColor={ColorOption.lightGrey}>({props.secondaryValue}) </Text>
</Container>
<Text fontWeight={fontWeight} fontColor={ColorOption.grey}>
{props.primaryValue}
</Text>
</Container>
</Flex>
</Container>
);
};
OrderDetailsRow.defaultProps = {
shouldEmphasize: false,
};
OrderDetailsRow.displayName = 'OrderDetailsRow';

View File

@ -0,0 +1,60 @@
import { darken, saturate } from 'polished';
import * as React from 'react';
import { ColorOption, styled } from '../../style/theme';
export interface ButtonProps {
backgroundColor?: ColorOption;
borderColor?: ColorOption;
width?: string;
padding?: string;
type?: string;
isDisabled?: boolean;
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
className?: string;
}
const PlainButton: React.StatelessComponent<ButtonProps> = ({ children, isDisabled, onClick, type, className }) => (
<button type={type} className={className} onClick={isDisabled ? undefined : onClick} disabled={isDisabled}>
{children}
</button>
);
const darkenOnHoverAmount = 0.1;
const darkenOnActiveAmount = 0.2;
const saturateOnFocusAmount = 0.2;
export const Button = styled(PlainButton)`
cursor: ${props => (props.isDisabled ? 'default' : 'pointer')};
transition: background-color, opacity 0.5s ease;
padding: ${props => props.padding};
border-radius: 3px;
outline: none;
width: ${props => props.width};
background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')};
border: ${props => (props.borderColor ? `1px solid ${props.theme[props.borderColor]}` : 'none')};
&:hover {
background-color: ${props =>
!props.isDisabled
? darken(darkenOnHoverAmount, props.theme[props.backgroundColor || 'white'])
: ''} !important;
}
&:active {
background-color: ${props =>
!props.isDisabled ? darken(darkenOnActiveAmount, props.theme[props.backgroundColor || 'white']) : ''};
}
&:disabled {
opacity: 0.5;
}
&:focus {
background-color: ${props => saturate(saturateOnFocusAmount, props.theme[props.backgroundColor || 'white'])};
}
`;
Button.defaultProps = {
backgroundColor: ColorOption.primaryColor,
width: 'auto',
isDisabled: false,
padding: '1em 2.2em',
};
Button.displayName = 'Button';

View File

@ -0,0 +1,64 @@
import * as React from 'react';
import { ColorOption, styled } from '../../style/theme';
import { cssRuleIfExists } from '../../style/util';
export interface ContainerProps {
display?: string;
position?: string;
top?: string;
right?: string;
bottom?: string;
left?: string;
width?: string;
maxWidth?: string;
margin?: string;
marginTop?: string;
marginRight?: string;
marginBottom?: string;
marginLeft?: string;
padding?: string;
borderRadius?: string;
border?: string;
borderColor?: ColorOption;
borderTop?: string;
borderBottom?: string;
className?: string;
backgroundColor?: ColorOption;
hasBoxShadow?: boolean;
}
const PlainContainer: React.StatelessComponent<ContainerProps> = ({ children, className }) => (
<div className={className}>{children}</div>
);
export const Container = styled(PlainContainer)`
box-sizing: border-box;
${props => cssRuleIfExists(props, 'display')}
${props => cssRuleIfExists(props, 'position')}
${props => cssRuleIfExists(props, 'top')}
${props => cssRuleIfExists(props, 'right')}
${props => cssRuleIfExists(props, 'bottom')}
${props => cssRuleIfExists(props, 'left')}
${props => cssRuleIfExists(props, 'width')}
${props => cssRuleIfExists(props, 'max-width')}
${props => cssRuleIfExists(props, 'margin')}
${props => cssRuleIfExists(props, 'margin-top')}
${props => cssRuleIfExists(props, 'margin-right')}
${props => cssRuleIfExists(props, 'margin-bottom')}
${props => cssRuleIfExists(props, 'margin-left')}
${props => cssRuleIfExists(props, 'padding')}
${props => cssRuleIfExists(props, 'border-radius')}
${props => cssRuleIfExists(props, 'border')}
${props => cssRuleIfExists(props, 'border-top')}
${props => cssRuleIfExists(props, 'border-bottom')}
${props => (props.hasBoxShadow ? `box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.1)` : '')};
background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')};
border-color: ${props => (props.borderColor ? props.theme[props.borderColor] : 'none')};
`;
Container.defaultProps = {
display: 'block',
};
Container.displayName = 'Container';

View File

@ -0,0 +1,37 @@
import * as React from 'react';
import { ColorOption, styled } from '../../style/theme';
import { cssRuleIfExists } from '../../style/util';
export interface FlexProps {
direction?: 'row' | 'column';
flexWrap?: 'wrap' | 'nowrap';
justify?: 'flex-start' | 'center' | 'space-around' | 'space-between' | 'space-evenly' | 'flex-end';
align?: 'flex-start' | 'center' | 'space-around' | 'space-between' | 'space-evenly' | 'flex-end';
width?: string;
backgroundColor?: ColorOption;
className?: string;
}
const PlainFlex: React.StatelessComponent<FlexProps> = ({ children, className }) => (
<div className={className}>{children}</div>
);
export const Flex = styled(PlainFlex)`
display: flex;
flex-direction: ${props => props.direction};
flex-wrap: ${props => props.flexWrap};
justify-content: ${props => props.justify};
align-items: ${props => props.align};
${props => cssRuleIfExists(props, 'width')}
background-color: ${props => (props.backgroundColor ? props.theme[props.backgroundColor] : 'none')};
`;
Flex.defaultProps = {
direction: 'row',
flexWrap: 'nowrap',
justify: 'center',
align: 'center',
};
Flex.displayName = 'Flex';

View File

@ -0,0 +1,5 @@
export { Text, Title } from './text';
export { Button } from './button';
export { Flex } from './flex';
export { Container } from './container';
export { Input } from './input';

View File

@ -0,0 +1,40 @@
import * as React from 'react';
import { ColorOption, styled } from '../../style/theme';
export interface InputProps {
className?: string;
value?: string;
width?: string;
fontSize?: string;
fontColor?: ColorOption;
placeholder?: string;
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
}
const PlainInput: React.StatelessComponent<InputProps> = ({ value, className, placeholder, onChange }) => (
<input className={className} value={value} onChange={onChange} placeholder={placeholder} />
);
export const Input = styled(PlainInput)`
font-size: ${props => props.fontSize};
width: ${props => props.width};
padding: 0.1em 0em;
font-family: 'Inter UI';
color: ${props => props.theme[props.fontColor || 'white']};
background: transparent;
outline: none;
border: none;
&::placeholder {
color: ${props => props.theme[props.fontColor || 'white']};
opacity: 0.5;
}
`;
Input.defaultProps = {
width: 'auto',
fontColor: ColorOption.white,
fontSize: '12px',
};
Input.displayName = 'Input';

View File

@ -0,0 +1,80 @@
import { darken } from 'polished';
import * as React from 'react';
import { ColorOption, styled } from '../../style/theme';
export interface TextProps {
fontColor?: ColorOption;
fontFamily?: string;
fontStyle?: string;
fontSize?: string;
opacity?: number;
letterSpacing?: string;
textTransform?: string;
lineHeight?: string;
className?: string;
minHeight?: string;
center?: boolean;
fontWeight?: number | string;
textDecorationLine?: string;
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
hoverColor?: string;
noWrap?: boolean;
display?: string;
}
const PlainText: React.StatelessComponent<TextProps> = ({ children, className, onClick }) => (
<div className={className} onClick={onClick}>
{children}
</div>
);
const darkenOnHoverAmount = 0.3;
export const Text = styled(PlainText)`
font-family: ${props => props.fontFamily};
font-style: ${props => props.fontStyle};
font-weight: ${props => props.fontWeight};
font-size: ${props => props.fontSize};
opacity: ${props => props.opacity};
text-decoration-line: ${props => props.textDecorationLine};
${props => (props.lineHeight ? `line-height: ${props.lineHeight}` : '')};
${props => (props.center ? 'text-align: center' : '')};
color: ${props => props.fontColor && props.theme[props.fontColor]};
${props => (props.minHeight ? `min-height: ${props.minHeight}` : '')};
${props => (props.onClick ? 'cursor: pointer' : '')};
transition: color 0.5s ease;
${props => (props.noWrap ? 'white-space: nowrap' : '')};
${props => (props.display ? `display: ${props.display}` : '')};
${props => (props.letterSpacing ? `letter-spacing: ${props.letterSpacing}` : '')};
${props => (props.textTransform ? `text-transform: ${props.textTransform}` : '')};
&:hover {
${props =>
props.onClick
? `color: ${props.hoverColor || darken(darkenOnHoverAmount, props.theme[props.fontColor || 'white'])}`
: ''};
}
`;
Text.defaultProps = {
fontFamily: 'Inter UI',
fontStyle: 'normal',
fontWeight: 400,
fontColor: ColorOption.black,
fontSize: '15px',
textDecorationLine: 'none',
noWrap: false,
display: 'inline-block',
};
Text.displayName = 'Text';
export const Title: React.StatelessComponent<TextProps> = props => <Text {...props} />;
Title.defaultProps = {
fontSize: '20px',
fontWeight: 600,
opacity: 1,
fontColor: ColorOption.primaryColor,
};
Title.displayName = 'Title';

View File

@ -1,5 +1,20 @@
import * as React from 'react';
import { Provider } from 'react-redux';
import { store } from '../redux/store';
import { fonts } from '../style/fonts';
import { theme, ThemeProvider } from '../style/theme';
import { ZeroExInstantContainer } from './zero_ex_instant_container';
fonts.include();
export interface ZeroExInstantProps {}
export const ZeroExInstant: React.StatelessComponent<ZeroExInstantProps> = () => <div>ZeroExInstant</div>;
export const ZeroExInstant: React.StatelessComponent<ZeroExInstantProps> = () => (
<Provider store={store}>
<ThemeProvider theme={theme}>
<ZeroExInstantContainer />
</ThemeProvider>
</Provider>
);

View File

@ -0,0 +1,20 @@
import * as React from 'react';
import { ColorOption } from '../style/theme';
import { BuyButton } from './buy_button';
import { InstantHeading } from './instant_heading';
import { OrderDetails } from './order_details';
import { Container, Flex } from './ui';
export interface ZeroExInstantContainerProps {}
export const ZeroExInstantContainer: React.StatelessComponent<ZeroExInstantContainerProps> = props => (
<Container hasBoxShadow={true} width="350px" backgroundColor={ColorOption.white} borderRadius="3px">
<Flex direction="column" justify="flex-start">
<InstantHeading />
<OrderDetails />
<BuyButton />
</Flex>
</Container>
);

View File

@ -0,0 +1,36 @@
import { BigNumber } from '@0xproject/utils';
import * as React from 'react';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
import { State } from '../redux/reducer';
import { ColorOption } from '../style/theme';
import { Action, ActionTypes } from '../types';
import { AmountInput } from '../components/amount_input';
export interface SelectedAssetAmountInputProps {
fontColor?: ColorOption;
fontSize?: string;
}
interface ConnectedState {
value?: BigNumber;
}
interface ConnectedDispatch {
onChange?: (value?: BigNumber) => void;
}
const mapStateToProps = (state: State, _ownProps: SelectedAssetAmountInputProps): ConnectedState => ({
value: state.selectedAssetAmount,
});
const mapDispatchToProps = (dispatch: Dispatch<Action>): ConnectedDispatch => ({
onChange: value => dispatch({ type: ActionTypes.UPDATE_SELECTED_ASSET_AMOUNT, data: value }),
});
export const SelectedAssetAmountInput: React.ComponentClass<SelectedAssetAmountInputProps> = connect(
mapStateToProps,
mapDispatchToProps,
)(AmountInput);

View File

@ -0,0 +1,31 @@
import { BigNumber } from '@0xproject/utils';
import * as _ from 'lodash';
import { Action, ActionTypes } from '../types';
export interface State {
ethUsdPrice?: string;
selectedAssetAmount?: BigNumber;
}
export const INITIAL_STATE: State = {
ethUsdPrice: undefined,
selectedAssetAmount: undefined,
};
export const reducer = (state: State = INITIAL_STATE, action: Action): State => {
switch (action.type) {
case ActionTypes.UPDATE_ETH_USD_PRICE:
return {
...state,
ethUsdPrice: action.data,
};
case ActionTypes.UPDATE_SELECTED_ASSET_AMOUNT:
return {
...state,
selectedAssetAmount: action.data,
};
default:
return state;
}
};

View File

@ -0,0 +1,6 @@
import * as _ from 'lodash';
import { createStore, Store as ReduxStore } from 'redux';
import { reducer, State } from './reducer';
export const store: ReduxStore<State> = createStore(reducer);

View File

@ -0,0 +1,10 @@
import { injectGlobal } from './theme';
export const fonts = {
include: () => {
// Inject the inter-ui font into the page
return injectGlobal`
@import url('https://rsms.me/inter/inter-ui.css');
`;
},
};

View File

@ -0,0 +1,27 @@
import * as styledComponents from 'styled-components';
const { default: styled, css, injectGlobal, keyframes, ThemeProvider } = styledComponents;
export type Theme = { [key in ColorOption]: string };
export enum ColorOption {
primaryColor = 'primaryColor',
black = 'black',
lightGrey = 'lightGrey',
grey = 'grey',
feintGrey = 'feintGrey',
darkGrey = 'darkGrey',
white = 'white',
}
export const theme: Theme = {
primaryColor: '#512D80',
black: 'black',
lightGrey: '#999999',
grey: '#666666',
feintGrey: '#DEDEDE',
darkGrey: '#333333',
white: 'white',
};
export { styled, css, injectGlobal, keyframes, ThemeProvider };

View File

@ -0,0 +1,11 @@
import { ObjectMap } from '@0xproject/types';
import * as _ from 'lodash';
export const cssRuleIfExists = (props: ObjectMap<any>, rule: string): string => {
const camelCaseRule = _.camelCase(rule);
const ruleValueIfExists = props[camelCaseRule];
if (!_.isUndefined(ruleValueIfExists)) {
return `${rule}: ${ruleValueIfExists};`;
}
return '';
};

View File

@ -0,0 +1,9 @@
export enum ActionTypes {
UPDATE_ETH_USD_PRICE,
UPDATE_SELECTED_ASSET_AMOUNT,
}
export interface Action {
type: ActionTypes;
data?: any;
}

View File

@ -1,3 +1,7 @@
{
"extends": ["@0xproject/tslint-config"]
"extends": ["@0xproject/tslint-config"],
"rules": {
"custom-no-magic-numbers": false,
"semicolon": [true, "always", "ignore-bound-class-methods"]
}
}

View File

@ -0,0 +1,28 @@
export const eip712TypedDataSchema = {
id: '/eip712TypedData',
type: 'object',
properties: {
types: {
type: 'object',
properties: {
EIP712Domain: { type: 'array' },
},
additionalProperties: {
type: 'array',
items: {
type: 'object',
properties: {
name: { type: 'string' },
type: { type: 'string' },
},
required: ['name', 'type'],
},
},
required: ['EIP712Domain'],
},
primaryType: { type: 'string' },
domain: { type: 'object' },
message: { type: 'object' },
},
required: ['types', 'primaryType', 'domain', 'message'],
};

View File

@ -0,0 +1,10 @@
export const zeroExTransactionSchema = {
id: '/zeroExTransactionSchema',
properties: {
data: { $ref: '/hexSchema' },
signerAddress: { $ref: '/addressSchema' },
salt: { $ref: '/numberSchema' },
},
required: ['data', 'salt', 'signerAddress'],
type: 'object',
};

View File

@ -2,6 +2,7 @@ import { addressSchema, hexSchema, numberSchema } from '../schemas/basic_type_sc
import { blockParamSchema, blockRangeSchema } from '../schemas/block_range_schema';
import { callDataSchema } from '../schemas/call_data_schema';
import { ecSignatureParameterSchema, ecSignatureSchema } from '../schemas/ec_signature_schema';
import { eip712TypedDataSchema } from '../schemas/eip712_typed_data';
import { indexFilterValuesSchema } from '../schemas/index_filter_values_schema';
import { orderCancellationRequestsSchema } from '../schemas/order_cancel_schema';
import { orderFillOrKillRequestsSchema } from '../schemas/order_fill_or_kill_requests_schema';
@ -31,6 +32,7 @@ import { relayerApiOrdersSchema } from '../schemas/relayer_api_orders_schema';
import { signedOrdersSchema } from '../schemas/signed_orders_schema';
import { tokenSchema } from '../schemas/token_schema';
import { jsNumber, txDataSchema } from '../schemas/tx_data_schema';
import { zeroExTransactionSchema } from '../schemas/zero_ex_transaction_schema';
export const schemas = {
numberSchema,
@ -39,6 +41,7 @@ export const schemas = {
hexSchema,
ecSignatureParameterSchema,
ecSignatureSchema,
eip712TypedDataSchema,
indexFilterValuesSchema,
orderCancellationRequestsSchema,
orderFillOrKillRequestsSchema,
@ -68,4 +71,5 @@ export const schemas = {
relayerApiOrdersChannelUpdateSchema,
relayerApiOrdersResponseSchema,
relayerApiAssetDataPairsSchema,
zeroExTransactionSchema,
};

View File

@ -41,7 +41,7 @@
"@types/mocha": "^5.2.2",
"copyfiles": "^2.0.0",
"ethereum-types": "^1.0.11",
"ethers": "4.0.0-beta.14",
"ethers": "~4.0.4",
"lodash": "^4.17.5",
"run-s": "^0.0.0"
},

View File

@ -54,7 +54,7 @@
"@0xproject/web3-wrapper": "^3.0.3",
"@ledgerhq/hw-app-eth": "^4.3.0",
"ethereum-types": "^1.0.11",
"ethers": "4.0.0-beta.14",
"ethers": "~4.0.4",
"lodash": "^4.17.5"
},
"optionalDependencies": {

View File

@ -1,4 +1,22 @@
[
{
"version": "2.0.0",
"changes": [
{
"note":
"Added `ecSignOrderAsync` to first sign an order using `eth_signTypedData` and fallback to `eth_sign`.",
"pr": 1102
},
{
"note": "Added `ecSignTypedDataOrderAsync` to sign an order exclusively using `eth_signTypedData`.",
"pr": 1102
},
{
"note": "Rename `ecSignOrderHashAsync` to `ecSignHashAsync` removing `SignerType` parameter.",
"pr": 1102
}
]
},
{
"version": "1.0.7",
"changes": [

View File

@ -70,7 +70,7 @@
"ethereum-types": "^1.0.11",
"ethereumjs-abi": "0.6.5",
"ethereumjs-util": "^5.1.1",
"ethers": "4.0.0-beta.14",
"ethers": "~4.0.4",
"lodash": "^4.17.5"
},
"publishConfig": {

View File

@ -13,4 +13,39 @@ export const constants = {
BASE_16: 16,
INFINITE_TIMESTAMP_SEC: new BigNumber(2524604400), // Close to infinite
ZERO_AMOUNT: new BigNumber(0),
EIP712_DOMAIN_NAME: '0x Protocol',
EIP712_DOMAIN_VERSION: '2',
EIP712_DOMAIN_SCHEMA: {
name: 'EIP712Domain',
parameters: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'verifyingContract', type: 'address' },
],
},
EIP712_ORDER_SCHEMA: {
name: 'Order',
parameters: [
{ name: 'makerAddress', type: 'address' },
{ name: 'takerAddress', type: 'address' },
{ name: 'feeRecipientAddress', type: 'address' },
{ name: 'senderAddress', type: 'address' },
{ name: 'makerAssetAmount', type: 'uint256' },
{ name: 'takerAssetAmount', type: 'uint256' },
{ name: 'makerFee', type: 'uint256' },
{ name: 'takerFee', type: 'uint256' },
{ name: 'expirationTimeSeconds', type: 'uint256' },
{ name: 'salt', type: 'uint256' },
{ name: 'makerAssetData', type: 'bytes' },
{ name: 'takerAssetData', type: 'bytes' },
],
},
EIP712_ZEROEX_TRANSACTION_SCHEMA: {
name: 'ZeroExTransaction',
parameters: [
{ name: 'salt', type: 'uint256' },
{ name: 'signerAddress', type: 'address' },
{ name: 'data', type: 'bytes' },
],
},
};

View File

@ -1,109 +1,83 @@
import ethUtil = require('ethereumjs-util');
import { assert } from '@0xproject/assert';
import { schemas } from '@0xproject/json-schemas';
import { EIP712Object, EIP712TypedData, EIP712Types, Order, ZeroExTransaction } from '@0xproject/types';
import * as _ from 'lodash';
import { crypto } from './crypto';
import { EIP712Schema, EIP712Types } from './types';
const EIP191_PREFIX = '\x19\x01';
const EIP712_DOMAIN_NAME = '0x Protocol';
const EIP712_DOMAIN_VERSION = '2';
const EIP712_VALUE_LENGTH = 32;
const EIP712_DOMAIN_SCHEMA: EIP712Schema = {
name: 'EIP712Domain',
parameters: [
{ name: 'name', type: EIP712Types.String },
{ name: 'version', type: EIP712Types.String },
{ name: 'verifyingContract', type: EIP712Types.Address },
],
};
import { constants } from './constants';
export const eip712Utils = {
/**
* Compiles the EIP712Schema and returns the hash of the schema.
* @param schema The EIP712 schema.
* @return The hash of the compiled schema
* Creates a EIP712TypedData object specific to the 0x protocol for use with signTypedData.
* @param primaryType The primary type found in message
* @param types The additional types for the data in message
* @param message The contents of the message
* @param exchangeAddress The address of the exchange contract
* @return A typed data object
*/
compileSchema(schema: EIP712Schema): Buffer {
const eip712Schema = eip712Utils._encodeType(schema);
const eip712SchemaHashBuffer = crypto.solSHA3([eip712Schema]);
return eip712SchemaHashBuffer;
createTypedData: (
primaryType: string,
types: EIP712Types,
message: EIP712Object,
exchangeAddress: string,
): EIP712TypedData => {
assert.isETHAddressHex('exchangeAddress', exchangeAddress);
assert.isString('primaryType', primaryType);
const typedData = {
types: {
EIP712Domain: constants.EIP712_DOMAIN_SCHEMA.parameters,
...types,
},
/**
* Merges the EIP712 hash of a struct with the DomainSeparator for 0x v2.
* @param hashStruct the EIP712 hash of a struct
* @param contractAddress the exchange contract address
* @return The hash of an EIP712 message with domain separator prefixed
*/
createEIP712Message(hashStruct: Buffer, contractAddress: string): Buffer {
const domainSeparatorHashBuffer = eip712Utils._getDomainSeparatorHashBuffer(contractAddress);
const messageBuff = crypto.solSHA3([EIP191_PREFIX, domainSeparatorHashBuffer, hashStruct]);
return messageBuff;
},
/**
* Pad an address to 32 bytes
* @param address Address to pad
* @return padded address
*/
pad32Address(address: string): Buffer {
const addressBuffer = ethUtil.toBuffer(address);
const addressPadded = eip712Utils.pad32Buffer(addressBuffer);
return addressPadded;
},
/**
* Pad an buffer to 32 bytes
* @param buffer Address to pad
* @return padded buffer
*/
pad32Buffer(buffer: Buffer): Buffer {
const bufferPadded = ethUtil.setLengthLeft(buffer, EIP712_VALUE_LENGTH);
return bufferPadded;
},
/**
* Hash together a EIP712 schema with the corresponding data
* @param schema EIP712-compliant schema
* @param data Data the complies to the schema
* @return A buffer containing the SHA256 hash of the schema and encoded data
*/
structHash(schema: EIP712Schema, data: { [key: string]: any }): Buffer {
const encodedData = eip712Utils._encodeData(schema, data);
const schemaHash = eip712Utils.compileSchema(schema);
const hashBuffer = crypto.solSHA3([schemaHash, ...encodedData]);
return hashBuffer;
},
_getDomainSeparatorSchemaBuffer(): Buffer {
return eip712Utils.compileSchema(EIP712_DOMAIN_SCHEMA);
},
_getDomainSeparatorHashBuffer(exchangeAddress: string): Buffer {
const domainSeparatorSchemaBuffer = eip712Utils._getDomainSeparatorSchemaBuffer();
const encodedData = eip712Utils._encodeData(EIP712_DOMAIN_SCHEMA, {
name: EIP712_DOMAIN_NAME,
version: EIP712_DOMAIN_VERSION,
domain: {
name: constants.EIP712_DOMAIN_NAME,
version: constants.EIP712_DOMAIN_VERSION,
verifyingContract: exchangeAddress,
},
message,
primaryType,
};
assert.doesConformToSchema('typedData', typedData, schemas.eip712TypedDataSchema);
return typedData;
},
/**
* Creates an Order EIP712TypedData object for use with signTypedData.
* @param Order the order
* @return A typed data object
*/
createOrderTypedData: (order: Order): EIP712TypedData => {
assert.doesConformToSchema('order', order, schemas.orderSchema, [schemas.hexSchema]);
const normalizedOrder = _.mapValues(order, value => {
return !_.isString(value) ? value.toString() : value;
});
const domainSeparatorHashBuff2 = crypto.solSHA3([domainSeparatorSchemaBuffer, ...encodedData]);
return domainSeparatorHashBuff2;
const typedData = eip712Utils.createTypedData(
constants.EIP712_ORDER_SCHEMA.name,
{ Order: constants.EIP712_ORDER_SCHEMA.parameters },
normalizedOrder,
order.exchangeAddress,
);
return typedData;
},
_encodeType(schema: EIP712Schema): string {
const namedTypes = _.map(schema.parameters, ({ name, type }) => `${type} ${name}`);
const namedTypesJoined = namedTypes.join(',');
const encodedType = `${schema.name}(${namedTypesJoined})`;
return encodedType;
},
_encodeData(schema: EIP712Schema, data: { [key: string]: any }): any {
const encodedValues = [];
for (const parameter of schema.parameters) {
const value = data[parameter.name];
if (parameter.type === EIP712Types.String || parameter.type === EIP712Types.Bytes) {
encodedValues.push(crypto.solSHA3([ethUtil.toBuffer(value)]));
} else if (parameter.type === EIP712Types.Uint256) {
encodedValues.push(value);
} else if (parameter.type === EIP712Types.Address) {
encodedValues.push(eip712Utils.pad32Address(value));
} else {
throw new Error(`Unable to encode ${parameter.type}`);
}
}
return encodedValues;
/**
* Creates an ExecuteTransaction EIP712TypedData object for use with signTypedData and
* 0x Exchange executeTransaction.
* @param ZeroExTransaction the 0x transaction
* @param exchangeAddress The address of the exchange contract
* @return A typed data object
*/
createZeroExTransactionTypedData: (
zeroExTransaction: ZeroExTransaction,
exchangeAddress: string,
): EIP712TypedData => {
assert.isETHAddressHex('exchangeAddress', exchangeAddress);
assert.doesConformToSchema('zeroExTransaction', zeroExTransaction, schemas.zeroExTransactionSchema);
const normalizedTransaction = _.mapValues(zeroExTransaction, value => {
return !_.isString(value) ? value.toString() : value;
});
const typedData = eip712Utils.createTypedData(
constants.EIP712_ZEROEX_TRANSACTION_SCHEMA.name,
{ ZeroExTransaction: constants.EIP712_ZEROEX_TRANSACTION_SCHEMA.parameters },
normalizedTransaction,
exchangeAddress,
);
return typedData;
},
};

View File

@ -2,7 +2,6 @@ export { orderHashUtils } from './order_hash';
export { signatureUtils } from './signature_utils';
export { generatePseudoRandomSalt } from './salt';
export { assetDataUtils } from './asset_data_utils';
export { eip712Utils } from './eip712_utils';
export { marketUtils } from './market_utils';
export { rateUtils } from './rate_utils';
export { sortingUtils } from './sorting_utils';
@ -19,7 +18,17 @@ export { ExchangeTransferSimulator } from './exchange_transfer_simulator';
export { BalanceAndProxyAllowanceLazyStore } from './store/balance_and_proxy_allowance_lazy_store';
export { OrderFilledCancelledLazyStore } from './store/order_filled_cancelled_lazy_store';
export { Provider, JSONRPCRequestPayload, JSONRPCErrorCallback, JSONRPCResponsePayload } from 'ethereum-types';
export { constants } from './constants';
export { eip712Utils } from './eip712_utils';
export {
Provider,
JSONRPCRequestPayload,
JSONRPCErrorCallback,
JSONRPCResponsePayload,
JSONRPCResponseError,
} from 'ethereum-types';
export {
SignedOrder,
Order,
@ -29,17 +38,19 @@ export {
ERC20AssetData,
ERC721AssetData,
AssetProxyId,
SignerType,
SignatureType,
OrderStateValid,
OrderStateInvalid,
ExchangeContractErrs,
EIP712Parameter,
EIP712TypedData,
EIP712Types,
EIP712Object,
EIP712ObjectValue,
ZeroExTransaction,
} from '@0xproject/types';
export {
OrderError,
EIP712Parameter,
EIP712Schema,
EIP712Types,
TradeSide,
TransferType,
FindFeeOrdersThatCoverFeesForTargetOrdersOpts,

View File

@ -1,4 +1,4 @@
import { Order, SignedOrder, SignerType } from '@0xproject/types';
import { Order, SignedOrder } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import { Provider } from 'ethereum-types';
import * as _ from 'lodash';
@ -71,12 +71,7 @@ export const orderFactory = {
createOrderOpts,
);
const orderHash = orderHashUtils.getOrderHashHex(order);
const signature = await signatureUtils.ecSignOrderHashAsync(
provider,
orderHash,
makerAddress,
SignerType.Default,
);
const signature = await signatureUtils.ecSignHashAsync(provider, orderHash, makerAddress);
const signedOrder: SignedOrder = _.assign(order, { signature });
return signedOrder;
},

View File

@ -1,31 +1,13 @@
import { schemas, SchemaValidator } from '@0xproject/json-schemas';
import { Order, SignedOrder } from '@0xproject/types';
import { signTypedDataUtils } from '@0xproject/utils';
import * as _ from 'lodash';
import { assert } from './assert';
import { eip712Utils } from './eip712_utils';
import { EIP712Schema, EIP712Types } from './types';
const INVALID_TAKER_FORMAT = 'instance.takerAddress is not of a type(s) string';
const EIP712_ORDER_SCHEMA: EIP712Schema = {
name: 'Order',
parameters: [
{ name: 'makerAddress', type: EIP712Types.Address },
{ name: 'takerAddress', type: EIP712Types.Address },
{ name: 'feeRecipientAddress', type: EIP712Types.Address },
{ name: 'senderAddress', type: EIP712Types.Address },
{ name: 'makerAssetAmount', type: EIP712Types.Uint256 },
{ name: 'takerAssetAmount', type: EIP712Types.Uint256 },
{ name: 'makerFee', type: EIP712Types.Uint256 },
{ name: 'takerFee', type: EIP712Types.Uint256 },
{ name: 'expirationTimeSeconds', type: EIP712Types.Uint256 },
{ name: 'salt', type: EIP712Types.Uint256 },
{ name: 'makerAssetData', type: EIP712Types.Bytes },
{ name: 'takerAssetData', type: EIP712Types.Bytes },
],
};
export const orderHashUtils = {
/**
* Checks if the supplied hex encoded order hash is valid.
@ -45,7 +27,7 @@ export const orderHashUtils = {
/**
* Computes the orderHash for a supplied order.
* @param order An object that conforms to the Order or SignedOrder interface definitions.
* @return The resulting orderHash from hashing the supplied order.
* @return Hex encoded string orderHash from hashing the supplied order.
*/
getOrderHashHex(order: SignedOrder | Order): string {
try {
@ -64,16 +46,13 @@ export const orderHashUtils = {
return orderHashHex;
},
/**
* Computes the orderHash for a supplied order and returns it as a Buffer
* Computes the orderHash for a supplied order
* @param order An object that conforms to the Order or SignedOrder interface definitions.
* @return The resulting orderHash from hashing the supplied order as a Buffer
* @return A Buffer containing the resulting orderHash from hashing the supplied order
*/
getOrderHashBuffer(order: SignedOrder | Order): Buffer {
const orderParamsHashBuff = eip712Utils.structHash(EIP712_ORDER_SCHEMA, order);
const orderHashBuff = eip712Utils.createEIP712Message(orderParamsHashBuff, order.exchangeAddress);
const typedData = eip712Utils.createOrderTypedData(order);
const orderHashBuff = signTypedDataUtils.generateTypedDataHash(typedData);
return orderHashBuff;
},
_getOrderSchemaBuffer(): Buffer {
return eip712Utils.compileSchema(EIP712_ORDER_SCHEMA);
},
};

View File

@ -1,5 +1,5 @@
import { schemas } from '@0xproject/json-schemas';
import { ECSignature, SignatureType, SignerType, ValidatorSignature } from '@0xproject/types';
import { ECSignature, Order, SignatureType, SignedOrder, ValidatorSignature } from '@0xproject/types';
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import { Provider } from 'ethereum-types';
import * as ethUtil from 'ethereumjs-util';
@ -7,9 +7,11 @@ import * as _ from 'lodash';
import { artifacts } from './artifacts';
import { assert } from './assert';
import { eip712Utils } from './eip712_utils';
import { ExchangeContract } from './generated_contract_wrappers/exchange';
import { IValidatorContract } from './generated_contract_wrappers/i_validator';
import { IWalletContract } from './generated_contract_wrappers/i_wallet';
import { orderHashUtils } from './order_hash';
import { OrderError } from './types';
import { utils } from './utils';
@ -49,7 +51,7 @@ export const signatureUtils = {
case SignatureType.EthSign: {
const ecSignature = signatureUtils.parseECSignature(signature);
const prefixedMessageHex = signatureUtils.addSignedMessagePrefix(data, SignerType.Default);
const prefixedMessageHex = signatureUtils.addSignedMessagePrefix(data);
return signatureUtils.isValidECSignature(prefixedMessageHex, ecSignature, signerAddress);
}
@ -192,36 +194,90 @@ export const signatureUtils = {
}
},
/**
* Signs an orderHash and returns it's elliptic curve signature and signature type.
* This method currently supports TestRPC, Geth and Parity above and below V1.6.6
* @param orderHash Hex encoded orderHash to sign.
* Signs an order and returns a SignedOrder. First `eth_signTypedData` is requested
* then a fallback to `eth_sign` if not available on the supplied provider.
* @param order The Order to sign.
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
* must be available via the Provider supplied to 0x.js.
* @param signerType Different signers add/require different prefixes to be prepended to the message being signed.
* Since we cannot know ahead of time which signer you are using, you must supply a SignerType.
* @return A hex encoded string containing the Elliptic curve signature generated by signing the orderHash and the Signature Type.
* must be available via the supplied Provider.
* @return A SignedOrder containing the order and Elliptic curve signature with Signature Type.
*/
async ecSignOrderHashAsync(
provider: Provider,
orderHash: string,
signerAddress: string,
signerType: SignerType,
): Promise<string> {
async ecSignOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise<SignedOrder> {
assert.doesConformToSchema('order', order, schemas.orderSchema, [schemas.hexSchema]);
try {
const signedOrder = await signatureUtils.ecSignTypedDataOrderAsync(provider, order, signerAddress);
return signedOrder;
} catch (err) {
// HACK: We are unable to handle specific errors thrown since provider is not an object
// under our control. It could be Metamask Web3, Ethers, or any general RPC provider.
// We check for a user denying the signature request in a way that supports Metamask and
// Coinbase Wallet. Unfortunately for signers with a different error message,
// they will receive two signature requests.
if (err.message.includes('User denied message signature')) {
throw err;
}
const orderHash = orderHashUtils.getOrderHashHex(order);
const signatureHex = await signatureUtils.ecSignHashAsync(provider, orderHash, signerAddress);
const signedOrder = {
...order,
signature: signatureHex,
};
return signedOrder;
}
},
/**
* Signs an order using `eth_signTypedData` and returns a SignedOrder.
* @param order The Order to sign.
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
* must be available via the supplied Provider.
* @return A SignedOrder containing the order and Elliptic curve signature with Signature Type.
*/
async ecSignTypedDataOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise<SignedOrder> {
assert.isWeb3Provider('provider', provider);
assert.isHexString('orderHash', orderHash);
assert.isETHAddressHex('signerAddress', signerAddress);
assert.doesConformToSchema('order', order, schemas.orderSchema, [schemas.hexSchema]);
const web3Wrapper = new Web3Wrapper(provider);
await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper);
const normalizedSignerAddress = signerAddress.toLowerCase();
const typedData = eip712Utils.createOrderTypedData(order);
try {
const signature = await web3Wrapper.signTypedDataAsync(normalizedSignerAddress, typedData);
const ecSignatureRSV = parseSignatureHexAsRSV(signature);
const signatureBuffer = Buffer.concat([
ethUtil.toBuffer(ecSignatureRSV.v),
ethUtil.toBuffer(ecSignatureRSV.r),
ethUtil.toBuffer(ecSignatureRSV.s),
ethUtil.toBuffer(SignatureType.EIP712),
]);
const signatureHex = `0x${signatureBuffer.toString('hex')}`;
return {
...order,
signature: signatureHex,
};
} catch (err) {
// Detect if Metamask to transition users to the MetamaskSubprovider
if ((provider as any).isMetaMask) {
throw new Error(OrderError.InvalidMetamaskSigner);
} else {
throw err;
}
}
},
/**
* Signs a hash using `eth_sign` and returns its elliptic curve signature and signature type.
* @param msgHash Hex encoded message to sign.
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
* must be available via the supplied Provider.
* @return A hex encoded string containing the Elliptic curve signature generated by signing the msgHash and the Signature Type.
*/
async ecSignHashAsync(provider: Provider, msgHash: string, signerAddress: string): Promise<string> {
assert.isWeb3Provider('provider', provider);
assert.isHexString('msgHash', msgHash);
assert.isETHAddressHex('signerAddress', signerAddress);
const web3Wrapper = new Web3Wrapper(provider);
await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper);
const normalizedSignerAddress = signerAddress.toLowerCase();
let msgHashHex = orderHash;
const prefixedMsgHashHex = signatureUtils.addSignedMessagePrefix(orderHash, signerType);
// Metamask incorrectly implements eth_sign and does not prefix the message as per the spec
// Source: https://github.com/MetaMask/metamask-extension/commit/a9d36860bec424dcee8db043d3e7da6a5ff5672e
if (signerType === SignerType.Metamask) {
msgHashHex = prefixedMsgHashHex;
}
const signature = await web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHashHex);
const signature = await web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHash);
const prefixedMsgHashHex = signatureUtils.addSignedMessagePrefix(msgHash);
// HACK: There is no consensus on whether the signatureHex string should be formatted as
// v + r + s OR r + s + v, and different clients (even different versions of the same client)
@ -238,10 +294,7 @@ export const signatureUtils = {
normalizedSignerAddress,
);
if (isValidRSVSignature) {
const convertedSignatureHex = signatureUtils.convertECSignatureToSignatureHex(
ecSignatureRSV,
signerType,
);
const convertedSignatureHex = signatureUtils.convertECSignatureToSignatureHex(ecSignatureRSV);
return convertedSignatureHex;
}
}
@ -253,41 +306,30 @@ export const signatureUtils = {
normalizedSignerAddress,
);
if (isValidVRSSignature) {
const convertedSignatureHex = signatureUtils.convertECSignatureToSignatureHex(
ecSignatureVRS,
signerType,
);
const convertedSignatureHex = signatureUtils.convertECSignatureToSignatureHex(ecSignatureVRS);
return convertedSignatureHex;
}
}
// Detect if Metamask to transition users to the MetamaskSubprovider
if ((provider as any).isMetaMask) {
throw new Error(OrderError.InvalidMetamaskSigner);
} else {
throw new Error(OrderError.InvalidSignature);
}
},
/**
* Combines ECSignature with V,R,S and the relevant signature type for use in 0x protocol
* Combines ECSignature with V,R,S and the EthSign signature type for use in 0x protocol
* @param ecSignature The ECSignature of the signed data
* @param signerType The SignerType of the signed data
* @return Hex encoded string of signature (v,r,s) with Signature Type
*/
convertECSignatureToSignatureHex(ecSignature: ECSignature, signerType: SignerType): string {
convertECSignatureToSignatureHex(ecSignature: ECSignature): string {
const signatureBuffer = Buffer.concat([
ethUtil.toBuffer(ecSignature.v),
ethUtil.toBuffer(ecSignature.r),
ethUtil.toBuffer(ecSignature.s),
]);
const signatureHex = `0x${signatureBuffer.toString('hex')}`;
let signatureType;
switch (signerType) {
case SignerType.Metamask:
case SignerType.Ledger:
case SignerType.Default: {
signatureType = SignatureType.EthSign;
break;
}
default:
throw new Error(`Unrecognized SignerType: ${signerType}`);
}
const signatureWithType = signatureUtils.convertToSignatureWithType(signatureHex, signatureType);
const signatureWithType = signatureUtils.convertToSignatureWithType(signatureHex, SignatureType.EthSign);
return signatureWithType;
},
/**
@ -304,28 +346,17 @@ export const signatureUtils = {
/**
* Adds the relevant prefix to the message being signed.
* @param message Message to sign
* @param signerType The type of message prefix to add for a given SignerType. Different signers expect
* specific message prefixes.
* @return Prefixed message
*/
addSignedMessagePrefix(message: string, signerType: SignerType = SignerType.Default): string {
addSignedMessagePrefix(message: string): string {
assert.isString('message', message);
assert.doesBelongToStringEnum('signerType', signerType, SignerType);
switch (signerType) {
case SignerType.Metamask:
case SignerType.Ledger:
case SignerType.Default: {
const msgBuff = ethUtil.toBuffer(message);
const prefixedMsgBuff = ethUtil.hashPersonalMessage(msgBuff);
const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff);
return prefixedMsgHex;
}
default:
throw new Error(`Unrecognized SignerType: ${signerType}`);
}
},
/**
* Parse a 0x protocol hex-encoded signature string into it's ECSignature components
* Parse a 0x protocol hex-encoded signature string into its ECSignature components
* @param signature A hex encoded ecSignature 0x Protocol signature
* @return An ECSignature object with r,s,v parameters
*/

View File

@ -2,6 +2,7 @@ import { BigNumber } from '@0xproject/utils';
export enum OrderError {
InvalidSignature = 'INVALID_SIGNATURE',
InvalidMetamaskSigner = "MetaMask provider must be wrapped in a MetamaskSubprovider (from the '@0xproject/subproviders' package) in order to work with this method.",
}
export enum TradeSide {
@ -14,24 +15,6 @@ export enum TransferType {
Fee = 'fee',
}
export interface EIP712Parameter {
name: string;
type: EIP712Types;
}
export interface EIP712Schema {
name: string;
parameters: EIP712Parameter[];
}
export enum EIP712Types {
Address = 'address',
Bytes = 'bytes',
Bytes32 = 'bytes32',
String = 'string',
Uint256 = 'uint256',
}
export interface CreateOrderOpts {
takerAddress?: string;
senderAddress?: string;

View File

@ -0,0 +1,44 @@
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
import 'mocha';
import { constants } from '../src/constants';
import { eip712Utils } from '../src/eip712_utils';
import { chaiSetup } from './utils/chai_setup';
chaiSetup.configure();
const expect = chai.expect;
describe('EIP712 Utils', () => {
describe('createTypedData', () => {
it('adds in the EIP712DomainSeparator', () => {
const primaryType = 'Test';
const typedData = eip712Utils.createTypedData(
primaryType,
{ Test: [{ name: 'testValue', type: 'uint256' }] },
{ testValue: '1' },
constants.NULL_ADDRESS,
);
expect(typedData.domain).to.not.be.undefined();
expect(typedData.types.EIP712Domain).to.not.be.undefined();
const domainObject = typedData.domain;
expect(domainObject.name).to.eq(constants.EIP712_DOMAIN_NAME);
expect(typedData.primaryType).to.eq(primaryType);
});
});
describe('createTypedData', () => {
it('adds in the EIP712DomainSeparator', () => {
const typedData = eip712Utils.createZeroExTransactionTypedData(
{
salt: new BigNumber('0'),
data: constants.NULL_BYTES,
signerAddress: constants.NULL_ADDRESS,
},
constants.NULL_ADDRESS,
);
expect(typedData.primaryType).to.eq(constants.EIP712_ZEROEX_TRANSACTION_SCHEMA.name);
expect(typedData.types.EIP712Domain).to.not.be.undefined();
});
});
});

View File

@ -35,6 +35,20 @@ describe('Order hashing', () => {
const orderHash = orderHashUtils.getOrderHashHex(order);
expect(orderHash).to.be.equal(expectedOrderHash);
});
it('calculates the order hash if amounts are strings', async () => {
// It's common for developers using javascript to provide the amounts
// as strings. Since we eventually toString() the BigNumber
// before encoding we should result in the same orderHash in this scenario
// tslint:disable-next-line:no-unnecessary-type-assertion
const orderHash = orderHashUtils.getOrderHashHex({
...order,
makerAssetAmount: '0',
takerAssetAmount: '0',
makerFee: '0',
takerFee: '0',
} as any);
expect(orderHash).to.be.equal(expectedOrderHash);
});
it('throws a readable error message if taker format is invalid', async () => {
const orderWithInvalidtakerFormat = {
...order,

View File

@ -1,12 +1,13 @@
import { SignerType } from '@0xproject/types';
import { Order, SignatureType } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import * as chai from 'chai';
import { JSONRPCErrorCallback, JSONRPCRequestPayload } from 'ethereum-types';
import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';
import 'mocha';
import * as Sinon from 'sinon';
import { generatePseudoRandomSalt } from '../src';
import { generatePseudoRandomSalt, orderHashUtils } from '../src';
import { constants } from '../src/constants';
import { signatureUtils } from '../src/signature_utils';
import { chaiSetup } from './utils/chai_setup';
@ -16,6 +17,28 @@ chaiSetup.configure();
const expect = chai.expect;
describe('Signature utils', () => {
let makerAddress: string;
const fakeExchangeContractAddress = '0x1dc4c1cefef38a777b15aa20260a54e584b16c48';
let order: Order;
before(async () => {
const availableAddreses = await web3Wrapper.getAvailableAddressesAsync();
makerAddress = availableAddreses[0];
order = {
makerAddress,
takerAddress: constants.NULL_ADDRESS,
senderAddress: constants.NULL_ADDRESS,
feeRecipientAddress: constants.NULL_ADDRESS,
makerAssetData: constants.NULL_ADDRESS,
takerAssetData: constants.NULL_ADDRESS,
exchangeAddress: fakeExchangeContractAddress,
salt: new BigNumber(0),
makerFee: new BigNumber(0),
takerFee: new BigNumber(0),
makerAssetAmount: new BigNumber(0),
takerAssetAmount: new BigNumber(0),
expirationTimeSeconds: new BigNumber(0),
};
});
describe('#isValidSignatureAsync', () => {
let dataHex = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0';
const ethSignSignature =
@ -115,28 +138,64 @@ describe('Signature utils', () => {
expect(salt.lessThan(twoPow256)).to.be.true();
});
});
describe('#ecSignOrderHashAsync', () => {
let stubs: Sinon.SinonStub[] = [];
let makerAddress: string;
describe('#ecSignOrderAsync', () => {
it('should default to eth_sign if eth_signTypedData is unavailable', async () => {
const expectedSignature =
'0x1c3582f06356a1314dbf1c0e534c4d8e92e59b056ee607a7ff5a825f5f2cc5e6151c5cc7fdd420f5608e4d5bef108e42ad90c7a4b408caef32e24374cf387b0d7603';
const fakeProvider = {
async sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback): Promise<void> {
if (payload.method === 'eth_signTypedData') {
callback(new Error('Internal RPC Error'));
} else if (payload.method === 'eth_sign') {
const [address, message] = payload.params;
const signature = await web3Wrapper.signMessageAsync(address, message);
callback(null, {
id: 42,
jsonrpc: '2.0',
result: signature,
});
} else {
callback(null, { id: 42, jsonrpc: '2.0', result: [makerAddress] });
}
},
};
const signedOrder = await signatureUtils.ecSignOrderAsync(fakeProvider, order, makerAddress);
expect(signedOrder.signature).to.equal(expectedSignature);
});
it('should throw if the user denies the signing request', async () => {
const fakeProvider = {
async sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback): Promise<void> {
if (payload.method === 'eth_signTypedData') {
callback(new Error('User denied message signature'));
} else if (payload.method === 'eth_sign') {
const [address, message] = payload.params;
const signature = await web3Wrapper.signMessageAsync(address, message);
callback(null, {
id: 42,
jsonrpc: '2.0',
result: signature,
});
} else {
callback(null, { id: 42, jsonrpc: '2.0', result: [makerAddress] });
}
},
};
expect(signatureUtils.ecSignOrderAsync(fakeProvider, order, makerAddress)).to.to.be.rejectedWith(
'User denied message signature',
);
});
});
describe('#ecSignHashAsync', () => {
before(async () => {
const availableAddreses = await web3Wrapper.getAvailableAddressesAsync();
makerAddress = availableAddreses[0];
});
afterEach(() => {
// clean up any stubs after the test has completed
_.each(stubs, s => s.restore());
stubs = [];
});
it('Should return the correct Signature', async () => {
it('should return the correct Signature', async () => {
const orderHash = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0';
const expectedSignature =
'0x1b61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace225403';
const ecSignature = await signatureUtils.ecSignOrderHashAsync(
provider,
orderHash,
makerAddress,
SignerType.Default,
);
const ecSignature = await signatureUtils.ecSignHashAsync(provider, orderHash, makerAddress);
expect(ecSignature).to.equal(expectedSignature);
});
it('should return the correct Signature for signatureHex concatenated as R + S + V', async () => {
@ -162,12 +221,7 @@ describe('Signature utils', () => {
}
},
};
const ecSignature = await signatureUtils.ecSignOrderHashAsync(
fakeProvider,
orderHash,
makerAddress,
SignerType.Default,
);
const ecSignature = await signatureUtils.ecSignHashAsync(fakeProvider, orderHash, makerAddress);
expect(ecSignature).to.equal(expectedSignature);
});
it('should return the correct Signature for signatureHex concatenated as V + R + S', async () => {
@ -190,56 +244,12 @@ describe('Signature utils', () => {
},
};
const ecSignature = await signatureUtils.ecSignOrderHashAsync(
fakeProvider,
orderHash,
makerAddress,
SignerType.Default,
);
expect(ecSignature).to.equal(expectedSignature);
});
// Note this is due to a bug in Metamask where it does not prefix before signing, this is a known issue and is to be fixed in the future
// Source: https://github.com/MetaMask/metamask-extension/commit/a9d36860bec424dcee8db043d3e7da6a5ff5672e
it('should receive a payload modified with a prefix when Metamask is SignerType', async () => {
const orderHash = '0x34decbedc118904df65f379a175bb39ca18209d6ce41d5ed549d54e6e0a95004';
const orderHashPrefixed = '0xae70f31d26096291aa681b26cb7574563956221d0b4213631e1ef9df675d4cba';
const expectedSignature =
'0x1b117902c86dfb95fe0d1badd983ee166ad259b27acb220174cbb4460d872871137feabdfe76e05924b484789f79af4ee7fa29ec006cedce1bbf369320d034e10b03';
// Generated from a MM eth_sign request from 0x5409ed021d9299bf6814279a6a1411a7e866a631 signing 0xae70f31d26096291aa681b26cb7574563956221d0b4213631e1ef9df675d4cba
const metamaskSignature =
'0x117902c86dfb95fe0d1badd983ee166ad259b27acb220174cbb4460d872871137feabdfe76e05924b484789f79af4ee7fa29ec006cedce1bbf369320d034e10b1b';
const fakeProvider = {
async sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback): Promise<void> {
if (payload.method === 'eth_sign') {
const [, message] = payload.params;
expect(message).to.equal(orderHashPrefixed);
callback(null, {
id: 42,
jsonrpc: '2.0',
result: metamaskSignature,
});
} else {
callback(null, { id: 42, jsonrpc: '2.0', result: [makerAddress] });
}
},
};
const ecSignature = await signatureUtils.ecSignOrderHashAsync(
fakeProvider,
orderHash,
makerAddress,
SignerType.Metamask,
);
const ecSignature = await signatureUtils.ecSignHashAsync(fakeProvider, orderHash, makerAddress);
expect(ecSignature).to.equal(expectedSignature);
});
it('should return a valid signature', async () => {
const orderHash = '0x34decbedc118904df65f379a175bb39ca18209d6ce41d5ed549d54e6e0a95004';
const ecSignature = await signatureUtils.ecSignOrderHashAsync(
provider,
orderHash,
makerAddress,
SignerType.Default,
);
const ecSignature = await signatureUtils.ecSignHashAsync(provider, orderHash, makerAddress);
const isValidSignature = await signatureUtils.isValidSignatureAsync(
provider,
@ -250,44 +260,65 @@ describe('Signature utils', () => {
expect(isValidSignature).to.be.true();
});
});
describe('#ecSignTypedDataOrderAsync', () => {
it('should result in the same signature as signing the order hash without an ethereum message prefix', async () => {
// Note: Since order hash is an EIP712 hash the result of a valid EIP712 signature
// of order hash is the same as signing the order without the Ethereum Message prefix.
const orderHashHex = orderHashUtils.getOrderHashHex(order);
const sig = ethUtil.ecsign(
ethUtil.toBuffer(orderHashHex),
Buffer.from('F2F48EE19680706196E2E339E5DA3491186E0C4C5030670656B0E0164837257D', 'hex'),
);
const signatureBuffer = Buffer.concat([
ethUtil.toBuffer(sig.v),
ethUtil.toBuffer(sig.r),
ethUtil.toBuffer(sig.s),
ethUtil.toBuffer(SignatureType.EIP712),
]);
const signatureHex = `0x${signatureBuffer.toString('hex')}`;
const signedOrder = await signatureUtils.ecSignTypedDataOrderAsync(provider, order, makerAddress);
const isValidSignature = await signatureUtils.isValidSignatureAsync(
provider,
orderHashHex,
signedOrder.signature,
makerAddress,
);
expect(signatureHex).to.eq(signedOrder.signature);
expect(isValidSignature).to.eq(true);
});
it('should return the correct Signature for signatureHex concatenated as R + S + V', async () => {
const expectedSignature =
'0x1cd472c439833774b55d248c31b6585f21aea1b9363ebb4ec58549e46b62eb5a6f696f5781f62de008ee7f77650ef940d99c97ec1dee67b3f5cea1bbfdfeb2eba602';
const fakeProvider = {
async sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback): Promise<void> {
if (payload.method === 'eth_signTypedData') {
const [address, typedData] = payload.params;
const signature = await web3Wrapper.signTypedDataAsync(address, typedData);
callback(null, {
id: 42,
jsonrpc: '2.0',
result: signature,
});
} else {
callback(null, { id: 42, jsonrpc: '2.0', result: [makerAddress] });
}
},
};
const signedOrder = await signatureUtils.ecSignTypedDataOrderAsync(fakeProvider, order, makerAddress);
expect(signedOrder.signature).to.equal(expectedSignature);
});
});
describe('#convertECSignatureToSignatureHex', () => {
const ecSignature: ECSignature = {
v: 27,
r: '0xaca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d64393',
s: '0x46b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf2',
};
it('should concatenate v,r,s and append the EthSign signature type when SignerType is Default', async () => {
it('should concatenate v,r,s and append the EthSign signature type', async () => {
const expectedSignatureWithSignatureType =
'0x1baca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d6439346b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf203';
const signatureWithSignatureType = signatureUtils.convertECSignatureToSignatureHex(
ecSignature,
SignerType.Default,
);
const signatureWithSignatureType = signatureUtils.convertECSignatureToSignatureHex(ecSignature);
expect(signatureWithSignatureType).to.equal(expectedSignatureWithSignatureType);
});
it('should concatenate v,r,s and append the EthSign signature type when SignerType is Ledger', async () => {
const expectedSignatureWithSignatureType =
'0x1baca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d6439346b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf203';
const signatureWithSignatureType = signatureUtils.convertECSignatureToSignatureHex(
ecSignature,
SignerType.Ledger,
);
expect(signatureWithSignatureType).to.equal(expectedSignatureWithSignatureType);
});
it('should concatenate v,r,s and append the EthSign signature type when SignerType is Metamask', async () => {
const expectedSignatureWithSignatureType =
'0x1baca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d6439346b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf203';
const signatureWithSignatureType = signatureUtils.convertECSignatureToSignatureHex(
ecSignature,
SignerType.Metamask,
);
expect(signatureWithSignatureType).to.equal(expectedSignatureWithSignatureType);
});
it('should throw if the SignerType is invalid', async () => {
const expectedMessage = 'Unrecognized SignerType: INVALID_SIGNER';
expect(() =>
signatureUtils.convertECSignatureToSignatureHex(ecSignature, 'INVALID_SIGNER' as SignerType),
).to.throw(expectedMessage);
});
});
});

View File

@ -82,7 +82,7 @@
"bintrees": "^1.0.2",
"ethereum-types": "^1.0.11",
"ethereumjs-blockstream": "6.0.0",
"ethers": "4.0.0-beta.14",
"ethers": "~4.0.4",
"lodash": "^4.17.5"
},
"publishConfig": {

View File

@ -13,4 +13,10 @@ export {
export { OnOrderStateChangeCallback, OrderWatcherConfig } from './types';
export { SignedOrder } from '@0xproject/types';
export { JSONRPCRequestPayload, JSONRPCErrorCallback, Provider, JSONRPCResponsePayload } from 'ethereum-types';
export {
JSONRPCRequestPayload,
JSONRPCErrorCallback,
Provider,
JSONRPCResponsePayload,
JSONRPCResponseError,
} from 'ethereum-types';

View File

@ -8,7 +8,13 @@ export { ProfilerSubprovider } from './profiler_subprovider';
export { RevertTraceSubprovider } from './revert_trace_subprovider';
export { ContractData } from './types';
export { JSONRPCRequestPayload, Provider, JSONRPCErrorCallback, JSONRPCResponsePayload } from 'ethereum-types';
export {
JSONRPCRequestPayload,
Provider,
JSONRPCErrorCallback,
JSONRPCResponsePayload,
JSONRPCResponseError,
} from 'ethereum-types';
export {
JSONRPCRequestPayloadWithMethod,

View File

@ -1,4 +1,17 @@
[
{
"version": "2.1.0",
"changes": [
{
"note": "Add `MetamaskSubprovider` to handle inconsistent JSON RPC behaviour",
"pr": 1102
},
{
"note": "Add support for `eth_signTypedData` in wallets Mnemonic, Private and EthLightWallet",
"pr": 1102
}
]
},
{
"version": "2.0.7",
"changes": [

View File

@ -45,7 +45,7 @@
"ethereum-types": "^1.0.11",
"ethereumjs-tx": "^1.3.5",
"ethereumjs-util": "^5.1.1",
"ganache-core": "0xProject/ganache-core#monorepo-dep",
"ganache-core": "^2.2.1",
"hdkey": "^0.7.1",
"json-rpc-error": "2.0.0",
"lodash": "^4.17.5",

View File

@ -27,6 +27,7 @@ export { Subprovider } from './subproviders/subprovider';
export { NonceTrackerSubprovider } from './subproviders/nonce_tracker';
export { PrivateKeyWalletSubprovider } from './subproviders/private_key_wallet';
export { MnemonicWalletSubprovider } from './subproviders/mnemonic_wallet';
export { MetamaskSubprovider } from './subproviders/metamask_subprovider';
export { EthLightwalletSubprovider } from './subproviders/eth_lightwallet_subprovider';
export {
@ -47,6 +48,19 @@ export {
LedgerGetAddressResult,
} from './types';
export { ECSignature } from '@0xproject/types';
export {
ECSignature,
EIP712Object,
EIP712ObjectValue,
EIP712TypedData,
EIP712Types,
EIP712Parameter,
} from '@0xproject/types';
export { JSONRPCRequestPayload, Provider, JSONRPCResponsePayload, JSONRPCErrorCallback } from 'ethereum-types';
export {
JSONRPCRequestPayload,
Provider,
JSONRPCResponsePayload,
JSONRPCErrorCallback,
JSONRPCResponseError,
} from 'ethereum-types';

View File

@ -23,6 +23,7 @@ export abstract class BaseWalletSubprovider extends Subprovider {
public abstract async getAccountsAsync(): Promise<string[]>;
public abstract async signTransactionAsync(txParams: PartialTxParams): Promise<string>;
public abstract async signPersonalMessageAsync(data: string, address: string): Promise<string>;
public abstract async signTypedDataAsync(address: string, typedData: any): Promise<string>;
/**
* This method conforms to the web3-provider-engine interface.
@ -36,6 +37,8 @@ export abstract class BaseWalletSubprovider extends Subprovider {
public async handleRequest(payload: JSONRPCRequestPayload, next: Callback, end: ErrorCallback): Promise<void> {
let accounts;
let txParams;
let address;
let typedData;
switch (payload.method) {
case 'eth_coinbase':
try {
@ -86,7 +89,7 @@ export abstract class BaseWalletSubprovider extends Subprovider {
case 'eth_sign':
case 'personal_sign':
const data = payload.method === 'eth_sign' ? payload.params[1] : payload.params[0];
const address = payload.method === 'eth_sign' ? payload.params[0] : payload.params[1];
address = payload.method === 'eth_sign' ? payload.params[0] : payload.params[1];
try {
const ecSignatureHex = await this.signPersonalMessageAsync(data, address);
end(null, ecSignatureHex);
@ -94,6 +97,15 @@ export abstract class BaseWalletSubprovider extends Subprovider {
end(err);
}
return;
case 'eth_signTypedData':
[address, typedData] = payload.params;
try {
const signature = await this.signTypedDataAsync(address, typedData);
end(null, signature);
} catch (err) {
end(err);
}
return;
default:
next();

View File

@ -1,3 +1,4 @@
import { EIP712TypedData } from '@0xproject/types';
import * as lightwallet from 'eth-lightwallet';
import { PartialTxParams } from '../types';
@ -48,16 +49,16 @@ export class EthLightwalletSubprovider extends BaseWalletSubprovider {
// Lightwallet loses the chain id information when hex encoding the transaction
// this results in a different signature on certain networks. PrivateKeyWallet
// respects this as it uses the parameters passed in
let privKey = this._keystore.exportPrivateKey(txParams.from, this._pwDerivedKey);
const privKeyWallet = new PrivateKeyWalletSubprovider(privKey);
privKey = '';
const privKeySignature = await privKeyWallet.signTransactionAsync(txParams);
return privKeySignature;
let privateKey = this._keystore.exportPrivateKey(txParams.from, this._pwDerivedKey);
const privateKeyWallet = new PrivateKeyWalletSubprovider(privateKey);
privateKey = '';
const privateKeySignature = await privateKeyWallet.signTransactionAsync(txParams);
return privateKeySignature;
}
/**
* Sign a personal Ethereum signed message. The signing account will be the account
* associated with the provided address.
* If you've added the MnemonicWalletSubprovider to your app's provider, you can simply send an `eth_sign`
* If you've added this Subprovider to your app's provider, you can simply send an `eth_sign`
* or `personal_sign` JSON RPC request, and this method will be called auto-magically.
* If you are not using this via a ProviderEngine instance, you can call it directly.
* @param data Hex string message to sign
@ -65,10 +66,26 @@ export class EthLightwalletSubprovider extends BaseWalletSubprovider {
* @return Signature hex string (order: rsv)
*/
public async signPersonalMessageAsync(data: string, address: string): Promise<string> {
let privKey = this._keystore.exportPrivateKey(address, this._pwDerivedKey);
const privKeyWallet = new PrivateKeyWalletSubprovider(privKey);
privKey = '';
const result = privKeyWallet.signPersonalMessageAsync(data, address);
let privateKey = this._keystore.exportPrivateKey(address, this._pwDerivedKey);
const privateKeyWallet = new PrivateKeyWalletSubprovider(privateKey);
privateKey = '';
const result = privateKeyWallet.signPersonalMessageAsync(data, address);
return result;
}
/**
* Sign an EIP712 Typed Data message. The signing address will associated with the provided address.
* If you've added this Subprovider to your app's provider, you can simply send an `eth_signTypedData`
* JSON RPC request, and this method will be called auto-magically.
* If you are not using this via a ProviderEngine instance, you can call it directly.
* @param address Address of the account to sign with
* @param data the typed data object
* @return Signature hex string (order: rsv)
*/
public async signTypedDataAsync(address: string, typedData: EIP712TypedData): Promise<string> {
let privateKey = this._keystore.exportPrivateKey(address, this._pwDerivedKey);
const privateKeyWallet = new PrivateKeyWalletSubprovider(privateKey);
privateKey = '';
const result = privateKeyWallet.signTypedDataAsync(address, typedData);
return result;
}
}

View File

@ -187,6 +187,16 @@ export class LedgerSubprovider extends BaseWalletSubprovider {
throw err;
}
}
/**
* eth_signTypedData is currently not supported on Ledger devices.
* @param address Address of the account to sign with
* @param data the typed data object
* @return Signature hex string (order: rsv)
*/
// tslint:disable-next-line:prefer-function-over-method
public async signTypedDataAsync(address: string, typedData: any): Promise<string> {
throw new Error(WalletSubproviderErrors.MethodNotSupported);
}
private async _createLedgerClientAsync(): Promise<LedgerEthereumClient> {
await this._connectionLock.acquire();
if (!_.isUndefined(this._ledgerClientIfExists)) {

View File

@ -0,0 +1,126 @@
import { marshaller, Web3Wrapper } from '@0xproject/web3-wrapper';
import { JSONRPCRequestPayload, Provider } from 'ethereum-types';
import * as ethUtil from 'ethereumjs-util';
import { Callback, ErrorCallback } from '../types';
import { Subprovider } from './subprovider';
/**
* This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine)
* subprovider interface and the provider sendAsync interface.
* It handles inconsistencies with Metamask implementations of various JSON RPC methods.
* It forwards JSON RPC requests involving the domain of a signer (getAccounts,
* sendTransaction, signMessage etc...) to the provider instance supplied at instantiation. All other requests
* are passed onwards for subsequent subproviders to handle.
*/
export class MetamaskSubprovider extends Subprovider {
private readonly _web3Wrapper: Web3Wrapper;
private readonly _provider: Provider;
/**
* Instantiates a new MetamaskSubprovider
* @param provider Web3 provider that should handle all user account related requests
*/
constructor(provider: Provider) {
super();
this._web3Wrapper = new Web3Wrapper(provider);
this._provider = provider;
}
/**
* This method conforms to the web3-provider-engine interface.
* It is called internally by the ProviderEngine when it is this subproviders
* turn to handle a JSON RPC request.
* @param payload JSON RPC payload
* @param next Callback to call if this subprovider decides not to handle the request
* @param end Callback to call if subprovider handled the request and wants to pass back the request.
*/
// tslint:disable-next-line:prefer-function-over-method async-suffix
public async handleRequest(payload: JSONRPCRequestPayload, next: Callback, end: ErrorCallback): Promise<void> {
let message;
let address;
switch (payload.method) {
case 'web3_clientVersion':
try {
const nodeVersion = await this._web3Wrapper.getNodeVersionAsync();
end(null, nodeVersion);
} catch (err) {
end(err);
}
return;
case 'eth_accounts':
try {
const accounts = await this._web3Wrapper.getAvailableAddressesAsync();
end(null, accounts);
} catch (err) {
end(err);
}
return;
case 'eth_sendTransaction':
const [txParams] = payload.params;
try {
const txData = marshaller.unmarshalTxData(txParams);
const txHash = await this._web3Wrapper.sendTransactionAsync(txData);
end(null, txHash);
} catch (err) {
end(err);
}
return;
case 'eth_sign':
[address, message] = payload.params;
try {
// Metamask incorrectly implements eth_sign and does not prefix the message as per the spec
// Source: https://github.com/MetaMask/metamask-extension/commit/a9d36860bec424dcee8db043d3e7da6a5ff5672e
const msgBuff = ethUtil.toBuffer(message);
const prefixedMsgBuff = ethUtil.hashPersonalMessage(msgBuff);
const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff);
const signature = await this._web3Wrapper.signMessageAsync(address, prefixedMsgHex);
signature ? end(null, signature) : end(new Error('Error performing eth_sign'), null);
} catch (err) {
end(err);
}
return;
case 'eth_signTypedData':
case 'eth_signTypedData_v3':
[address, message] = payload.params;
try {
// Metamask supports multiple versions and has namespaced signTypedData to v3 for an indeterminate period of time.
// eth_signTypedData is mapped to an older implementation before the spec was finalized.
// Source: https://github.com/MetaMask/metamask-extension/blob/c49d854b55b3efd34c7fd0414b76f7feaa2eec7c/app/scripts/metamask-controller.js#L1262
// and expects message to be serialised as JSON
const messageJSON = JSON.stringify(message);
const signature = await this._web3Wrapper.sendRawPayloadAsync<string>({
method: 'eth_signTypedData_v3',
params: [address, messageJSON],
});
signature ? end(null, signature) : end(new Error('Error performing eth_signTypedData'), null);
} catch (err) {
end(err);
}
return;
default:
next();
return;
}
}
/**
* This method conforms to the provider sendAsync interface.
* Allowing the MetamaskSubprovider to be used as a generic provider (outside of Web3ProviderEngine) with the
* addition of wrapping the inconsistent Metamask behaviour
* @param payload JSON RPC payload
* @return The contents nested under the result key of the response body
*/
public sendAsync(payload: JSONRPCRequestPayload, callback: ErrorCallback): void {
void this.handleRequest(
payload,
// handleRequest has decided to not handle this, so fall through to the provider
() => {
const sendAsync = this._provider.sendAsync.bind(this._provider);
sendAsync(payload, callback);
},
// handleRequest has called end and will handle this
(err, data) => {
err ? callback(err) : callback(null, { ...payload, result: data });
},
);
}
}

View File

@ -1,4 +1,5 @@
import { assert } from '@0xproject/assert';
import { EIP712TypedData } from '@0xproject/types';
import { addressUtils } from '@0xproject/utils';
import * as bip39 from 'bip39';
import HDNode = require('hdkey');
@ -90,10 +91,10 @@ export class MnemonicWalletSubprovider extends BaseWalletSubprovider {
}
/**
* Sign a personal Ethereum signed message. The signing account will be the account
* associated with the provided address.
* If you've added the MnemonicWalletSubprovider to your app's provider, you can simply send an `eth_sign`
* or `personal_sign` JSON RPC request, and this method will be called auto-magically.
* If you are not using this via a ProviderEngine instance, you can call it directly.
* associated with the provided address. If you've added the MnemonicWalletSubprovider to
* your app's provider, you can simply send an `eth_sign` or `personal_sign` JSON RPC request,
* and this method will be called auto-magically. If you are not using this via a ProviderEngine
* instance, you can call it directly.
* @param data Hex string message to sign
* @param address Address of the account to sign with
* @return Signature hex string (order: rsv)
@ -108,6 +109,25 @@ export class MnemonicWalletSubprovider extends BaseWalletSubprovider {
const sig = await privateKeyWallet.signPersonalMessageAsync(data, address);
return sig;
}
/**
* Sign an EIP712 Typed Data message. The signing account will be the account
* associated with the provided address. If you've added this MnemonicWalletSubprovider to
* your app's provider, you can simply send an `eth_signTypedData` JSON RPC request, and
* this method will be called auto-magically. If you are not using this via a ProviderEngine
* instance, you can call it directly.
* @param address Address of the account to sign with
* @param data the typed data object
* @return Signature hex string (order: rsv)
*/
public async signTypedDataAsync(address: string, typedData: EIP712TypedData): Promise<string> {
if (_.isUndefined(typedData)) {
throw new Error(WalletSubproviderErrors.DataMissingForSignPersonalMessage);
}
assert.isETHAddressHex('address', address);
const privateKeyWallet = this._privateKeyWalletForAddress(address);
const sig = await privateKeyWallet.signTypedDataAsync(address, typedData);
return sig;
}
private _privateKeyWalletForAddress(address: string): PrivateKeyWalletSubprovider {
const derivedKeyInfo = this._findDerivedKeyInfoForAddress(address);
const privateKeyHex = derivedKeyInfo.hdKey.privateKey.toString('hex');

View File

@ -1,4 +1,6 @@
import { assert } from '@0xproject/assert';
import { EIP712TypedData } from '@0xproject/types';
import { signTypedDataUtils } from '@0xproject/utils';
import EthereumTx = require('ethereumjs-tx');
import * as ethUtil from 'ethereumjs-util';
import * as _ from 'lodash';
@ -23,7 +25,7 @@ export class PrivateKeyWalletSubprovider extends BaseWalletSubprovider {
constructor(privateKey: string) {
assert.isString('privateKey', privateKey);
super();
this._privateKeyBuffer = new Buffer(privateKey, 'hex');
this._privateKeyBuffer = Buffer.from(privateKey, 'hex');
this._address = `0x${ethUtil.privateToAddress(this._privateKeyBuffer).toString('hex')}`;
}
/**
@ -84,4 +86,29 @@ export class PrivateKeyWalletSubprovider extends BaseWalletSubprovider {
const rpcSig = ethUtil.toRpcSig(sig.v, sig.r, sig.s);
return rpcSig;
}
/**
* Sign an EIP712 Typed Data message. The signing address will be calculated from the private key.
* The address must be provided it must match the address calculated from the private key.
* If you've added this Subprovider to your app's provider, you can simply send an `eth_signTypedData`
* JSON RPC request, and this method will be called auto-magically.
* If you are not using this via a ProviderEngine instance, you can call it directly.
* @param address Address of the account to sign with
* @param data the typed data object
* @return Signature hex string (order: rsv)
*/
public async signTypedDataAsync(address: string, typedData: EIP712TypedData): Promise<string> {
if (_.isUndefined(typedData)) {
throw new Error(WalletSubproviderErrors.DataMissingForSignTypedData);
}
assert.isETHAddressHex('address', address);
if (address !== this._address) {
throw new Error(
`Requested to sign message with address: ${address}, instantiated with address: ${this._address}`,
);
}
const dataBuff = signTypedDataUtils.generateTypedDataHash(typedData);
const sig = ethUtil.ecsign(dataBuff, this._privateKeyBuffer);
const rpcSig = ethUtil.toRpcSig(sig.v, sig.r, sig.s);
return rpcSig;
}
}

Some files were not shown because too many files have changed in this diff Show More