Throw and handle errors from Providers.
In web3 wrapper when a response contains an error field we throw this rather than return response.result which is often undefined. In Signature Utils we handle the error thrown when a user rejects the signing dialogue to prevent double signing. Exposed the ZeroExTransaction JSON schema. In Website only use the MetamaskSubprovider if we can detect the provider is Metamask
This commit is contained in:
parent
e1236a4846
commit
9e8031d5e3
@ -7,7 +7,8 @@
|
|||||||
"pr": 1102
|
"pr": 1102
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"note": "Added `MetamaskSubprovider` to handle inconsistencies with signing.",
|
"note":
|
||||||
|
"Added `MetamaskSubprovider` to handle inconsistencies in Metamask's signing JSON RPC endpoints.",
|
||||||
"pr": 1102
|
"pr": 1102
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -90,6 +90,7 @@ export {
|
|||||||
JSONRPCRequestPayload,
|
JSONRPCRequestPayload,
|
||||||
JSONRPCResponsePayload,
|
JSONRPCResponsePayload,
|
||||||
JSONRPCErrorCallback,
|
JSONRPCErrorCallback,
|
||||||
|
JSONRPCResponseError,
|
||||||
LogEntry,
|
LogEntry,
|
||||||
DecodedLogArgs,
|
DecodedLogArgs,
|
||||||
LogEntryEvent,
|
LogEntryEvent,
|
||||||
|
@ -33,7 +33,7 @@ export class TransactionEncoder {
|
|||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
const typedData = eip712Utils.createZeroExTransactionTypedData(executeTransactionData, exchangeAddress);
|
const typedData = eip712Utils.createZeroExTransactionTypedData(executeTransactionData, exchangeAddress);
|
||||||
const eip712MessageBuffer = signTypedDataUtils.signTypedDataHash(typedData);
|
const eip712MessageBuffer = signTypedDataUtils.generateTypedDataHash(typedData);
|
||||||
const messageHex = `0x${eip712MessageBuffer.toString('hex')}`;
|
const messageHex = `0x${eip712MessageBuffer.toString('hex')}`;
|
||||||
return messageHex;
|
return messageHex;
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ export class TransactionFactory {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const typedData = eip712Utils.createZeroExTransactionTypedData(executeTransactionData, this._exchangeAddress);
|
const typedData = eip712Utils.createZeroExTransactionTypedData(executeTransactionData, this._exchangeAddress);
|
||||||
const eip712MessageBuffer = signTypedDataUtils.signTypedDataHash(typedData);
|
const eip712MessageBuffer = signTypedDataUtils.generateTypedDataHash(typedData);
|
||||||
const signature = signingUtils.signMessage(eip712MessageBuffer, this._privateKey, signatureType);
|
const signature = signingUtils.signMessage(eip712MessageBuffer, this._privateKey, signatureType);
|
||||||
const signedTx = {
|
const signedTx = {
|
||||||
exchangeAddress: this._exchangeAddress,
|
exchangeAddress: this._exchangeAddress,
|
||||||
|
@ -1,4 +1,13 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "1.1.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Add `JSONRPCResponseError` and error field on `JSONRPCResponsePayload`.",
|
||||||
|
"pr": 1102
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"timestamp": 1538693146,
|
"timestamp": 1538693146,
|
||||||
"version": "1.0.11",
|
"version": "1.0.11",
|
||||||
|
@ -113,10 +113,16 @@ export interface JSONRPCRequestPayload {
|
|||||||
jsonrpc: string;
|
jsonrpc: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface JSONRPCResponseError {
|
||||||
|
message: string;
|
||||||
|
code: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface JSONRPCResponsePayload {
|
export interface JSONRPCResponsePayload {
|
||||||
result: any;
|
result: any;
|
||||||
id: number;
|
id: number;
|
||||||
jsonrpc: string;
|
jsonrpc: string;
|
||||||
|
error?: JSONRPCResponseError;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AbstractBlock {
|
export interface AbstractBlock {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export const eip712TypedData = {
|
export const eip712TypedDataSchema = {
|
||||||
id: '/eip712TypedData',
|
id: '/eip712TypedData',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: {
|
properties: {
|
||||||
|
10
packages/json-schemas/schemas/zero_ex_transaction_schema.ts
Normal file
10
packages/json-schemas/schemas/zero_ex_transaction_schema.ts
Normal 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',
|
||||||
|
};
|
@ -2,7 +2,7 @@ import { addressSchema, hexSchema, numberSchema } from '../schemas/basic_type_sc
|
|||||||
import { blockParamSchema, blockRangeSchema } from '../schemas/block_range_schema';
|
import { blockParamSchema, blockRangeSchema } from '../schemas/block_range_schema';
|
||||||
import { callDataSchema } from '../schemas/call_data_schema';
|
import { callDataSchema } from '../schemas/call_data_schema';
|
||||||
import { ecSignatureParameterSchema, ecSignatureSchema } from '../schemas/ec_signature_schema';
|
import { ecSignatureParameterSchema, ecSignatureSchema } from '../schemas/ec_signature_schema';
|
||||||
import { eip712TypedData } from '../schemas/eip712_typed_data';
|
import { eip712TypedDataSchema } from '../schemas/eip712_typed_data';
|
||||||
import { indexFilterValuesSchema } from '../schemas/index_filter_values_schema';
|
import { indexFilterValuesSchema } from '../schemas/index_filter_values_schema';
|
||||||
import { orderCancellationRequestsSchema } from '../schemas/order_cancel_schema';
|
import { orderCancellationRequestsSchema } from '../schemas/order_cancel_schema';
|
||||||
import { orderFillOrKillRequestsSchema } from '../schemas/order_fill_or_kill_requests_schema';
|
import { orderFillOrKillRequestsSchema } from '../schemas/order_fill_or_kill_requests_schema';
|
||||||
@ -32,6 +32,7 @@ import { relayerApiOrdersSchema } from '../schemas/relayer_api_orders_schema';
|
|||||||
import { signedOrdersSchema } from '../schemas/signed_orders_schema';
|
import { signedOrdersSchema } from '../schemas/signed_orders_schema';
|
||||||
import { tokenSchema } from '../schemas/token_schema';
|
import { tokenSchema } from '../schemas/token_schema';
|
||||||
import { jsNumber, txDataSchema } from '../schemas/tx_data_schema';
|
import { jsNumber, txDataSchema } from '../schemas/tx_data_schema';
|
||||||
|
import { zeroExTransactionSchema } from '../schemas/zero_ex_transaction_schema';
|
||||||
|
|
||||||
export const schemas = {
|
export const schemas = {
|
||||||
numberSchema,
|
numberSchema,
|
||||||
@ -40,7 +41,7 @@ export const schemas = {
|
|||||||
hexSchema,
|
hexSchema,
|
||||||
ecSignatureParameterSchema,
|
ecSignatureParameterSchema,
|
||||||
ecSignatureSchema,
|
ecSignatureSchema,
|
||||||
eip712TypedData,
|
eip712TypedDataSchema,
|
||||||
indexFilterValuesSchema,
|
indexFilterValuesSchema,
|
||||||
orderCancellationRequestsSchema,
|
orderCancellationRequestsSchema,
|
||||||
orderFillOrKillRequestsSchema,
|
orderFillOrKillRequestsSchema,
|
||||||
@ -70,4 +71,5 @@ export const schemas = {
|
|||||||
relayerApiOrdersChannelUpdateSchema,
|
relayerApiOrdersChannelUpdateSchema,
|
||||||
relayerApiOrdersResponseSchema,
|
relayerApiOrdersResponseSchema,
|
||||||
relayerApiAssetDataPairsSchema,
|
relayerApiAssetDataPairsSchema,
|
||||||
|
zeroExTransactionSchema,
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { assert } from '@0xproject/assert';
|
||||||
|
import { schemas } from '@0xproject/json-schemas';
|
||||||
import { EIP712Object, EIP712TypedData, EIP712Types, Order, ZeroExTransaction } from '@0xproject/types';
|
import { EIP712Object, EIP712TypedData, EIP712Types, Order, ZeroExTransaction } from '@0xproject/types';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
@ -18,6 +20,8 @@ export const eip712Utils = {
|
|||||||
message: EIP712Object,
|
message: EIP712Object,
|
||||||
exchangeAddress: string,
|
exchangeAddress: string,
|
||||||
): EIP712TypedData => {
|
): EIP712TypedData => {
|
||||||
|
assert.isETHAddressHex('exchangeAddress', exchangeAddress);
|
||||||
|
assert.isString('primaryType', primaryType);
|
||||||
const typedData = {
|
const typedData = {
|
||||||
types: {
|
types: {
|
||||||
EIP712Domain: constants.EIP712_DOMAIN_SCHEMA.parameters,
|
EIP712Domain: constants.EIP712_DOMAIN_SCHEMA.parameters,
|
||||||
@ -31,6 +35,7 @@ export const eip712Utils = {
|
|||||||
message,
|
message,
|
||||||
primaryType,
|
primaryType,
|
||||||
};
|
};
|
||||||
|
assert.doesConformToSchema('typedData', typedData, schemas.eip712TypedDataSchema);
|
||||||
return typedData;
|
return typedData;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@ -39,6 +44,7 @@ export const eip712Utils = {
|
|||||||
* @return A typed data object
|
* @return A typed data object
|
||||||
*/
|
*/
|
||||||
createOrderTypedData: (order: Order): EIP712TypedData => {
|
createOrderTypedData: (order: Order): EIP712TypedData => {
|
||||||
|
assert.doesConformToSchema('order', order, schemas.orderSchema, [schemas.hexSchema]);
|
||||||
const normalizedOrder = _.mapValues(order, value => {
|
const normalizedOrder = _.mapValues(order, value => {
|
||||||
return !_.isString(value) ? value.toString() : value;
|
return !_.isString(value) ? value.toString() : value;
|
||||||
});
|
});
|
||||||
@ -61,6 +67,8 @@ export const eip712Utils = {
|
|||||||
zeroExTransaction: ZeroExTransaction,
|
zeroExTransaction: ZeroExTransaction,
|
||||||
exchangeAddress: string,
|
exchangeAddress: string,
|
||||||
): EIP712TypedData => {
|
): EIP712TypedData => {
|
||||||
|
assert.isETHAddressHex('exchangeAddress', exchangeAddress);
|
||||||
|
assert.doesConformToSchema('zeroExTransaction', zeroExTransaction, schemas.zeroExTransactionSchema);
|
||||||
const normalizedTransaction = _.mapValues(zeroExTransaction, value => {
|
const normalizedTransaction = _.mapValues(zeroExTransaction, value => {
|
||||||
return !_.isString(value) ? value.toString() : value;
|
return !_.isString(value) ? value.toString() : value;
|
||||||
});
|
});
|
||||||
|
@ -52,7 +52,7 @@ export const orderHashUtils = {
|
|||||||
*/
|
*/
|
||||||
getOrderHashBuffer(order: SignedOrder | Order): Buffer {
|
getOrderHashBuffer(order: SignedOrder | Order): Buffer {
|
||||||
const typedData = eip712Utils.createOrderTypedData(order);
|
const typedData = eip712Utils.createOrderTypedData(order);
|
||||||
const orderHashBuff = signTypedDataUtils.signTypedDataHash(typedData);
|
const orderHashBuff = signTypedDataUtils.generateTypedDataHash(typedData);
|
||||||
return orderHashBuff;
|
return orderHashBuff;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -194,7 +194,7 @@ export const signatureUtils = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Signs an order and returns its elliptic curve signature and signature type. First `eth_signTypedData` is requested
|
* 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.
|
* then a fallback to `eth_sign` if not available on the supplied provider.
|
||||||
* @param order The Order to sign.
|
* @param order The Order to sign.
|
||||||
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
|
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
|
||||||
@ -202,11 +202,18 @@ export const signatureUtils = {
|
|||||||
* @return A SignedOrder containing the order and Elliptic curve signature with Signature Type.
|
* @return A SignedOrder containing the order and Elliptic curve signature with Signature Type.
|
||||||
*/
|
*/
|
||||||
async ecSignOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise<SignedOrder> {
|
async ecSignOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise<SignedOrder> {
|
||||||
|
assert.doesConformToSchema('order', order, schemas.orderSchema, [schemas.hexSchema]);
|
||||||
try {
|
try {
|
||||||
const signedOrder = signatureUtils.ecSignTypedDataOrderAsync(provider, order, signerAddress);
|
const signedOrder = await signatureUtils.ecSignTypedDataOrderAsync(provider, order, signerAddress);
|
||||||
return signedOrder;
|
return signedOrder;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Fallback to using EthSign when ethSignTypedData is not supported
|
// 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
|
||||||
|
if (err.message.includes('User denied message signature')) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
const orderHash = orderHashUtils.getOrderHashHex(order);
|
const orderHash = orderHashUtils.getOrderHashHex(order);
|
||||||
const signatureHex = await signatureUtils.ecSignHashAsync(provider, orderHash, signerAddress);
|
const signatureHex = await signatureUtils.ecSignHashAsync(provider, orderHash, signerAddress);
|
||||||
const signedOrder = {
|
const signedOrder = {
|
||||||
@ -217,7 +224,7 @@ export const signatureUtils = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Signs an order using `eth_signTypedData` and returns its elliptic curve signature and signature type.
|
* Signs an order using `eth_signTypedData` and returns a SignedOrder.
|
||||||
* @param order The Order to sign.
|
* @param order The Order to sign.
|
||||||
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
|
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
|
||||||
* must be available via the supplied Provider.
|
* must be available via the supplied Provider.
|
||||||
@ -226,6 +233,7 @@ export const signatureUtils = {
|
|||||||
async ecSignTypedDataOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise<SignedOrder> {
|
async ecSignTypedDataOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise<SignedOrder> {
|
||||||
assert.isWeb3Provider('provider', provider);
|
assert.isWeb3Provider('provider', provider);
|
||||||
assert.isETHAddressHex('signerAddress', signerAddress);
|
assert.isETHAddressHex('signerAddress', signerAddress);
|
||||||
|
assert.doesConformToSchema('order', order, schemas.orderSchema, [schemas.hexSchema]);
|
||||||
const web3Wrapper = new Web3Wrapper(provider);
|
const web3Wrapper = new Web3Wrapper(provider);
|
||||||
await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper);
|
await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper);
|
||||||
const normalizedSignerAddress = signerAddress.toLowerCase();
|
const normalizedSignerAddress = signerAddress.toLowerCase();
|
||||||
@ -247,14 +255,16 @@ export const signatureUtils = {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Detect if Metamask to transition users to the MetamaskSubprovider
|
// Detect if Metamask to transition users to the MetamaskSubprovider
|
||||||
if ((provider as any).isMetaMask) {
|
if ((provider as any).isMetaMask) {
|
||||||
throw new Error(`Unsupported Provider, please use MetamaskSubprovider: ${err.message}`);
|
throw new Error(
|
||||||
|
`MetaMask provider must be wrapped in a MetamaskSubprovider (from the '@0xproject/subproviders' package) in order to work with this method.`,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Signs a hash and returns its elliptic curve signature and signature type.
|
* Signs a hash using `eth_sign` and returns its elliptic curve signature and signature type.
|
||||||
* @param msgHash Hex encoded message to sign.
|
* @param msgHash Hex encoded message to sign.
|
||||||
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
|
* @param signerAddress The hex encoded Ethereum address you wish to sign it with. This address
|
||||||
* must be available via the supplied Provider.
|
* must be available via the supplied Provider.
|
||||||
@ -303,7 +313,9 @@ export const signatureUtils = {
|
|||||||
}
|
}
|
||||||
// Detect if Metamask to transition users to the MetamaskSubprovider
|
// Detect if Metamask to transition users to the MetamaskSubprovider
|
||||||
if ((provider as any).isMetaMask) {
|
if ((provider as any).isMetaMask) {
|
||||||
throw new Error('Unsupported Provider, please use MetamaskSubprovider.');
|
throw new Error(
|
||||||
|
`MetaMask provider must be wrapped in a MetamaskSubprovider (from the '@0xproject/subproviders' package) in order to work with this method.`,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(OrderError.InvalidSignature);
|
throw new Error(OrderError.InvalidSignature);
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ describe('EIP712 Utils', () => {
|
|||||||
{
|
{
|
||||||
salt: new BigNumber('0'),
|
salt: new BigNumber('0'),
|
||||||
data: constants.NULL_BYTES,
|
data: constants.NULL_BYTES,
|
||||||
signerAddress: constants.NULL_BYTES,
|
signerAddress: constants.NULL_ADDRESS,
|
||||||
},
|
},
|
||||||
constants.NULL_ADDRESS,
|
constants.NULL_ADDRESS,
|
||||||
);
|
);
|
||||||
|
@ -17,6 +17,28 @@ chaiSetup.configure();
|
|||||||
const expect = chai.expect;
|
const expect = chai.expect;
|
||||||
|
|
||||||
describe('Signature utils', () => {
|
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', () => {
|
describe('#isValidSignatureAsync', () => {
|
||||||
let dataHex = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0';
|
let dataHex = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0';
|
||||||
const ethSignSignature =
|
const ethSignSignature =
|
||||||
@ -117,54 +139,54 @@ describe('Signature utils', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('#ecSignOrderAsync', () => {
|
describe('#ecSignOrderAsync', () => {
|
||||||
let makerAddress: string;
|
it('should default to eth_sign if eth_signTypedData is unavailable', async () => {
|
||||||
const fakeExchangeContractAddress = '0x1dc4c1cefef38a777b15aa20260a54e584b16c48';
|
const expectedSignature =
|
||||||
let order: Order;
|
'0x1c3582f06356a1314dbf1c0e534c4d8e92e59b056ee607a7ff5a825f5f2cc5e6151c5cc7fdd420f5608e4d5bef108e42ad90c7a4b408caef32e24374cf387b0d7603';
|
||||||
before(async () => {
|
|
||||||
const availableAddreses = await web3Wrapper.getAvailableAddressesAsync();
|
const fakeProvider = {
|
||||||
makerAddress = availableAddreses[0];
|
async sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback): Promise<void> {
|
||||||
order = {
|
if (payload.method === 'eth_signTypedData') {
|
||||||
makerAddress,
|
callback(new Error('Internal RPC Error'));
|
||||||
takerAddress: constants.NULL_ADDRESS,
|
} else if (payload.method === 'eth_sign') {
|
||||||
senderAddress: constants.NULL_ADDRESS,
|
const [address, message] = payload.params;
|
||||||
feeRecipientAddress: constants.NULL_ADDRESS,
|
const signature = await web3Wrapper.signMessageAsync(address, message);
|
||||||
makerAssetData: constants.NULL_ADDRESS,
|
callback(null, {
|
||||||
takerAssetData: constants.NULL_ADDRESS,
|
id: 42,
|
||||||
exchangeAddress: fakeExchangeContractAddress,
|
jsonrpc: '2.0',
|
||||||
salt: new BigNumber(0),
|
result: signature,
|
||||||
makerFee: new BigNumber(0),
|
|
||||||
takerFee: new BigNumber(0),
|
|
||||||
makerAssetAmount: new BigNumber(0),
|
|
||||||
takerAssetAmount: new BigNumber(0),
|
|
||||||
expirationTimeSeconds: new BigNumber(0),
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
it('should result in the same signature as signing order hash without prefix', async () => {
|
} else {
|
||||||
const orderHashHex = orderHashUtils.getOrderHashHex(order);
|
callback(null, { id: 42, jsonrpc: '2.0', result: [makerAddress] });
|
||||||
const sig = ethUtil.ecsign(
|
}
|
||||||
ethUtil.toBuffer(orderHashHex),
|
},
|
||||||
Buffer.from('F2F48EE19680706196E2E339E5DA3491186E0C4C5030670656B0E0164837257D', 'hex'),
|
};
|
||||||
|
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',
|
||||||
);
|
);
|
||||||
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.ecSignOrderAsync(provider, order, makerAddress);
|
|
||||||
const isValidSignature = await signatureUtils.isValidSignatureAsync(
|
|
||||||
provider,
|
|
||||||
orderHashHex,
|
|
||||||
signedOrder.signature,
|
|
||||||
makerAddress,
|
|
||||||
);
|
|
||||||
expect(signatureHex).to.eq(signedOrder.signature);
|
|
||||||
expect(isValidSignature).to.eq(true);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('#ecSignHashAsync', () => {
|
describe('#ecSignHashAsync', () => {
|
||||||
let makerAddress: string;
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
const availableAddreses = await web3Wrapper.getAvailableAddressesAsync();
|
const availableAddreses = await web3Wrapper.getAvailableAddressesAsync();
|
||||||
makerAddress = availableAddreses[0];
|
makerAddress = availableAddreses[0];
|
||||||
@ -239,27 +261,30 @@ describe('Signature utils', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('#ecSignTypedDataOrderAsync', () => {
|
describe('#ecSignTypedDataOrderAsync', () => {
|
||||||
let makerAddress: string;
|
it('should result in the same signature as signing the order hash without an ethereum message prefix', async () => {
|
||||||
const fakeExchangeContractAddress = '0x1dc4c1cefef38a777b15aa20260a54e584b16c48';
|
// Note: Since order hash is an EIP712 hash the result of a valid EIP712 signature
|
||||||
let order: Order;
|
// of order hash is the same as signing the order without the Ethereum Message prefix.
|
||||||
before(async () => {
|
const orderHashHex = orderHashUtils.getOrderHashHex(order);
|
||||||
const availableAddreses = await web3Wrapper.getAvailableAddressesAsync();
|
const sig = ethUtil.ecsign(
|
||||||
makerAddress = availableAddreses[0];
|
ethUtil.toBuffer(orderHashHex),
|
||||||
order = {
|
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,
|
makerAddress,
|
||||||
takerAddress: constants.NULL_ADDRESS,
|
);
|
||||||
senderAddress: constants.NULL_ADDRESS,
|
expect(signatureHex).to.eq(signedOrder.signature);
|
||||||
feeRecipientAddress: constants.NULL_ADDRESS,
|
expect(isValidSignature).to.eq(true);
|
||||||
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),
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
it('should return the correct Signature for signatureHex concatenated as R + S + V', async () => {
|
it('should return the correct Signature for signatureHex concatenated as R + S + V', async () => {
|
||||||
const expectedSignature =
|
const expectedSignature =
|
||||||
|
@ -57,4 +57,10 @@ export {
|
|||||||
EIP712Parameter,
|
EIP712Parameter,
|
||||||
} from '@0xproject/types';
|
} from '@0xproject/types';
|
||||||
|
|
||||||
export { JSONRPCRequestPayload, Provider, JSONRPCResponsePayload, JSONRPCErrorCallback } from 'ethereum-types';
|
export {
|
||||||
|
JSONRPCRequestPayload,
|
||||||
|
Provider,
|
||||||
|
JSONRPCResponsePayload,
|
||||||
|
JSONRPCErrorCallback,
|
||||||
|
JSONRPCResponseError,
|
||||||
|
} from 'ethereum-types';
|
||||||
|
@ -106,7 +106,7 @@ export class PrivateKeyWalletSubprovider extends BaseWalletSubprovider {
|
|||||||
`Requested to sign message with address: ${address}, instantiated with address: ${this._address}`,
|
`Requested to sign message with address: ${address}, instantiated with address: ${this._address}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const dataBuff = signTypedDataUtils.signTypedDataHash(typedData);
|
const dataBuff = signTypedDataUtils.generateTypedDataHash(typedData);
|
||||||
const sig = ethUtil.ecsign(dataBuff, this._privateKeyBuffer);
|
const sig = ethUtil.ecsign(dataBuff, this._privateKeyBuffer);
|
||||||
const rpcSig = ethUtil.toRpcSig(sig.v, sig.r, sig.s);
|
const rpcSig = ethUtil.toRpcSig(sig.v, sig.r, sig.s);
|
||||||
return rpcSig;
|
return rpcSig;
|
||||||
|
@ -6,11 +6,11 @@ import { EIP712Object, EIP712ObjectValue, EIP712TypedData, EIP712Types } from '@
|
|||||||
|
|
||||||
export const signTypedDataUtils = {
|
export const signTypedDataUtils = {
|
||||||
/**
|
/**
|
||||||
* Computes the Sign Typed Data hash
|
* Generates the EIP712 Typed Data hash for signing
|
||||||
* @param typedData An object that conforms to the EIP712TypedData interface
|
* @param typedData An object that conforms to the EIP712TypedData interface
|
||||||
* @return A Buffer containing the hash of the sign typed data.
|
* @return A Buffer containing the hash of the typed data.
|
||||||
*/
|
*/
|
||||||
signTypedDataHash(typedData: EIP712TypedData): Buffer {
|
generateTypedDataHash(typedData: EIP712TypedData): Buffer {
|
||||||
return ethUtil.sha3(
|
return ethUtil.sha3(
|
||||||
Buffer.concat([
|
Buffer.concat([
|
||||||
Buffer.from('1901', 'hex'),
|
Buffer.from('1901', 'hex'),
|
||||||
|
@ -127,12 +127,12 @@ describe('signTypedDataUtils', () => {
|
|||||||
primaryType: 'Order',
|
primaryType: 'Order',
|
||||||
};
|
};
|
||||||
it('creates a hash of the test sign typed data', () => {
|
it('creates a hash of the test sign typed data', () => {
|
||||||
const hash = signTypedDataUtils.signTypedDataHash(simpleSignTypedData).toString('hex');
|
const hash = signTypedDataUtils.generateTypedDataHash(simpleSignTypedData).toString('hex');
|
||||||
const hashHex = `0x${hash}`;
|
const hashHex = `0x${hash}`;
|
||||||
expect(hashHex).to.be.eq(simpleSignTypedDataHashHex);
|
expect(hashHex).to.be.eq(simpleSignTypedDataHashHex);
|
||||||
});
|
});
|
||||||
it('creates a hash of the order sign typed data', () => {
|
it('creates a hash of the order sign typed data', () => {
|
||||||
const hash = signTypedDataUtils.signTypedDataHash(orderSignTypedData).toString('hex');
|
const hash = signTypedDataUtils.generateTypedDataHash(orderSignTypedData).toString('hex');
|
||||||
const hashHex = `0x${hash}`;
|
const hashHex = `0x${hash}`;
|
||||||
expect(hashHex).to.be.eq(orderSignTypedDataHashHex);
|
expect(hashHex).to.be.eq(orderSignTypedDataHashHex);
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,18 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "3.1.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Add `signTypedData` to perform EIP712 `eth_signTypedData`.",
|
||||||
|
"pr": 1102
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"note":
|
||||||
|
"Web3Wrapper now throws when an RPC request contains an error field in the response. Previously errors could be swallowed and undefined returned.",
|
||||||
|
"pr": 1102
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"changes": [
|
"changes": [
|
||||||
|
@ -322,7 +322,7 @@ export class Web3Wrapper {
|
|||||||
*/
|
*/
|
||||||
public async signTypedDataAsync(address: string, typedData: any): Promise<string> {
|
public async signTypedDataAsync(address: string, typedData: any): Promise<string> {
|
||||||
assert.isETHAddressHex('address', address);
|
assert.isETHAddressHex('address', address);
|
||||||
assert.doesConformToSchema('typedData', typedData, schemas.eip712TypedData);
|
assert.doesConformToSchema('typedData', typedData, schemas.eip712TypedDataSchema);
|
||||||
const signData = await this.sendRawPayloadAsync<string>({
|
const signData = await this.sendRawPayloadAsync<string>({
|
||||||
method: 'eth_signTypedData',
|
method: 'eth_signTypedData',
|
||||||
params: [address, typedData],
|
params: [address, typedData],
|
||||||
@ -669,6 +669,9 @@ export class Web3Wrapper {
|
|||||||
...payload,
|
...payload,
|
||||||
};
|
};
|
||||||
const response = await promisify<JSONRPCResponsePayload>(sendAsync)(payloadWithDefaults);
|
const response = await promisify<JSONRPCResponsePayload>(sendAsync)(payloadWithDefaults);
|
||||||
|
if (response.error) {
|
||||||
|
throw new Error(response.error.message);
|
||||||
|
}
|
||||||
const result = response.result;
|
const result = response.result;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import * as chai from 'chai';
|
import * as chai from 'chai';
|
||||||
import { BlockParamLiteral } from 'ethereum-types';
|
import { BlockParamLiteral, JSONRPCErrorCallback, JSONRPCRequestPayload } from 'ethereum-types';
|
||||||
import * as Ganache from 'ganache-core';
|
import * as Ganache from 'ganache-core';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
@ -78,6 +78,19 @@ describe('Web3Wrapper tests', () => {
|
|||||||
const signatureLength = 132;
|
const signatureLength = 132;
|
||||||
expect(signature.length).to.be.equal(signatureLength);
|
expect(signature.length).to.be.equal(signatureLength);
|
||||||
});
|
});
|
||||||
|
it('should throw if the provider returns an error', async () => {
|
||||||
|
const message = '0xdeadbeef';
|
||||||
|
const signer = addresses[1];
|
||||||
|
const fakeProvider = {
|
||||||
|
async sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback): Promise<void> {
|
||||||
|
callback(new Error('User denied message signature'));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const errorWeb3Wrapper = new Web3Wrapper(fakeProvider);
|
||||||
|
expect(errorWeb3Wrapper.signMessageAsync(signer, message)).to.be.rejectedWith(
|
||||||
|
'User denied message signature',
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe('#getBlockNumberAsync', () => {
|
describe('#getBlockNumberAsync', () => {
|
||||||
it('get block number', async () => {
|
it('get block number', async () => {
|
||||||
|
@ -17,6 +17,7 @@ import {
|
|||||||
MetamaskSubprovider,
|
MetamaskSubprovider,
|
||||||
RedundantSubprovider,
|
RedundantSubprovider,
|
||||||
RPCSubprovider,
|
RPCSubprovider,
|
||||||
|
SignerSubprovider,
|
||||||
Web3ProviderEngine,
|
Web3ProviderEngine,
|
||||||
} from '@0xproject/subproviders';
|
} from '@0xproject/subproviders';
|
||||||
import { SignedOrder, Token as ZeroExToken } from '@0xproject/types';
|
import { SignedOrder, Token as ZeroExToken } from '@0xproject/types';
|
||||||
@ -27,8 +28,6 @@ import * as _ from 'lodash';
|
|||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import contract = require('truffle-contract');
|
import contract = require('truffle-contract');
|
||||||
import { tokenAddressOverrides } from 'ts/utils/token_address_overrides';
|
|
||||||
|
|
||||||
import { BlockchainWatcher } from 'ts/blockchain_watcher';
|
import { BlockchainWatcher } from 'ts/blockchain_watcher';
|
||||||
import { AssetSendCompleted } from 'ts/components/flash_messages/asset_send_completed';
|
import { AssetSendCompleted } from 'ts/components/flash_messages/asset_send_completed';
|
||||||
import { TransactionSubmitted } from 'ts/components/flash_messages/transaction_submitted';
|
import { TransactionSubmitted } from 'ts/components/flash_messages/transaction_submitted';
|
||||||
@ -54,6 +53,7 @@ import { backendClient } from 'ts/utils/backend_client';
|
|||||||
import { configs } from 'ts/utils/configs';
|
import { configs } from 'ts/utils/configs';
|
||||||
import { constants } from 'ts/utils/constants';
|
import { constants } from 'ts/utils/constants';
|
||||||
import { errorReporter } from 'ts/utils/error_reporter';
|
import { errorReporter } from 'ts/utils/error_reporter';
|
||||||
|
import { tokenAddressOverrides } from 'ts/utils/token_address_overrides';
|
||||||
import { utils } from 'ts/utils/utils';
|
import { utils } from 'ts/utils/utils';
|
||||||
import FilterSubprovider = require('web3-provider-engine/subproviders/filters');
|
import FilterSubprovider = require('web3-provider-engine/subproviders/filters');
|
||||||
|
|
||||||
@ -161,7 +161,13 @@ export class Blockchain {
|
|||||||
// We catch all requests involving a users account and send it to the injectedWeb3
|
// We catch all requests involving a users account and send it to the injectedWeb3
|
||||||
// instance. All other requests go to the public hosted node.
|
// instance. All other requests go to the public hosted node.
|
||||||
const provider = new Web3ProviderEngine();
|
const provider = new Web3ProviderEngine();
|
||||||
provider.addProvider(new MetamaskSubprovider(injectedWeb3.currentProvider));
|
const providerName = this._getNameGivenProvider(injectedWeb3.currentProvider);
|
||||||
|
// Wrap Metamask in a compatability wrapper MetamaskSubprovider (to handle inconsistencies)
|
||||||
|
const signerSubprovider =
|
||||||
|
providerName === Providers.Metamask
|
||||||
|
? new MetamaskSubprovider(injectedWeb3.currentProvider)
|
||||||
|
: new SignerSubprovider(injectedWeb3.currentProvider);
|
||||||
|
provider.addProvider(signerSubprovider);
|
||||||
provider.addProvider(new FilterSubprovider());
|
provider.addProvider(new FilterSubprovider());
|
||||||
const rpcSubproviders = _.map(publicNodeUrlsIfExistsForNetworkId, publicNodeUrl => {
|
const rpcSubproviders = _.map(publicNodeUrlsIfExistsForNetworkId, publicNodeUrl => {
|
||||||
return new RPCSubprovider(publicNodeUrl);
|
return new RPCSubprovider(publicNodeUrl);
|
||||||
|
64
yarn.lock
64
yarn.lock
@ -5606,19 +5606,6 @@ ethereumjs-wallet@0.6.0:
|
|||||||
utf8 "^2.1.1"
|
utf8 "^2.1.1"
|
||||||
uuid "^2.0.1"
|
uuid "^2.0.1"
|
||||||
|
|
||||||
ethereumjs-wallet@~0.6.0:
|
|
||||||
version "0.6.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/ethereumjs-wallet/-/ethereumjs-wallet-0.6.2.tgz#67244b6af3e8113b53d709124b25477b64aeccda"
|
|
||||||
dependencies:
|
|
||||||
aes-js "^3.1.1"
|
|
||||||
bs58check "^2.1.2"
|
|
||||||
ethereumjs-util "^5.2.0"
|
|
||||||
hdkey "^1.0.0"
|
|
||||||
safe-buffer "^5.1.2"
|
|
||||||
scrypt.js "^0.2.0"
|
|
||||||
utf8 "^3.0.0"
|
|
||||||
uuid "^3.3.2"
|
|
||||||
|
|
||||||
ethers@3.0.22:
|
ethers@3.0.22:
|
||||||
version "3.0.22"
|
version "3.0.22"
|
||||||
resolved "https://registry.yarnpkg.com/ethers/-/ethers-3.0.22.tgz#7fab1ea16521705837aa43c15831877b2716b436"
|
resolved "https://registry.yarnpkg.com/ethers/-/ethers-3.0.22.tgz#7fab1ea16521705837aa43c15831877b2716b436"
|
||||||
@ -6432,7 +6419,7 @@ ganache-core@0xProject/ganache-core#monorepo-dep:
|
|||||||
ethereumjs-tx "0xProject/ethereumjs-tx#fake-tx-include-signature-by-default"
|
ethereumjs-tx "0xProject/ethereumjs-tx#fake-tx-include-signature-by-default"
|
||||||
ethereumjs-util "^5.2.0"
|
ethereumjs-util "^5.2.0"
|
||||||
ethereumjs-vm "2.3.5"
|
ethereumjs-vm "2.3.5"
|
||||||
ethereumjs-wallet "0.6.0"
|
ethereumjs-wallet "~0.6.0"
|
||||||
fake-merkle-patricia-tree "~1.0.1"
|
fake-merkle-patricia-tree "~1.0.1"
|
||||||
heap "~0.2.6"
|
heap "~0.2.6"
|
||||||
js-scrypt "^0.2.0"
|
js-scrypt "^0.2.0"
|
||||||
@ -6797,7 +6784,7 @@ globby@^6.1.0:
|
|||||||
pify "^2.0.0"
|
pify "^2.0.0"
|
||||||
pinkie-promise "^2.0.0"
|
pinkie-promise "^2.0.0"
|
||||||
|
|
||||||
globby@^8.0.0, globby@^8.0.1:
|
globby@^8.0.1:
|
||||||
version "8.0.1"
|
version "8.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/globby/-/globby-8.0.1.tgz#b5ad48b8aa80b35b814fc1281ecc851f1d2b5b50"
|
resolved "https://registry.yarnpkg.com/globby/-/globby-8.0.1.tgz#b5ad48b8aa80b35b814fc1281ecc851f1d2b5b50"
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -8576,10 +8563,6 @@ jsesc@~0.5.0:
|
|||||||
version "0.5.0"
|
version "0.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
|
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
|
||||||
|
|
||||||
json-buffer@3.0.0:
|
|
||||||
version "3.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
|
|
||||||
|
|
||||||
json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
|
json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
|
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
|
||||||
@ -9357,7 +9340,7 @@ lodash@^4.13.1, lodash@^4.15.0:
|
|||||||
version "4.17.11"
|
version "4.17.11"
|
||||||
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
|
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
|
||||||
|
|
||||||
lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.1:
|
lodash@^4.14.0, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.5.1:
|
||||||
version "4.17.5"
|
version "4.17.5"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
|
||||||
|
|
||||||
@ -10882,10 +10865,6 @@ p-is-promise@^1.1.0:
|
|||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e"
|
resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e"
|
||||||
|
|
||||||
p-lazy@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/p-lazy/-/p-lazy-1.0.0.tgz#ec53c802f2ee3ac28f166cc82d0b2b02de27a835"
|
|
||||||
|
|
||||||
p-limit@^1.1.0:
|
p-limit@^1.1.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c"
|
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c"
|
||||||
@ -15834,12 +15813,6 @@ webidl-conversions@^4.0.2:
|
|||||||
version "4.0.2"
|
version "4.0.2"
|
||||||
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
|
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
|
||||||
|
|
||||||
webpack-addons@^1.1.5:
|
|
||||||
version "1.1.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/webpack-addons/-/webpack-addons-1.1.5.tgz#2b178dfe873fb6e75e40a819fa5c26e4a9bc837a"
|
|
||||||
dependencies:
|
|
||||||
jscodeshift "^0.4.0"
|
|
||||||
|
|
||||||
webpack-cli@3.1.2, webpack-cli@^3.1.1:
|
webpack-cli@3.1.2, webpack-cli@^3.1.1:
|
||||||
version "3.1.2"
|
version "3.1.2"
|
||||||
resolved "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.1.2.tgz#17d7e01b77f89f884a2bbf9db545f0f6a648e746"
|
resolved "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.1.2.tgz#17d7e01b77f89f884a2bbf9db545f0f6a648e746"
|
||||||
@ -15855,37 +15828,6 @@ webpack-cli@3.1.2, webpack-cli@^3.1.1:
|
|||||||
v8-compile-cache "^2.0.2"
|
v8-compile-cache "^2.0.2"
|
||||||
yargs "^12.0.2"
|
yargs "^12.0.2"
|
||||||
|
|
||||||
webpack-cli@^2.0.9:
|
|
||||||
version "2.1.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-2.1.3.tgz#65d166851abaa56067ef3f716b02a97ba6bbe84d"
|
|
||||||
dependencies:
|
|
||||||
chalk "^2.3.2"
|
|
||||||
cross-spawn "^6.0.5"
|
|
||||||
diff "^3.5.0"
|
|
||||||
enhanced-resolve "^4.0.0"
|
|
||||||
envinfo "^4.4.2"
|
|
||||||
glob-all "^3.1.0"
|
|
||||||
global-modules "^1.0.0"
|
|
||||||
got "^8.2.0"
|
|
||||||
import-local "^1.0.0"
|
|
||||||
inquirer "^5.1.0"
|
|
||||||
interpret "^1.0.4"
|
|
||||||
jscodeshift "^0.5.0"
|
|
||||||
listr "^0.13.0"
|
|
||||||
loader-utils "^1.1.0"
|
|
||||||
lodash "^4.17.5"
|
|
||||||
log-symbols "^2.2.0"
|
|
||||||
mkdirp "^0.5.1"
|
|
||||||
p-each-series "^1.0.0"
|
|
||||||
p-lazy "^1.0.0"
|
|
||||||
prettier "^1.5.3"
|
|
||||||
supports-color "^5.3.0"
|
|
||||||
v8-compile-cache "^1.1.2"
|
|
||||||
webpack-addons "^1.1.5"
|
|
||||||
yargs "^11.1.0"
|
|
||||||
yeoman-environment "^2.0.0"
|
|
||||||
yeoman-generator "^2.0.4"
|
|
||||||
|
|
||||||
webpack-dev-middleware@3.4.0:
|
webpack-dev-middleware@3.4.0:
|
||||||
version "3.4.0"
|
version "3.4.0"
|
||||||
resolved "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.4.0.tgz#1132fecc9026fd90f0ecedac5cbff75d1fb45890"
|
resolved "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.4.0.tgz#1132fecc9026fd90f0ecedac5cbff75d1fb45890"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user