Merge branch 'development'
This commit is contained in:
@@ -25,7 +25,7 @@ If you're developing on 0x now or are interested in using 0x infrastructure in t
|
||||
| [`0x.js`](/packages/0x.js) | [](https://www.npmjs.com/package/0x.js) | A Javascript library for interacting with the 0x protocol |
|
||||
| [`@0xproject/abi-gen`](/packages/abi-gen) | [](https://www.npmjs.com/package/@0xproject/abi-gen) | Tool to generate TS wrappers from smart contract ABIs |
|
||||
| [`@0xproject/assert`](/packages/assert) | [](https://www.npmjs.com/package/@0xproject/assert) | Type and schema assertions used by our packages |
|
||||
| [`@0xproject/assert`](/packages/asset-buyer) | [](https://www.npmjs.com/package/@0xproject/asset-buyer) | Convenience package for discovering and buying assets with Ether. |
|
||||
| [`@0xproject/asset-buyer`](/packages/asset-buyer) | [](https://www.npmjs.com/package/@0xproject/asset-buyer) | Convenience package for discovering and buying assets with Ether. |
|
||||
| [`@0xproject/base-contract`](/packages/base-contract) | [](https://www.npmjs.com/package/@0xproject/base-contract) | BaseContract used by auto-generated `abi-gen` wrapper contracts |
|
||||
| [`@0xproject/connect`](/packages/connect) | [](https://www.npmjs.com/package/@0xproject/connect) | A Javascript library for interacting with the Standard Relayer API |
|
||||
| [`@0xproject/dev-utils`](/packages/dev-utils) | [](https://www.npmjs.com/package/@0xproject/dev-utils) | Dev utils to be shared across 0x projects and packages |
|
||||
|
@@ -56,7 +56,7 @@
|
||||
"async-child-process": "^1.1.1",
|
||||
"bundlesize": "^0.17.0",
|
||||
"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",
|
||||
|
@@ -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": [
|
||||
|
@@ -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,
|
||||
|
@@ -39,6 +39,7 @@ export {
|
||||
JSONRPCRequestPayload,
|
||||
JSONRPCResponsePayload,
|
||||
JSONRPCErrorCallback,
|
||||
JSONRPCResponseError,
|
||||
AbiDefinition,
|
||||
LogWithDecodedArgs,
|
||||
FunctionAbi,
|
||||
|
@@ -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;
|
||||
}
|
||||
/**
|
||||
|
@@ -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,
|
||||
|
@@ -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();
|
||||
|
@@ -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
|
||||
|
@@ -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')}`,
|
||||
|
@@ -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",
|
||||
|
@@ -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 {
|
||||
|
28
packages/json-schemas/schemas/eip712_typed_data.ts
Normal file
28
packages/json-schemas/schemas/eip712_typed_data.ts
Normal 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'],
|
||||
};
|
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,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,
|
||||
};
|
||||
|
@@ -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": [
|
||||
|
@@ -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' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
@@ -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,
|
||||
},
|
||||
domain: {
|
||||
name: constants.EIP712_DOMAIN_NAME,
|
||||
version: constants.EIP712_DOMAIN_VERSION,
|
||||
verifyingContract: exchangeAddress,
|
||||
},
|
||||
message,
|
||||
primaryType,
|
||||
};
|
||||
assert.doesConformToSchema('typedData', typedData, schemas.eip712TypedDataSchema);
|
||||
return typedData;
|
||||
},
|
||||
/**
|
||||
* 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
|
||||
* Creates an Order EIP712TypedData object for use with signTypedData.
|
||||
* @param Order the order
|
||||
* @return A typed data object
|
||||
*/
|
||||
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,
|
||||
verifyingContract: exchangeAddress,
|
||||
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;
|
||||
},
|
||||
};
|
||||
|
@@ -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,
|
||||
|
@@ -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;
|
||||
},
|
||||
|
@@ -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);
|
||||
},
|
||||
};
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(OrderError.InvalidSignature);
|
||||
// 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}`);
|
||||
}
|
||||
const msgBuff = ethUtil.toBuffer(message);
|
||||
const prefixedMsgBuff = ethUtil.hashPersonalMessage(msgBuff);
|
||||
const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff);
|
||||
return prefixedMsgHex;
|
||||
},
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
@@ -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;
|
||||
|
44
packages/order-utils/test/eip712_utils_test.ts
Normal file
44
packages/order-utils/test/eip712_utils_test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
@@ -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,
|
||||
|
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -12,4 +12,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';
|
||||
|
@@ -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,
|
||||
|
@@ -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": [
|
||||
|
@@ -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",
|
||||
|
@@ -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';
|
||||
|
@@ -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();
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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)) {
|
||||
|
126
packages/subproviders/src/subproviders/metamask_subprovider.ts
Normal file
126
packages/subproviders/src/subproviders/metamask_subprovider.ts
Normal 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 });
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@@ -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');
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -14,7 +14,7 @@ import { Subprovider } from './subprovider';
|
||||
export class SignerSubprovider extends Subprovider {
|
||||
private readonly _web3Wrapper: Web3Wrapper;
|
||||
/**
|
||||
* Instantiates a new SignerSubprovider
|
||||
* Instantiates a new SignerSubprovider.
|
||||
* @param provider Web3 provider that should handle all user account related requests
|
||||
*/
|
||||
constructor(provider: Provider) {
|
||||
@@ -31,6 +31,8 @@ export class SignerSubprovider extends Subprovider {
|
||||
*/
|
||||
// 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 {
|
||||
@@ -59,7 +61,7 @@ export class SignerSubprovider extends Subprovider {
|
||||
}
|
||||
return;
|
||||
case 'eth_sign':
|
||||
const [address, message] = payload.params;
|
||||
[address, message] = payload.params;
|
||||
try {
|
||||
const signature = await this._web3Wrapper.signMessageAsync(address, message);
|
||||
end(null, signature);
|
||||
@@ -67,6 +69,15 @@ export class SignerSubprovider extends Subprovider {
|
||||
end(err);
|
||||
}
|
||||
return;
|
||||
case 'eth_signTypedData':
|
||||
[address, message] = payload.params;
|
||||
try {
|
||||
const signature = await this._web3Wrapper.signTypedDataAsync(address, message);
|
||||
end(null, signature);
|
||||
} catch (err) {
|
||||
end(err);
|
||||
}
|
||||
return;
|
||||
default:
|
||||
next();
|
||||
return;
|
||||
|
@@ -107,8 +107,10 @@ export interface ResponseWithTxParams {
|
||||
export enum WalletSubproviderErrors {
|
||||
AddressNotFound = 'ADDRESS_NOT_FOUND',
|
||||
DataMissingForSignPersonalMessage = 'DATA_MISSING_FOR_SIGN_PERSONAL_MESSAGE',
|
||||
DataMissingForSignTypedData = 'DATA_MISSING_FOR_SIGN_TYPED_DATA',
|
||||
SenderInvalidOrNotSupplied = 'SENDER_INVALID_OR_NOT_SUPPLIED',
|
||||
FromAddressMissingOrInvalid = 'FROM_ADDRESS_MISSING_OR_INVALID',
|
||||
MethodNotSupported = 'METHOD_NOT_SUPPORTED',
|
||||
}
|
||||
export enum LedgerSubproviderErrors {
|
||||
TooOldLedgerFirmware = 'TOO_OLD_LEDGER_FIRMWARE',
|
||||
|
@@ -73,6 +73,13 @@ describe('EthLightwalletSubprovider', () => {
|
||||
const txHex = await ethLightwalletSubprovider.signTransactionAsync(fixtureData.TX_DATA);
|
||||
expect(txHex).to.be.equal(fixtureData.TX_DATA_SIGNED_RESULT);
|
||||
});
|
||||
it('signs an EIP712 sign typed data message', async () => {
|
||||
const signature = await ethLightwalletSubprovider.signTypedDataAsync(
|
||||
fixtureData.TEST_RPC_ACCOUNT_0,
|
||||
fixtureData.EIP712_TEST_TYPED_DATA,
|
||||
);
|
||||
expect(signature).to.be.equal(fixtureData.EIP712_TEST_TYPED_DATA_SIGNED_RESULT);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('calls through a provider', () => {
|
||||
@@ -129,6 +136,20 @@ describe('EthLightwalletSubprovider', () => {
|
||||
});
|
||||
provider.sendAsync(payload, callback);
|
||||
});
|
||||
it('signs an EIP712 sign typed data message with eth_signTypedData', (done: DoneCallback) => {
|
||||
const payload = {
|
||||
jsonrpc: '2.0',
|
||||
method: 'eth_signTypedData',
|
||||
params: [fixtureData.TEST_RPC_ACCOUNT_0, fixtureData.EIP712_TEST_TYPED_DATA],
|
||||
id: 1,
|
||||
};
|
||||
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
||||
expect(err).to.be.a('null');
|
||||
expect(response.result).to.be.equal(fixtureData.EIP712_TEST_TYPED_DATA_SIGNED_RESULT);
|
||||
done();
|
||||
});
|
||||
provider.sendAsync(payload, callback);
|
||||
});
|
||||
});
|
||||
describe('failure cases', () => {
|
||||
it('should throw if `data` param not hex when calling eth_sign', (done: DoneCallback) => {
|
||||
|
@@ -47,6 +47,13 @@ describe('MnemonicWalletSubprovider', () => {
|
||||
const txHex = await subprovider.signTransactionAsync(txData);
|
||||
expect(txHex).to.be.equal(fixtureData.TX_DATA_ACCOUNT_1_SIGNED_RESULT);
|
||||
});
|
||||
it('signs an EIP712 sign typed data message', async () => {
|
||||
const signature = await subprovider.signTypedDataAsync(
|
||||
fixtureData.TEST_RPC_ACCOUNT_0,
|
||||
fixtureData.EIP712_TEST_TYPED_DATA,
|
||||
);
|
||||
expect(signature).to.be.equal(fixtureData.EIP712_TEST_TYPED_DATA_SIGNED_RESULT);
|
||||
});
|
||||
});
|
||||
describe('failure cases', () => {
|
||||
it('throws an error if address is invalid ', async () => {
|
||||
@@ -118,6 +125,20 @@ describe('MnemonicWalletSubprovider', () => {
|
||||
});
|
||||
provider.sendAsync(payload, callback);
|
||||
});
|
||||
it('signs an EIP712 sign typed data message with eth_signTypedData', (done: DoneCallback) => {
|
||||
const payload = {
|
||||
jsonrpc: '2.0',
|
||||
method: 'eth_signTypedData',
|
||||
params: [fixtureData.TEST_RPC_ACCOUNT_0, fixtureData.EIP712_TEST_TYPED_DATA],
|
||||
id: 1,
|
||||
};
|
||||
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
||||
expect(err).to.be.a('null');
|
||||
expect(response.result).to.be.equal(fixtureData.EIP712_TEST_TYPED_DATA_SIGNED_RESULT);
|
||||
done();
|
||||
});
|
||||
provider.sendAsync(payload, callback);
|
||||
});
|
||||
});
|
||||
describe('failure cases', () => {
|
||||
it('should throw if `data` param not hex when calling eth_sign', (done: DoneCallback) => {
|
||||
|
@@ -32,6 +32,13 @@ describe('PrivateKeyWalletSubprovider', () => {
|
||||
const txHex = await subprovider.signTransactionAsync(fixtureData.TX_DATA);
|
||||
expect(txHex).to.be.equal(fixtureData.TX_DATA_SIGNED_RESULT);
|
||||
});
|
||||
it('signs an EIP712 sign typed data message', async () => {
|
||||
const signature = await subprovider.signTypedDataAsync(
|
||||
fixtureData.TEST_RPC_ACCOUNT_0,
|
||||
fixtureData.EIP712_TEST_TYPED_DATA,
|
||||
);
|
||||
expect(signature).to.be.equal(fixtureData.EIP712_TEST_TYPED_DATA_SIGNED_RESULT);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('calls through a provider', () => {
|
||||
@@ -103,6 +110,20 @@ describe('PrivateKeyWalletSubprovider', () => {
|
||||
});
|
||||
provider.sendAsync(payload, callback);
|
||||
});
|
||||
it('signs an EIP712 sign typed data message with eth_signTypedData', (done: DoneCallback) => {
|
||||
const payload = {
|
||||
jsonrpc: '2.0',
|
||||
method: 'eth_signTypedData',
|
||||
params: [fixtureData.TEST_RPC_ACCOUNT_0, fixtureData.EIP712_TEST_TYPED_DATA],
|
||||
id: 1,
|
||||
};
|
||||
const callback = reportCallbackErrors(done)((err: Error, response: JSONRPCResponsePayload) => {
|
||||
expect(err).to.be.a('null');
|
||||
expect(response.result).to.be.equal(fixtureData.EIP712_TEST_TYPED_DATA_SIGNED_RESULT);
|
||||
done();
|
||||
});
|
||||
provider.sendAsync(payload, callback);
|
||||
});
|
||||
});
|
||||
describe('failure cases', () => {
|
||||
it('should throw if `data` param not hex when calling eth_sign', (done: DoneCallback) => {
|
||||
|
@@ -30,4 +30,35 @@ export const fixtureData = {
|
||||
'0xf85f8080822710940000000000000000000000000000000000000000808078a0712854c73c69445cc1b22a7c3d7312ff9a97fe4ffba35fd636e8236b211b6e7ca0647cee031615e52d916c7c707025bc64ad525d8f1b9876c3435a863b42743178',
|
||||
TX_DATA_ACCOUNT_1_SIGNED_RESULT:
|
||||
'0xf85f8080822710940000000000000000000000000000000000000000808078a04b02af7ff3f18ce114b601542cc8ebdc50921354f75dd510d31793453a0710e6a0540082a01e475465801b8186a2edc79ec1a2dcf169b9781c25a58a417023c9ca',
|
||||
EIP712_TEST_TYPED_DATA: {
|
||||
types: {
|
||||
EIP712Domain: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
Test: [
|
||||
{
|
||||
name: 'testAddress',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
name: 'testNumber',
|
||||
type: 'uint256',
|
||||
},
|
||||
],
|
||||
},
|
||||
domain: {
|
||||
name: 'Test',
|
||||
},
|
||||
message: {
|
||||
testAddress: '0x0000000000000000000000000000000000000000',
|
||||
testNumber: '12345',
|
||||
},
|
||||
primaryType: 'Test',
|
||||
},
|
||||
EIP712_TEST_TYPED_DATA_HASH: '0xb460d69ca60383293877cd765c0f97bd832d66bca720f7e32222ce1118832493',
|
||||
EIP712_TEST_TYPED_DATA_SIGNED_RESULT:
|
||||
'0x20af5b6bfc3658942198d6eeda159b4ed589f90cee6eac3ba117818ffba5fd7e354a353aad93faabd6eb6c66e17921c92bd1cd09c92a770f554470dc3e254ce701',
|
||||
};
|
||||
|
@@ -1,4 +1,17 @@
|
||||
[
|
||||
{
|
||||
"version": "1.2.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Added `EIP712Parameter` `EIP712Types` `EIP712TypedData` for EIP712 signing",
|
||||
"pr": 1102
|
||||
},
|
||||
{
|
||||
"note": "Added `ZeroExTransaction` type for Exchange executeTransaction",
|
||||
"pr": 1102
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1538693146,
|
||||
"version": "1.1.4",
|
||||
|
@@ -41,6 +41,15 @@ export interface SignedOrder extends Order {
|
||||
signature: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* ZeroExTransaction for use with 0x Exchange executeTransaction
|
||||
*/
|
||||
export interface ZeroExTransaction {
|
||||
salt: BigNumber;
|
||||
signerAddress: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Elliptic Curve signature
|
||||
*/
|
||||
@@ -143,16 +152,6 @@ export enum SignatureType {
|
||||
NSignatureTypes,
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of the Signer implementation. Some signer implementations use different message prefixes or implement different
|
||||
* eth_sign behaviour (e.g Metamask). Default assumes a spec compliant `eth_sign`.
|
||||
*/
|
||||
export enum SignerType {
|
||||
Default = 'DEFAULT',
|
||||
Ledger = 'LEDGER',
|
||||
Metamask = 'METAMASK',
|
||||
}
|
||||
|
||||
export enum AssetProxyId {
|
||||
ERC20 = '0xf47261b0',
|
||||
ERC721 = '0x02571792',
|
||||
@@ -599,3 +598,25 @@ export interface Metadata {
|
||||
externalTypeToLink: ExternalTypeToLink;
|
||||
externalExportToLink: ExternalExportToLink;
|
||||
}
|
||||
|
||||
export interface EIP712Parameter {
|
||||
name: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface EIP712Types {
|
||||
[key: string]: EIP712Parameter[];
|
||||
}
|
||||
|
||||
export type EIP712ObjectValue = string | number | EIP712Object;
|
||||
|
||||
export interface EIP712Object {
|
||||
[key: string]: EIP712ObjectValue;
|
||||
}
|
||||
|
||||
export interface EIP712TypedData {
|
||||
types: EIP712Types;
|
||||
domain: EIP712Object;
|
||||
message: EIP712Object;
|
||||
primaryType: string;
|
||||
}
|
||||
|
@@ -9,3 +9,4 @@ export { abiUtils } from './abi_utils';
|
||||
export { NULL_BYTES } from './constants';
|
||||
export { errorUtils } from './error_utils';
|
||||
export { fetchAsync } from './fetch_async';
|
||||
export { signTypedDataUtils } from './sign_typed_data_utils';
|
||||
|
82
packages/utils/src/sign_typed_data_utils.ts
Normal file
82
packages/utils/src/sign_typed_data_utils.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
import * as ethers from 'ethers';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { EIP712Object, EIP712ObjectValue, EIP712TypedData, EIP712Types } from '@0xproject/types';
|
||||
|
||||
export const signTypedDataUtils = {
|
||||
/**
|
||||
* Generates the EIP712 Typed Data hash for signing
|
||||
* @param typedData An object that conforms to the EIP712TypedData interface
|
||||
* @return A Buffer containing the hash of the typed data.
|
||||
*/
|
||||
generateTypedDataHash(typedData: EIP712TypedData): Buffer {
|
||||
return ethUtil.sha3(
|
||||
Buffer.concat([
|
||||
Buffer.from('1901', 'hex'),
|
||||
signTypedDataUtils._structHash('EIP712Domain', typedData.domain, typedData.types),
|
||||
signTypedDataUtils._structHash(typedData.primaryType, typedData.message, typedData.types),
|
||||
]),
|
||||
);
|
||||
},
|
||||
_findDependencies(primaryType: string, types: EIP712Types, found: string[] = []): string[] {
|
||||
if (found.includes(primaryType) || types[primaryType] === undefined) {
|
||||
return found;
|
||||
}
|
||||
found.push(primaryType);
|
||||
for (const field of types[primaryType]) {
|
||||
for (const dep of signTypedDataUtils._findDependencies(field.type, types, found)) {
|
||||
if (!found.includes(dep)) {
|
||||
found.push(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
return found;
|
||||
},
|
||||
_encodeType(primaryType: string, types: EIP712Types): string {
|
||||
let deps = signTypedDataUtils._findDependencies(primaryType, types);
|
||||
deps = deps.filter(d => d !== primaryType);
|
||||
deps = [primaryType].concat(deps.sort());
|
||||
let result = '';
|
||||
for (const dep of deps) {
|
||||
result += `${dep}(${types[dep].map(({ name, type }) => `${type} ${name}`).join(',')})`;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
_encodeData(primaryType: string, data: EIP712Object, types: EIP712Types): string {
|
||||
const encodedTypes = ['bytes32'];
|
||||
const encodedValues: Array<Buffer | EIP712ObjectValue> = [signTypedDataUtils._typeHash(primaryType, types)];
|
||||
for (const field of types[primaryType]) {
|
||||
const value = data[field.name];
|
||||
if (field.type === 'string' || field.type === 'bytes') {
|
||||
const hashValue = ethUtil.sha3(value as string);
|
||||
encodedTypes.push('bytes32');
|
||||
encodedValues.push(hashValue);
|
||||
} else if (types[field.type] !== undefined) {
|
||||
encodedTypes.push('bytes32');
|
||||
const hashValue = ethUtil.sha3(
|
||||
// tslint:disable-next-line:no-unnecessary-type-assertion
|
||||
signTypedDataUtils._encodeData(field.type, value as EIP712Object, types),
|
||||
);
|
||||
encodedValues.push(hashValue);
|
||||
} else if (field.type.lastIndexOf(']') === field.type.length - 1) {
|
||||
throw new Error('Arrays currently unimplemented in encodeData');
|
||||
} else {
|
||||
encodedTypes.push(field.type);
|
||||
const normalizedValue = signTypedDataUtils._normalizeValue(field.type, value);
|
||||
encodedValues.push(normalizedValue);
|
||||
}
|
||||
}
|
||||
return ethers.utils.defaultAbiCoder.encode(encodedTypes, encodedValues);
|
||||
},
|
||||
_normalizeValue(type: string, value: any): EIP712ObjectValue {
|
||||
const normalizedValue = type === 'uint256' && _.isObject(value) && value.isBigNumber ? value.toString() : value;
|
||||
return normalizedValue;
|
||||
},
|
||||
_typeHash(primaryType: string, types: EIP712Types): Buffer {
|
||||
return ethUtil.sha3(signTypedDataUtils._encodeType(primaryType, types));
|
||||
},
|
||||
_structHash(primaryType: string, data: EIP712Object, types: EIP712Types): Buffer {
|
||||
return ethUtil.sha3(signTypedDataUtils._encodeData(primaryType, data, types));
|
||||
},
|
||||
};
|
140
packages/utils/test/sign_typed_data_utils_test.ts
Normal file
140
packages/utils/test/sign_typed_data_utils_test.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import * as chai from 'chai';
|
||||
import 'mocha';
|
||||
|
||||
import { signTypedDataUtils } from '../src/sign_typed_data_utils';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('signTypedDataUtils', () => {
|
||||
describe('signTypedDataHash', () => {
|
||||
const simpleSignTypedDataHashHex = '0xb460d69ca60383293877cd765c0f97bd832d66bca720f7e32222ce1118832493';
|
||||
const simpleSignTypedData = {
|
||||
types: {
|
||||
EIP712Domain: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
Test: [
|
||||
{
|
||||
name: 'testAddress',
|
||||
type: 'address',
|
||||
},
|
||||
{
|
||||
name: 'testNumber',
|
||||
type: 'uint256',
|
||||
},
|
||||
],
|
||||
},
|
||||
domain: {
|
||||
name: 'Test',
|
||||
},
|
||||
message: {
|
||||
testAddress: '0x0000000000000000000000000000000000000000',
|
||||
testNumber: '12345',
|
||||
},
|
||||
primaryType: 'Test',
|
||||
};
|
||||
const orderSignTypedDataHashHex = '0x55eaa6ec02f3224d30873577e9ddd069a288c16d6fb407210eecbc501fa76692';
|
||||
const orderSignTypedData = {
|
||||
types: {
|
||||
EIP712Domain: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'version',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
name: 'verifyingContract',
|
||||
type: 'address',
|
||||
},
|
||||
],
|
||||
Order: [
|
||||
{
|
||||
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',
|
||||
},
|
||||
],
|
||||
},
|
||||
domain: {
|
||||
name: '0x Protocol',
|
||||
version: '2',
|
||||
verifyingContract: '0x0000000000000000000000000000000000000000',
|
||||
},
|
||||
message: {
|
||||
makerAddress: '0x0000000000000000000000000000000000000000',
|
||||
takerAddress: '0x0000000000000000000000000000000000000000',
|
||||
makerAssetAmount: '1000000000000000000',
|
||||
takerAssetAmount: '1000000000000000000',
|
||||
expirationTimeSeconds: '12345',
|
||||
makerFee: '0',
|
||||
takerFee: '0',
|
||||
feeRecipientAddress: '0x0000000000000000000000000000000000000000',
|
||||
senderAddress: '0x0000000000000000000000000000000000000000',
|
||||
salt: '12345',
|
||||
makerAssetData: '0x0000000000000000000000000000000000000000',
|
||||
takerAssetData: '0x0000000000000000000000000000000000000000',
|
||||
exchangeAddress: '0x0000000000000000000000000000000000000000',
|
||||
},
|
||||
primaryType: 'Order',
|
||||
};
|
||||
it('creates a hash of the test sign typed data', () => {
|
||||
const hash = signTypedDataUtils.generateTypedDataHash(simpleSignTypedData).toString('hex');
|
||||
const hashHex = `0x${hash}`;
|
||||
expect(hashHex).to.be.eq(simpleSignTypedDataHashHex);
|
||||
});
|
||||
it('creates a hash of the order sign typed data', () => {
|
||||
const hash = signTypedDataUtils.generateTypedDataHash(orderSignTypedData).toString('hex');
|
||||
const hashHex = `0x${hash}`;
|
||||
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",
|
||||
"changes": [
|
||||
|
@@ -30,6 +30,7 @@ export {
|
||||
OpCode,
|
||||
TxDataPayable,
|
||||
JSONRPCResponsePayload,
|
||||
JSONRPCResponseError,
|
||||
RawLogEntry,
|
||||
DecodedLogEntryEvent,
|
||||
LogWithDecodedArgs,
|
||||
|
@@ -314,6 +314,21 @@ export class Web3Wrapper {
|
||||
});
|
||||
return signData;
|
||||
}
|
||||
/**
|
||||
* Sign an EIP712 typed data message with a specific address's private key (`eth_signTypedData`)
|
||||
* @param address Address of signer
|
||||
* @param typedData Typed data message to sign
|
||||
* @returns Signature string (as RSV)
|
||||
*/
|
||||
public async signTypedDataAsync(address: string, typedData: any): Promise<string> {
|
||||
assert.isETHAddressHex('address', address);
|
||||
assert.doesConformToSchema('typedData', typedData, schemas.eip712TypedDataSchema);
|
||||
const signData = await this.sendRawPayloadAsync<string>({
|
||||
method: 'eth_signTypedData',
|
||||
params: [address, typedData],
|
||||
});
|
||||
return signData;
|
||||
}
|
||||
/**
|
||||
* Fetches the latest block number
|
||||
* @returns Block number
|
||||
@@ -654,6 +669,9 @@ export class Web3Wrapper {
|
||||
...payload,
|
||||
};
|
||||
const response = await promisify<JSONRPCResponsePayload>(sendAsync)(payloadWithDefaults);
|
||||
if (response.error) {
|
||||
throw new Error(response.error.message);
|
||||
}
|
||||
const result = response.result;
|
||||
return result;
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
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 _ from 'lodash';
|
||||
import 'mocha';
|
||||
@@ -78,6 +78,19 @@ describe('Web3Wrapper tests', () => {
|
||||
const signatureLength = 132;
|
||||
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', () => {
|
||||
it('get block number', async () => {
|
||||
|
@@ -1,3 +1,3 @@
|
||||
Welcome to the [@0xproject/json-schemas](https://github.com/0xProject/0x-monorepo/tree/development/packages/json-schemas) documentation! This package provides JSON schemas for validating 0x Protocol & Standard Relayer API data structures. It provides both the raw JSON schemas and a schema validator class to interact with them from a JS project.
|
||||
|
||||
If you are not using a Javascript-based language for your project, you can copy-paste the JSON schemas within this package and use them together with a [JSON Schema](http://json-schema.org/) implementation in your [language of choice](http://json-schema.org/implementations.html) (e.g Python, Haskell, Go, C, C++, Rust, Ruby, Scala, etc...).
|
||||
If you are not using a Javascript-based language for your project, you can use a Javascript environment to render the JSON schemas within this package and use them together with a [JSON Schema](http://json-schema.org/) implementation in your [language of choice](http://json-schema.org/implementations.html) (e.g Python, Haskell, Go, C, C++, Rust, Ruby, Scala, etc...). All the schema files are currently TypeScript that require evaluation in order to be recognized as valid JSON.
|
||||
|
@@ -9,11 +9,12 @@ import {
|
||||
ExchangeFillEventArgs,
|
||||
IndexedFilterValues,
|
||||
} from '@0xproject/contract-wrappers';
|
||||
import { assetDataUtils, orderHashUtils, signatureUtils, SignerType } from '@0xproject/order-utils';
|
||||
import { assetDataUtils, orderHashUtils, signatureUtils } from '@0xproject/order-utils';
|
||||
import { EtherscanLinkSuffixes, utils as sharedUtils } from '@0xproject/react-shared';
|
||||
import {
|
||||
ledgerEthereumBrowserClientFactoryAsync,
|
||||
LedgerSubprovider,
|
||||
MetamaskSubprovider,
|
||||
RedundantSubprovider,
|
||||
RPCSubprovider,
|
||||
SignerSubprovider,
|
||||
@@ -27,8 +28,6 @@ import * as _ from 'lodash';
|
||||
import * as moment from 'moment';
|
||||
import * as React from 'react';
|
||||
import contract = require('truffle-contract');
|
||||
import { tokenAddressOverrides } from 'ts/utils/token_address_overrides';
|
||||
|
||||
import { BlockchainWatcher } from 'ts/blockchain_watcher';
|
||||
import { AssetSendCompleted } from 'ts/components/flash_messages/asset_send_completed';
|
||||
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 { constants } from 'ts/utils/constants';
|
||||
import { errorReporter } from 'ts/utils/error_reporter';
|
||||
import { tokenAddressOverrides } from 'ts/utils/token_address_overrides';
|
||||
import { utils } from 'ts/utils/utils';
|
||||
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
|
||||
// instance. All other requests go to the public hosted node.
|
||||
const provider = new Web3ProviderEngine();
|
||||
provider.addProvider(new SignerSubprovider(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());
|
||||
const rpcSubproviders = _.map(publicNodeUrlsIfExistsForNetworkId, publicNodeUrl => {
|
||||
return new RPCSubprovider(publicNodeUrl);
|
||||
@@ -432,21 +438,7 @@ export class Blockchain {
|
||||
}
|
||||
this._showFlashMessageIfLedger();
|
||||
const provider = this._contractWrappers.getProvider();
|
||||
const isLedgerSigner = !_.isUndefined(this._ledgerSubprovider);
|
||||
const injectedProvider = Blockchain._getInjectedWeb3().currentProvider;
|
||||
const isMetaMaskSigner = utils.getProviderType(injectedProvider) === Providers.Metamask;
|
||||
let signerType = SignerType.Default;
|
||||
if (isLedgerSigner) {
|
||||
signerType = SignerType.Ledger;
|
||||
} else if (isMetaMaskSigner) {
|
||||
signerType = SignerType.Metamask;
|
||||
}
|
||||
const ecSignatureString = await signatureUtils.ecSignOrderHashAsync(
|
||||
provider,
|
||||
orderHash,
|
||||
makerAddress,
|
||||
signerType,
|
||||
);
|
||||
const ecSignatureString = await signatureUtils.ecSignHashAsync(provider, orderHash, makerAddress);
|
||||
this._dispatcher.updateSignature(ecSignatureString);
|
||||
return ecSignatureString;
|
||||
}
|
||||
|
Reference in New Issue
Block a user