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                                                                 | | | [`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/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/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/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/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                                                                    | | | [`@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", |         "async-child-process": "^1.1.1", | ||||||
|         "bundlesize": "^0.17.0", |         "bundlesize": "^0.17.0", | ||||||
|         "coveralls": "^3.0.0", |         "coveralls": "^3.0.0", | ||||||
|         "ganache-cli": "6.1.3", |         "ganache-cli": "6.1.8", | ||||||
|         "lcov-result-merger": "^3.0.0", |         "lcov-result-merger": "^3.0.0", | ||||||
|         "npm-cli-login": "^0.0.10", |         "npm-cli-login": "^0.0.10", | ||||||
|         "npm-run-all": "^4.1.2", |         "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", |         "version": "1.0.8", | ||||||
|         "changes": [ |         "changes": [ | ||||||
|   | |||||||
| @@ -53,7 +53,13 @@ export { OrderWatcher, OnOrderStateChangeCallback, OrderWatcherConfig } from '@0 | |||||||
|  |  | ||||||
| export import Web3ProviderEngine = require('web3-provider-engine'); | 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'; | export { AbiDecoder } from '@0xproject/utils'; | ||||||
|  |  | ||||||
| @@ -68,7 +74,6 @@ export { | |||||||
|     OrderStateInvalid, |     OrderStateInvalid, | ||||||
|     OrderState, |     OrderState, | ||||||
|     AssetProxyId, |     AssetProxyId, | ||||||
|     SignerType, |  | ||||||
|     ERC20AssetData, |     ERC20AssetData, | ||||||
|     ERC721AssetData, |     ERC721AssetData, | ||||||
|     SignatureType, |     SignatureType, | ||||||
| @@ -85,6 +90,7 @@ export { | |||||||
|     JSONRPCRequestPayload, |     JSONRPCRequestPayload, | ||||||
|     JSONRPCResponsePayload, |     JSONRPCResponsePayload, | ||||||
|     JSONRPCErrorCallback, |     JSONRPCErrorCallback, | ||||||
|  |     JSONRPCResponseError, | ||||||
|     LogEntry, |     LogEntry, | ||||||
|     DecodedLogArgs, |     DecodedLogArgs, | ||||||
|     LogEntryEvent, |     LogEntryEvent, | ||||||
|   | |||||||
| @@ -39,6 +39,7 @@ export { | |||||||
|     JSONRPCRequestPayload, |     JSONRPCRequestPayload, | ||||||
|     JSONRPCResponsePayload, |     JSONRPCResponsePayload, | ||||||
|     JSONRPCErrorCallback, |     JSONRPCErrorCallback, | ||||||
|  |     JSONRPCResponseError, | ||||||
|     AbiDefinition, |     AbiDefinition, | ||||||
|     LogWithDecodedArgs, |     LogWithDecodedArgs, | ||||||
|     FunctionAbi, |     FunctionAbi, | ||||||
|   | |||||||
| @@ -1,22 +1,13 @@ | |||||||
| import { schemas } from '@0xproject/json-schemas'; | 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 { Order, SignedOrder } from '@0xproject/types'; | ||||||
| import { BigNumber } from '@0xproject/utils'; | import { BigNumber, signTypedDataUtils } from '@0xproject/utils'; | ||||||
| import _ = require('lodash'); | import _ = require('lodash'); | ||||||
|  |  | ||||||
| import { ExchangeContract } from '../contract_wrappers/generated/exchange'; | import { ExchangeContract } from '../contract_wrappers/generated/exchange'; | ||||||
|  |  | ||||||
| import { assert } from './assert'; | 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 |  * 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 |  * 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, |             signerAddress, | ||||||
|             data, |             data, | ||||||
|         }; |         }; | ||||||
|         const executeTransactionHashBuff = eip712Utils.structHash( |         const typedData = eip712Utils.createZeroExTransactionTypedData(executeTransactionData, exchangeAddress); | ||||||
|             EIP712_ZEROEX_TRANSACTION_SCHEMA, |         const eip712MessageBuffer = signTypedDataUtils.generateTypedDataHash(typedData); | ||||||
|             executeTransactionData, |         const messageHex = `0x${eip712MessageBuffer.toString('hex')}`; | ||||||
|         ); |  | ||||||
|         const eip721MessageBuffer = eip712Utils.createEIP712Message(executeTransactionHashBuff, exchangeAddress); |  | ||||||
|         const messageHex = `0x${eip721MessageBuffer.toString('hex')}`; |  | ||||||
|         return messageHex; |         return messageHex; | ||||||
|     } |     } | ||||||
|     /** |     /** | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| import { BlockchainLifecycle } from '@0xproject/dev-utils'; | import { BlockchainLifecycle } from '@0xproject/dev-utils'; | ||||||
| import { FillScenarios } from '@0xproject/fill-scenarios'; | import { FillScenarios } from '@0xproject/fill-scenarios'; | ||||||
| import { assetDataUtils, generatePseudoRandomSalt, orderHashUtils, signatureUtils } from '@0xproject/order-utils'; | 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 { BigNumber } from '@0xproject/utils'; | ||||||
| import 'mocha'; | import 'mocha'; | ||||||
|  |  | ||||||
| @@ -80,12 +80,7 @@ describe('TransactionEncoder', () => { | |||||||
|         ): Promise<void> => { |         ): Promise<void> => { | ||||||
|             const salt = generatePseudoRandomSalt(); |             const salt = generatePseudoRandomSalt(); | ||||||
|             const encodedTransaction = encoder.getTransactionHex(data, salt, signerAddress); |             const encodedTransaction = encoder.getTransactionHex(data, salt, signerAddress); | ||||||
|             const signature = await signatureUtils.ecSignOrderHashAsync( |             const signature = await signatureUtils.ecSignHashAsync(provider, encodedTransaction, signerAddress); | ||||||
|                 provider, |  | ||||||
|                 encodedTransaction, |  | ||||||
|                 signerAddress, |  | ||||||
|                 SignerType.Default, |  | ||||||
|             ); |  | ||||||
|             txHash = await contractWrappers.exchange.executeTransactionAsync( |             txHash = await contractWrappers.exchange.executeTransactionAsync( | ||||||
|                 salt, |                 salt, | ||||||
|                 signerAddress, |                 signerAddress, | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { BlockchainLifecycle } from '@0xproject/dev-utils'; | 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 { SignedOrder } from '@0xproject/types'; | ||||||
| import { BigNumber } from '@0xproject/utils'; | import { BigNumber } from '@0xproject/utils'; | ||||||
| import * as chai from 'chai'; | import * as chai from 'chai'; | ||||||
| @@ -126,22 +126,6 @@ describe('Exchange libs', () => { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     describe('LibOrder', () => { |     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', () => { |         describe('getOrderHash', () => { | ||||||
|             it('should output the correct orderHash', async () => { |             it('should output the correct orderHash', async () => { | ||||||
|                 signedOrder = await orderFactory.newSignedOrderAsync(); |                 signedOrder = await orderFactory.newSignedOrderAsync(); | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import { BlockchainLifecycle } from '@0xproject/dev-utils'; | import { BlockchainLifecycle } from '@0xproject/dev-utils'; | ||||||
| import { assetDataUtils, orderHashUtils, signatureUtils } from '@0xproject/order-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 * as chai from 'chai'; | ||||||
| import { LogWithDecodedArgs } from 'ethereum-types'; | import { LogWithDecodedArgs } from 'ethereum-types'; | ||||||
| import ethUtil = require('ethereumjs-util'); | import ethUtil = require('ethereumjs-util'); | ||||||
| @@ -231,10 +231,7 @@ describe('MixinSignatureValidator', () => { | |||||||
|         it('should return true when SignatureType=EthSign and signature is valid', async () => { |         it('should return true when SignatureType=EthSign and signature is valid', async () => { | ||||||
|             // Create EthSign signature |             // Create EthSign signature | ||||||
|             const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); |             const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); | ||||||
|             const orderHashWithEthSignPrefixHex = signatureUtils.addSignedMessagePrefix( |             const orderHashWithEthSignPrefixHex = signatureUtils.addSignedMessagePrefix(orderHashHex); | ||||||
|                 orderHashHex, |  | ||||||
|                 SignerType.Default, |  | ||||||
|             ); |  | ||||||
|             const orderHashWithEthSignPrefixBuffer = ethUtil.toBuffer(orderHashWithEthSignPrefixHex); |             const orderHashWithEthSignPrefixBuffer = ethUtil.toBuffer(orderHashWithEthSignPrefixHex); | ||||||
|             const ecSignature = ethUtil.ecsign(orderHashWithEthSignPrefixBuffer, signerPrivateKey); |             const ecSignature = ethUtil.ecsign(orderHashWithEthSignPrefixBuffer, signerPrivateKey); | ||||||
|             // Create 0x signature from EthSign signature |             // Create 0x signature from EthSign signature | ||||||
| @@ -257,10 +254,7 @@ describe('MixinSignatureValidator', () => { | |||||||
|         it('should return false when SignatureType=EthSign and signature is invalid', async () => { |         it('should return false when SignatureType=EthSign and signature is invalid', async () => { | ||||||
|             // Create EthSign signature |             // Create EthSign signature | ||||||
|             const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); |             const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder); | ||||||
|             const orderHashWithEthSignPrefixHex = signatureUtils.addSignedMessagePrefix( |             const orderHashWithEthSignPrefixHex = signatureUtils.addSignedMessagePrefix(orderHashHex); | ||||||
|                 orderHashHex, |  | ||||||
|                 SignerType.Default, |  | ||||||
|             ); |  | ||||||
|             const orderHashWithEthSignPrefixBuffer = ethUtil.toBuffer(orderHashWithEthSignPrefixHex); |             const orderHashWithEthSignPrefixBuffer = ethUtil.toBuffer(orderHashWithEthSignPrefixHex); | ||||||
|             const ecSignature = ethUtil.ecsign(orderHashWithEthSignPrefixBuffer, signerPrivateKey); |             const ecSignature = ethUtil.ecsign(orderHashWithEthSignPrefixBuffer, signerPrivateKey); | ||||||
|             // Create 0x signature from EthSign signature |             // 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 { SignatureType } from '@0xproject/types'; | ||||||
|  | import { signTypedDataUtils } from '@0xproject/utils'; | ||||||
| import * as ethUtil from 'ethereumjs-util'; | import * as ethUtil from 'ethereumjs-util'; | ||||||
|  |  | ||||||
| import { signingUtils } from './signing_utils'; | import { signingUtils } from './signing_utils'; | ||||||
| import { SignedTransaction } from './types'; | 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 { | export class TransactionFactory { | ||||||
|     private readonly _signerBuff: Buffer; |     private readonly _signerBuff: Buffer; | ||||||
|     private readonly _exchangeAddress: string; |     private readonly _exchangeAddress: string; | ||||||
| @@ -31,12 +23,10 @@ export class TransactionFactory { | |||||||
|             signerAddress, |             signerAddress, | ||||||
|             data, |             data, | ||||||
|         }; |         }; | ||||||
|         const executeTransactionHashBuff = eip712Utils.structHash( |  | ||||||
|             EIP712_ZEROEX_TRANSACTION_SCHEMA, |         const typedData = eip712Utils.createZeroExTransactionTypedData(executeTransactionData, this._exchangeAddress); | ||||||
|             executeTransactionData, |         const eip712MessageBuffer = signTypedDataUtils.generateTypedDataHash(typedData); | ||||||
|         ); |         const signature = signingUtils.signMessage(eip712MessageBuffer, this._privateKey, signatureType); | ||||||
|         const txHash = eip712Utils.createEIP712Message(executeTransactionHashBuff, this._exchangeAddress); |  | ||||||
|         const signature = signingUtils.signMessage(txHash, this._privateKey, signatureType); |  | ||||||
|         const signedTx = { |         const signedTx = { | ||||||
|             exchangeAddress: this._exchangeAddress, |             exchangeAddress: this._exchangeAddress, | ||||||
|             signature: `0x${signature.toString('hex')}`, |             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, |         "timestamp": 1538693146, | ||||||
|         "version": "1.0.11", |         "version": "1.0.11", | ||||||
|   | |||||||
| @@ -113,10 +113,16 @@ export interface JSONRPCRequestPayload { | |||||||
|     jsonrpc: string; |     jsonrpc: string; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export interface JSONRPCResponseError { | ||||||
|  |     message: string; | ||||||
|  |     code: number; | ||||||
|  | } | ||||||
|  |  | ||||||
| export interface JSONRPCResponsePayload { | export interface JSONRPCResponsePayload { | ||||||
|     result: any; |     result: any; | ||||||
|     id: number; |     id: number; | ||||||
|     jsonrpc: string; |     jsonrpc: string; | ||||||
|  |     error?: JSONRPCResponseError; | ||||||
| } | } | ||||||
|  |  | ||||||
| export interface AbstractBlock { | export interface AbstractBlock { | ||||||
|   | |||||||
							
								
								
									
										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 { blockParamSchema, blockRangeSchema } from '../schemas/block_range_schema'; | ||||||
| import { callDataSchema } from '../schemas/call_data_schema'; | import { callDataSchema } from '../schemas/call_data_schema'; | ||||||
| import { ecSignatureParameterSchema, ecSignatureSchema } from '../schemas/ec_signature_schema'; | import { ecSignatureParameterSchema, ecSignatureSchema } from '../schemas/ec_signature_schema'; | ||||||
|  | import { eip712TypedDataSchema } from '../schemas/eip712_typed_data'; | ||||||
| import { indexFilterValuesSchema } from '../schemas/index_filter_values_schema'; | import { indexFilterValuesSchema } from '../schemas/index_filter_values_schema'; | ||||||
| import { orderCancellationRequestsSchema } from '../schemas/order_cancel_schema'; | import { orderCancellationRequestsSchema } from '../schemas/order_cancel_schema'; | ||||||
| import { orderFillOrKillRequestsSchema } from '../schemas/order_fill_or_kill_requests_schema'; | import { orderFillOrKillRequestsSchema } from '../schemas/order_fill_or_kill_requests_schema'; | ||||||
| @@ -31,6 +32,7 @@ import { relayerApiOrdersSchema } from '../schemas/relayer_api_orders_schema'; | |||||||
| import { signedOrdersSchema } from '../schemas/signed_orders_schema'; | import { signedOrdersSchema } from '../schemas/signed_orders_schema'; | ||||||
| import { tokenSchema } from '../schemas/token_schema'; | import { tokenSchema } from '../schemas/token_schema'; | ||||||
| import { jsNumber, txDataSchema } from '../schemas/tx_data_schema'; | import { jsNumber, txDataSchema } from '../schemas/tx_data_schema'; | ||||||
|  | import { zeroExTransactionSchema } from '../schemas/zero_ex_transaction_schema'; | ||||||
|  |  | ||||||
| export const schemas = { | export const schemas = { | ||||||
|     numberSchema, |     numberSchema, | ||||||
| @@ -39,6 +41,7 @@ export const schemas = { | |||||||
|     hexSchema, |     hexSchema, | ||||||
|     ecSignatureParameterSchema, |     ecSignatureParameterSchema, | ||||||
|     ecSignatureSchema, |     ecSignatureSchema, | ||||||
|  |     eip712TypedDataSchema, | ||||||
|     indexFilterValuesSchema, |     indexFilterValuesSchema, | ||||||
|     orderCancellationRequestsSchema, |     orderCancellationRequestsSchema, | ||||||
|     orderFillOrKillRequestsSchema, |     orderFillOrKillRequestsSchema, | ||||||
| @@ -68,4 +71,5 @@ export const schemas = { | |||||||
|     relayerApiOrdersChannelUpdateSchema, |     relayerApiOrdersChannelUpdateSchema, | ||||||
|     relayerApiOrdersResponseSchema, |     relayerApiOrdersResponseSchema, | ||||||
|     relayerApiAssetDataPairsSchema, |     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", |         "version": "1.0.7", | ||||||
|         "changes": [ |         "changes": [ | ||||||
|   | |||||||
| @@ -13,4 +13,39 @@ export const constants = { | |||||||
|     BASE_16: 16, |     BASE_16: 16, | ||||||
|     INFINITE_TIMESTAMP_SEC: new BigNumber(2524604400), // Close to infinite |     INFINITE_TIMESTAMP_SEC: new BigNumber(2524604400), // Close to infinite | ||||||
|     ZERO_AMOUNT: new BigNumber(0), |     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 * as _ from 'lodash'; | ||||||
|  |  | ||||||
| import { crypto } from './crypto'; | import { constants } from './constants'; | ||||||
| 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 }, |  | ||||||
|     ], |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export const eip712Utils = { | export const eip712Utils = { | ||||||
|     /** |     /** | ||||||
|      * Compiles the EIP712Schema and returns the hash of the schema. |      * Creates a EIP712TypedData object specific to the 0x protocol for use with signTypedData. | ||||||
|      * @param   schema The EIP712 schema. |      * @param   primaryType The primary type found in message | ||||||
|      * @return  The hash of the compiled schema |      * @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 { |     createTypedData: ( | ||||||
|         const eip712Schema = eip712Utils._encodeType(schema); |         primaryType: string, | ||||||
|         const eip712SchemaHashBuffer = crypto.solSHA3([eip712Schema]); |         types: EIP712Types, | ||||||
|         return eip712SchemaHashBuffer; |         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. |      * Creates an Order EIP712TypedData object for use with signTypedData. | ||||||
|      * @param   hashStruct the EIP712 hash of a struct |      * @param   Order the order | ||||||
|      * @param   contractAddress the exchange contract address |      * @return  A typed data object | ||||||
|      * @return  The hash of an EIP712 message with domain separator prefixed |  | ||||||
|      */ |      */ | ||||||
|     createEIP712Message(hashStruct: Buffer, contractAddress: string): Buffer { |     createOrderTypedData: (order: Order): EIP712TypedData => { | ||||||
|         const domainSeparatorHashBuffer = eip712Utils._getDomainSeparatorHashBuffer(contractAddress); |         assert.doesConformToSchema('order', order, schemas.orderSchema, [schemas.hexSchema]); | ||||||
|         const messageBuff = crypto.solSHA3([EIP191_PREFIX, domainSeparatorHashBuffer, hashStruct]); |         const normalizedOrder = _.mapValues(order, value => { | ||||||
|         return messageBuff; |             return !_.isString(value) ? value.toString() : value; | ||||||
|     }, |  | ||||||
|     /** |  | ||||||
|      * 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, |  | ||||||
|         }); |         }); | ||||||
|         const domainSeparatorHashBuff2 = crypto.solSHA3([domainSeparatorSchemaBuffer, ...encodedData]); |         const typedData = eip712Utils.createTypedData( | ||||||
|         return domainSeparatorHashBuff2; |             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}`); |      * Creates an ExecuteTransaction EIP712TypedData object for use with signTypedData and | ||||||
|         const namedTypesJoined = namedTypes.join(','); |      * 0x Exchange executeTransaction. | ||||||
|         const encodedType = `${schema.name}(${namedTypesJoined})`; |      * @param   ZeroExTransaction the 0x transaction | ||||||
|         return encodedType; |      * @param   exchangeAddress The address of the exchange contract | ||||||
|     }, |      * @return  A typed data object | ||||||
|     _encodeData(schema: EIP712Schema, data: { [key: string]: any }): any { |      */ | ||||||
|         const encodedValues = []; |     createZeroExTransactionTypedData: ( | ||||||
|         for (const parameter of schema.parameters) { |         zeroExTransaction: ZeroExTransaction, | ||||||
|             const value = data[parameter.name]; |         exchangeAddress: string, | ||||||
|             if (parameter.type === EIP712Types.String || parameter.type === EIP712Types.Bytes) { |     ): EIP712TypedData => { | ||||||
|                 encodedValues.push(crypto.solSHA3([ethUtil.toBuffer(value)])); |         assert.isETHAddressHex('exchangeAddress', exchangeAddress); | ||||||
|             } else if (parameter.type === EIP712Types.Uint256) { |         assert.doesConformToSchema('zeroExTransaction', zeroExTransaction, schemas.zeroExTransactionSchema); | ||||||
|                 encodedValues.push(value); |         const normalizedTransaction = _.mapValues(zeroExTransaction, value => { | ||||||
|             } else if (parameter.type === EIP712Types.Address) { |             return !_.isString(value) ? value.toString() : value; | ||||||
|                 encodedValues.push(eip712Utils.pad32Address(value)); |         }); | ||||||
|             } else { |         const typedData = eip712Utils.createTypedData( | ||||||
|                 throw new Error(`Unable to encode ${parameter.type}`); |             constants.EIP712_ZEROEX_TRANSACTION_SCHEMA.name, | ||||||
|             } |             { ZeroExTransaction: constants.EIP712_ZEROEX_TRANSACTION_SCHEMA.parameters }, | ||||||
|         } |             normalizedTransaction, | ||||||
|         return encodedValues; |             exchangeAddress, | ||||||
|  |         ); | ||||||
|  |         return typedData; | ||||||
|     }, |     }, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -2,7 +2,6 @@ export { orderHashUtils } from './order_hash'; | |||||||
| export { signatureUtils } from './signature_utils'; | export { signatureUtils } from './signature_utils'; | ||||||
| export { generatePseudoRandomSalt } from './salt'; | export { generatePseudoRandomSalt } from './salt'; | ||||||
| export { assetDataUtils } from './asset_data_utils'; | export { assetDataUtils } from './asset_data_utils'; | ||||||
| export { eip712Utils } from './eip712_utils'; |  | ||||||
| export { marketUtils } from './market_utils'; | export { marketUtils } from './market_utils'; | ||||||
| export { rateUtils } from './rate_utils'; | export { rateUtils } from './rate_utils'; | ||||||
| export { sortingUtils } from './sorting_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 { BalanceAndProxyAllowanceLazyStore } from './store/balance_and_proxy_allowance_lazy_store'; | ||||||
| export { OrderFilledCancelledLazyStore } from './store/order_filled_cancelled_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 { | export { | ||||||
|     SignedOrder, |     SignedOrder, | ||||||
|     Order, |     Order, | ||||||
| @@ -29,17 +38,19 @@ export { | |||||||
|     ERC20AssetData, |     ERC20AssetData, | ||||||
|     ERC721AssetData, |     ERC721AssetData, | ||||||
|     AssetProxyId, |     AssetProxyId, | ||||||
|     SignerType, |  | ||||||
|     SignatureType, |     SignatureType, | ||||||
|     OrderStateValid, |     OrderStateValid, | ||||||
|     OrderStateInvalid, |     OrderStateInvalid, | ||||||
|     ExchangeContractErrs, |     ExchangeContractErrs, | ||||||
|  |     EIP712Parameter, | ||||||
|  |     EIP712TypedData, | ||||||
|  |     EIP712Types, | ||||||
|  |     EIP712Object, | ||||||
|  |     EIP712ObjectValue, | ||||||
|  |     ZeroExTransaction, | ||||||
| } from '@0xproject/types'; | } from '@0xproject/types'; | ||||||
| export { | export { | ||||||
|     OrderError, |     OrderError, | ||||||
|     EIP712Parameter, |  | ||||||
|     EIP712Schema, |  | ||||||
|     EIP712Types, |  | ||||||
|     TradeSide, |     TradeSide, | ||||||
|     TransferType, |     TransferType, | ||||||
|     FindFeeOrdersThatCoverFeesForTargetOrdersOpts, |     FindFeeOrdersThatCoverFeesForTargetOrdersOpts, | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { Order, SignedOrder, SignerType } from '@0xproject/types'; | import { Order, SignedOrder } from '@0xproject/types'; | ||||||
| import { BigNumber } from '@0xproject/utils'; | import { BigNumber } from '@0xproject/utils'; | ||||||
| import { Provider } from 'ethereum-types'; | import { Provider } from 'ethereum-types'; | ||||||
| import * as _ from 'lodash'; | import * as _ from 'lodash'; | ||||||
| @@ -71,12 +71,7 @@ export const orderFactory = { | |||||||
|             createOrderOpts, |             createOrderOpts, | ||||||
|         ); |         ); | ||||||
|         const orderHash = orderHashUtils.getOrderHashHex(order); |         const orderHash = orderHashUtils.getOrderHashHex(order); | ||||||
|         const signature = await signatureUtils.ecSignOrderHashAsync( |         const signature = await signatureUtils.ecSignHashAsync(provider, orderHash, makerAddress); | ||||||
|             provider, |  | ||||||
|             orderHash, |  | ||||||
|             makerAddress, |  | ||||||
|             SignerType.Default, |  | ||||||
|         ); |  | ||||||
|         const signedOrder: SignedOrder = _.assign(order, { signature }); |         const signedOrder: SignedOrder = _.assign(order, { signature }); | ||||||
|         return signedOrder; |         return signedOrder; | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -1,31 +1,13 @@ | |||||||
| import { schemas, SchemaValidator } from '@0xproject/json-schemas'; | import { schemas, SchemaValidator } from '@0xproject/json-schemas'; | ||||||
| import { Order, SignedOrder } from '@0xproject/types'; | import { Order, SignedOrder } from '@0xproject/types'; | ||||||
|  | import { signTypedDataUtils } from '@0xproject/utils'; | ||||||
| import * as _ from 'lodash'; | import * as _ from 'lodash'; | ||||||
|  |  | ||||||
| import { assert } from './assert'; | import { assert } from './assert'; | ||||||
| import { eip712Utils } from './eip712_utils'; | import { eip712Utils } from './eip712_utils'; | ||||||
| import { EIP712Schema, EIP712Types } from './types'; |  | ||||||
|  |  | ||||||
| const INVALID_TAKER_FORMAT = 'instance.takerAddress is not of a type(s) string'; | 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 = { | export const orderHashUtils = { | ||||||
|     /** |     /** | ||||||
|      * Checks if the supplied hex encoded order hash is valid. |      * Checks if the supplied hex encoded order hash is valid. | ||||||
| @@ -45,7 +27,7 @@ export const orderHashUtils = { | |||||||
|     /** |     /** | ||||||
|      * Computes the orderHash for a supplied order. |      * Computes the orderHash for a supplied order. | ||||||
|      * @param   order   An object that conforms to the Order or SignedOrder interface definitions. |      * @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 { |     getOrderHashHex(order: SignedOrder | Order): string { | ||||||
|         try { |         try { | ||||||
| @@ -64,16 +46,13 @@ export const orderHashUtils = { | |||||||
|         return orderHashHex; |         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. |      * @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 { |     getOrderHashBuffer(order: SignedOrder | Order): Buffer { | ||||||
|         const orderParamsHashBuff = eip712Utils.structHash(EIP712_ORDER_SCHEMA, order); |         const typedData = eip712Utils.createOrderTypedData(order); | ||||||
|         const orderHashBuff = eip712Utils.createEIP712Message(orderParamsHashBuff, order.exchangeAddress); |         const orderHashBuff = signTypedDataUtils.generateTypedDataHash(typedData); | ||||||
|         return orderHashBuff; |         return orderHashBuff; | ||||||
|     }, |     }, | ||||||
|     _getOrderSchemaBuffer(): Buffer { |  | ||||||
|         return eip712Utils.compileSchema(EIP712_ORDER_SCHEMA); |  | ||||||
|     }, |  | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { schemas } from '@0xproject/json-schemas'; | 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 { Web3Wrapper } from '@0xproject/web3-wrapper'; | ||||||
| import { Provider } from 'ethereum-types'; | import { Provider } from 'ethereum-types'; | ||||||
| import * as ethUtil from 'ethereumjs-util'; | import * as ethUtil from 'ethereumjs-util'; | ||||||
| @@ -7,9 +7,11 @@ import * as _ from 'lodash'; | |||||||
|  |  | ||||||
| import { artifacts } from './artifacts'; | import { artifacts } from './artifacts'; | ||||||
| import { assert } from './assert'; | import { assert } from './assert'; | ||||||
|  | import { eip712Utils } from './eip712_utils'; | ||||||
| import { ExchangeContract } from './generated_contract_wrappers/exchange'; | import { ExchangeContract } from './generated_contract_wrappers/exchange'; | ||||||
| import { IValidatorContract } from './generated_contract_wrappers/i_validator'; | import { IValidatorContract } from './generated_contract_wrappers/i_validator'; | ||||||
| import { IWalletContract } from './generated_contract_wrappers/i_wallet'; | import { IWalletContract } from './generated_contract_wrappers/i_wallet'; | ||||||
|  | import { orderHashUtils } from './order_hash'; | ||||||
| import { OrderError } from './types'; | import { OrderError } from './types'; | ||||||
| import { utils } from './utils'; | import { utils } from './utils'; | ||||||
|  |  | ||||||
| @@ -49,7 +51,7 @@ export const signatureUtils = { | |||||||
|  |  | ||||||
|             case SignatureType.EthSign: { |             case SignatureType.EthSign: { | ||||||
|                 const ecSignature = signatureUtils.parseECSignature(signature); |                 const ecSignature = signatureUtils.parseECSignature(signature); | ||||||
|                 const prefixedMessageHex = signatureUtils.addSignedMessagePrefix(data, SignerType.Default); |                 const prefixedMessageHex = signatureUtils.addSignedMessagePrefix(data); | ||||||
|                 return signatureUtils.isValidECSignature(prefixedMessageHex, ecSignature, signerAddress); |                 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. |      * Signs an order and returns a SignedOrder. First `eth_signTypedData` is requested | ||||||
|      * This method currently supports TestRPC, Geth and Parity above and below V1.6.6 |      * then a fallback to `eth_sign` if not available on the supplied provider. | ||||||
|      * @param   orderHash       Hex encoded orderHash to sign. |      * @param   order The Order to sign. | ||||||
|      * @param   signerAddress   The hex encoded Ethereum address you wish to sign it with. This address |      * @param   signerAddress   The hex encoded Ethereum address you wish to sign it with. This address | ||||||
|      *          must be available via the Provider supplied to 0x.js. |      *          must be available via the supplied Provider. | ||||||
|      * @param   signerType Different signers add/require different prefixes to be prepended to the message being signed. |      * @return  A SignedOrder containing the order and Elliptic curve signature with Signature Type. | ||||||
|      *          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. |  | ||||||
|      */ |      */ | ||||||
|     async ecSignOrderHashAsync( |     async ecSignOrderAsync(provider: Provider, order: Order, signerAddress: string): Promise<SignedOrder> { | ||||||
|         provider: Provider, |         assert.doesConformToSchema('order', order, schemas.orderSchema, [schemas.hexSchema]); | ||||||
|         orderHash: string, |         try { | ||||||
|         signerAddress: string, |             const signedOrder = await signatureUtils.ecSignTypedDataOrderAsync(provider, order, signerAddress); | ||||||
|         signerType: SignerType, |             return signedOrder; | ||||||
|     ): Promise<string> { |         } 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.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); |         assert.isETHAddressHex('signerAddress', signerAddress); | ||||||
|         const web3Wrapper = new Web3Wrapper(provider); |         const web3Wrapper = new Web3Wrapper(provider); | ||||||
|         await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper); |         await assert.isSenderAddressAsync('signerAddress', signerAddress, web3Wrapper); | ||||||
|         const normalizedSignerAddress = signerAddress.toLowerCase(); |         const normalizedSignerAddress = signerAddress.toLowerCase(); | ||||||
|  |         const signature = await web3Wrapper.signMessageAsync(normalizedSignerAddress, msgHash); | ||||||
|         let msgHashHex = orderHash; |         const prefixedMsgHashHex = signatureUtils.addSignedMessagePrefix(msgHash); | ||||||
|         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); |  | ||||||
|  |  | ||||||
|         // HACK: There is no consensus on whether the signatureHex string should be formatted as |         // 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) |         // 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, |                 normalizedSignerAddress, | ||||||
|             ); |             ); | ||||||
|             if (isValidRSVSignature) { |             if (isValidRSVSignature) { | ||||||
|                 const convertedSignatureHex = signatureUtils.convertECSignatureToSignatureHex( |                 const convertedSignatureHex = signatureUtils.convertECSignatureToSignatureHex(ecSignatureRSV); | ||||||
|                     ecSignatureRSV, |  | ||||||
|                     signerType, |  | ||||||
|                 ); |  | ||||||
|                 return convertedSignatureHex; |                 return convertedSignatureHex; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -253,41 +306,30 @@ export const signatureUtils = { | |||||||
|                 normalizedSignerAddress, |                 normalizedSignerAddress, | ||||||
|             ); |             ); | ||||||
|             if (isValidVRSSignature) { |             if (isValidVRSSignature) { | ||||||
|                 const convertedSignatureHex = signatureUtils.convertECSignatureToSignatureHex( |                 const convertedSignatureHex = signatureUtils.convertECSignatureToSignatureHex(ecSignatureVRS); | ||||||
|                     ecSignatureVRS, |  | ||||||
|                     signerType, |  | ||||||
|                 ); |  | ||||||
|                 return convertedSignatureHex; |                 return convertedSignatureHex; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |         // Detect if Metamask to transition users to the MetamaskSubprovider | ||||||
|         throw new Error(OrderError.InvalidSignature); |         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 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 |      * @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([ |         const signatureBuffer = Buffer.concat([ | ||||||
|             ethUtil.toBuffer(ecSignature.v), |             ethUtil.toBuffer(ecSignature.v), | ||||||
|             ethUtil.toBuffer(ecSignature.r), |             ethUtil.toBuffer(ecSignature.r), | ||||||
|             ethUtil.toBuffer(ecSignature.s), |             ethUtil.toBuffer(ecSignature.s), | ||||||
|         ]); |         ]); | ||||||
|         const signatureHex = `0x${signatureBuffer.toString('hex')}`; |         const signatureHex = `0x${signatureBuffer.toString('hex')}`; | ||||||
|         let signatureType; |         const signatureWithType = signatureUtils.convertToSignatureWithType(signatureHex, SignatureType.EthSign); | ||||||
|         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); |  | ||||||
|         return signatureWithType; |         return signatureWithType; | ||||||
|     }, |     }, | ||||||
|     /** |     /** | ||||||
| @@ -304,28 +346,17 @@ export const signatureUtils = { | |||||||
|     /** |     /** | ||||||
|      * Adds the relevant prefix to the message being signed. |      * Adds the relevant prefix to the message being signed. | ||||||
|      * @param message Message to sign |      * @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 |      * @return Prefixed message | ||||||
|      */ |      */ | ||||||
|     addSignedMessagePrefix(message: string, signerType: SignerType = SignerType.Default): string { |     addSignedMessagePrefix(message: string): string { | ||||||
|         assert.isString('message', message); |         assert.isString('message', message); | ||||||
|         assert.doesBelongToStringEnum('signerType', signerType, SignerType); |         const msgBuff = ethUtil.toBuffer(message); | ||||||
|         switch (signerType) { |         const prefixedMsgBuff = ethUtil.hashPersonalMessage(msgBuff); | ||||||
|             case SignerType.Metamask: |         const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff); | ||||||
|             case SignerType.Ledger: |         return prefixedMsgHex; | ||||||
|             case SignerType.Default: { |  | ||||||
|                 const msgBuff = ethUtil.toBuffer(message); |  | ||||||
|                 const prefixedMsgBuff = ethUtil.hashPersonalMessage(msgBuff); |  | ||||||
|                 const prefixedMsgHex = ethUtil.bufferToHex(prefixedMsgBuff); |  | ||||||
|                 return prefixedMsgHex; |  | ||||||
|             } |  | ||||||
|             default: |  | ||||||
|                 throw new Error(`Unrecognized SignerType: ${signerType}`); |  | ||||||
|         } |  | ||||||
|     }, |     }, | ||||||
|     /** |     /** | ||||||
|      * Parse a 0x protocol hex-encoded signature string into it's ECSignature components |      * Parse a 0x protocol hex-encoded signature string into its ECSignature components | ||||||
|      * @param signature A hex encoded ecSignature 0x Protocol signature |      * @param signature A hex encoded ecSignature 0x Protocol signature | ||||||
|      * @return An ECSignature object with r,s,v parameters |      * @return An ECSignature object with r,s,v parameters | ||||||
|      */ |      */ | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ import { BigNumber } from '@0xproject/utils'; | |||||||
|  |  | ||||||
| export enum OrderError { | export enum OrderError { | ||||||
|     InvalidSignature = 'INVALID_SIGNATURE', |     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 { | export enum TradeSide { | ||||||
| @@ -14,24 +15,6 @@ export enum TransferType { | |||||||
|     Fee = 'fee', |     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 { | export interface CreateOrderOpts { | ||||||
|     takerAddress?: string; |     takerAddress?: string; | ||||||
|     senderAddress?: 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); |             const orderHash = orderHashUtils.getOrderHashHex(order); | ||||||
|             expect(orderHash).to.be.equal(expectedOrderHash); |             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 () => { |         it('throws a readable error message if taker format is invalid', async () => { | ||||||
|             const orderWithInvalidtakerFormat = { |             const orderWithInvalidtakerFormat = { | ||||||
|                 ...order, |                 ...order, | ||||||
|   | |||||||
| @@ -1,12 +1,13 @@ | |||||||
| import { SignerType } from '@0xproject/types'; | import { Order, SignatureType } from '@0xproject/types'; | ||||||
| import { BigNumber } from '@0xproject/utils'; | import { BigNumber } from '@0xproject/utils'; | ||||||
| import * as chai from 'chai'; | import * as chai from 'chai'; | ||||||
| import { JSONRPCErrorCallback, JSONRPCRequestPayload } from 'ethereum-types'; | import { JSONRPCErrorCallback, JSONRPCRequestPayload } from 'ethereum-types'; | ||||||
|  | import * as ethUtil from 'ethereumjs-util'; | ||||||
| import * as _ from 'lodash'; | import * as _ from 'lodash'; | ||||||
| import 'mocha'; | 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 { signatureUtils } from '../src/signature_utils'; | ||||||
|  |  | ||||||
| import { chaiSetup } from './utils/chai_setup'; | import { chaiSetup } from './utils/chai_setup'; | ||||||
| @@ -16,6 +17,28 @@ chaiSetup.configure(); | |||||||
| const expect = chai.expect; | const expect = chai.expect; | ||||||
|  |  | ||||||
| describe('Signature utils', () => { | describe('Signature utils', () => { | ||||||
|  |     let makerAddress: string; | ||||||
|  |     const fakeExchangeContractAddress = '0x1dc4c1cefef38a777b15aa20260a54e584b16c48'; | ||||||
|  |     let order: Order; | ||||||
|  |     before(async () => { | ||||||
|  |         const availableAddreses = await web3Wrapper.getAvailableAddressesAsync(); | ||||||
|  |         makerAddress = availableAddreses[0]; | ||||||
|  |         order = { | ||||||
|  |             makerAddress, | ||||||
|  |             takerAddress: constants.NULL_ADDRESS, | ||||||
|  |             senderAddress: constants.NULL_ADDRESS, | ||||||
|  |             feeRecipientAddress: constants.NULL_ADDRESS, | ||||||
|  |             makerAssetData: constants.NULL_ADDRESS, | ||||||
|  |             takerAssetData: constants.NULL_ADDRESS, | ||||||
|  |             exchangeAddress: fakeExchangeContractAddress, | ||||||
|  |             salt: new BigNumber(0), | ||||||
|  |             makerFee: new BigNumber(0), | ||||||
|  |             takerFee: new BigNumber(0), | ||||||
|  |             makerAssetAmount: new BigNumber(0), | ||||||
|  |             takerAssetAmount: new BigNumber(0), | ||||||
|  |             expirationTimeSeconds: new BigNumber(0), | ||||||
|  |         }; | ||||||
|  |     }); | ||||||
|     describe('#isValidSignatureAsync', () => { |     describe('#isValidSignatureAsync', () => { | ||||||
|         let dataHex = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0'; |         let dataHex = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0'; | ||||||
|         const ethSignSignature = |         const ethSignSignature = | ||||||
| @@ -115,28 +138,64 @@ describe('Signature utils', () => { | |||||||
|             expect(salt.lessThan(twoPow256)).to.be.true(); |             expect(salt.lessThan(twoPow256)).to.be.true(); | ||||||
|         }); |         }); | ||||||
|     }); |     }); | ||||||
|     describe('#ecSignOrderHashAsync', () => { |     describe('#ecSignOrderAsync', () => { | ||||||
|         let stubs: Sinon.SinonStub[] = []; |         it('should default to eth_sign if eth_signTypedData is unavailable', async () => { | ||||||
|         let makerAddress: string; |             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 () => { |         before(async () => { | ||||||
|             const availableAddreses = await web3Wrapper.getAvailableAddressesAsync(); |             const availableAddreses = await web3Wrapper.getAvailableAddressesAsync(); | ||||||
|             makerAddress = availableAddreses[0]; |             makerAddress = availableAddreses[0]; | ||||||
|         }); |         }); | ||||||
|         afterEach(() => { |         it('should return the correct Signature', async () => { | ||||||
|             // clean up any stubs after the test has completed |  | ||||||
|             _.each(stubs, s => s.restore()); |  | ||||||
|             stubs = []; |  | ||||||
|         }); |  | ||||||
|         it('Should return the correct Signature', async () => { |  | ||||||
|             const orderHash = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0'; |             const orderHash = '0x6927e990021d23b1eb7b8789f6a6feaf98fe104bb0cf8259421b79f9a34222b0'; | ||||||
|             const expectedSignature = |             const expectedSignature = | ||||||
|                 '0x1b61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace225403'; |                 '0x1b61a3ed31b43c8780e905a260a35faefcc527be7516aa11c0256729b5b351bc3340349190569279751135161d22529dc25add4f6069af05be04cacbda2ace225403'; | ||||||
|             const ecSignature = await signatureUtils.ecSignOrderHashAsync( |             const ecSignature = await signatureUtils.ecSignHashAsync(provider, orderHash, makerAddress); | ||||||
|                 provider, |  | ||||||
|                 orderHash, |  | ||||||
|                 makerAddress, |  | ||||||
|                 SignerType.Default, |  | ||||||
|             ); |  | ||||||
|             expect(ecSignature).to.equal(expectedSignature); |             expect(ecSignature).to.equal(expectedSignature); | ||||||
|         }); |         }); | ||||||
|         it('should return the correct Signature for signatureHex concatenated as R + S + V', async () => { |         it('should return the correct Signature for signatureHex concatenated as R + S + V', async () => { | ||||||
| @@ -162,12 +221,7 @@ describe('Signature utils', () => { | |||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|             }; |             }; | ||||||
|             const ecSignature = await signatureUtils.ecSignOrderHashAsync( |             const ecSignature = await signatureUtils.ecSignHashAsync(fakeProvider, orderHash, makerAddress); | ||||||
|                 fakeProvider, |  | ||||||
|                 orderHash, |  | ||||||
|                 makerAddress, |  | ||||||
|                 SignerType.Default, |  | ||||||
|             ); |  | ||||||
|             expect(ecSignature).to.equal(expectedSignature); |             expect(ecSignature).to.equal(expectedSignature); | ||||||
|         }); |         }); | ||||||
|         it('should return the correct Signature for signatureHex concatenated as V + R + S', async () => { |         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( |             const ecSignature = await signatureUtils.ecSignHashAsync(fakeProvider, orderHash, makerAddress); | ||||||
|                 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, |  | ||||||
|             ); |  | ||||||
|             expect(ecSignature).to.equal(expectedSignature); |             expect(ecSignature).to.equal(expectedSignature); | ||||||
|         }); |         }); | ||||||
|         it('should return a valid signature', async () => { |         it('should return a valid signature', async () => { | ||||||
|             const orderHash = '0x34decbedc118904df65f379a175bb39ca18209d6ce41d5ed549d54e6e0a95004'; |             const orderHash = '0x34decbedc118904df65f379a175bb39ca18209d6ce41d5ed549d54e6e0a95004'; | ||||||
|             const ecSignature = await signatureUtils.ecSignOrderHashAsync( |             const ecSignature = await signatureUtils.ecSignHashAsync(provider, orderHash, makerAddress); | ||||||
|                 provider, |  | ||||||
|                 orderHash, |  | ||||||
|                 makerAddress, |  | ||||||
|                 SignerType.Default, |  | ||||||
|             ); |  | ||||||
|  |  | ||||||
|             const isValidSignature = await signatureUtils.isValidSignatureAsync( |             const isValidSignature = await signatureUtils.isValidSignatureAsync( | ||||||
|                 provider, |                 provider, | ||||||
| @@ -250,44 +260,65 @@ describe('Signature utils', () => { | |||||||
|             expect(isValidSignature).to.be.true(); |             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', () => { |     describe('#convertECSignatureToSignatureHex', () => { | ||||||
|         const ecSignature: ECSignature = { |         const ecSignature: ECSignature = { | ||||||
|             v: 27, |             v: 27, | ||||||
|             r: '0xaca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d64393', |             r: '0xaca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d64393', | ||||||
|             s: '0x46b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf2', |             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 = |             const expectedSignatureWithSignatureType = | ||||||
|                 '0x1baca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d6439346b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf203'; |                 '0x1baca7da997ad177f040240cdccf6905b71ab16b74434388c3a72f34fd25d6439346b2bac274ff29b48b3ea6e2d04c1336eaceafda3c53ab483fc3ff12fac3ebf203'; | ||||||
|             const signatureWithSignatureType = signatureUtils.convertECSignatureToSignatureHex( |             const signatureWithSignatureType = signatureUtils.convertECSignatureToSignatureHex(ecSignature); | ||||||
|                 ecSignature, |  | ||||||
|                 SignerType.Default, |  | ||||||
|             ); |  | ||||||
|             expect(signatureWithSignatureType).to.equal(expectedSignatureWithSignatureType); |             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 { OnOrderStateChangeCallback, OrderWatcherConfig } from './types'; | ||||||
|  |  | ||||||
| export { SignedOrder } from '@0xproject/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 { RevertTraceSubprovider } from './revert_trace_subprovider'; | ||||||
|  |  | ||||||
| export { ContractData } from './types'; | export { ContractData } from './types'; | ||||||
| export { JSONRPCRequestPayload, Provider, JSONRPCErrorCallback, JSONRPCResponsePayload } from 'ethereum-types'; | export { | ||||||
|  |     JSONRPCRequestPayload, | ||||||
|  |     Provider, | ||||||
|  |     JSONRPCErrorCallback, | ||||||
|  |     JSONRPCResponsePayload, | ||||||
|  |     JSONRPCResponseError, | ||||||
|  | } from 'ethereum-types'; | ||||||
|  |  | ||||||
| export { | export { | ||||||
|     JSONRPCRequestPayloadWithMethod, |     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", |         "version": "2.0.7", | ||||||
|         "changes": [ |         "changes": [ | ||||||
|   | |||||||
| @@ -45,7 +45,7 @@ | |||||||
|         "ethereum-types": "^1.0.11", |         "ethereum-types": "^1.0.11", | ||||||
|         "ethereumjs-tx": "^1.3.5", |         "ethereumjs-tx": "^1.3.5", | ||||||
|         "ethereumjs-util": "^5.1.1", |         "ethereumjs-util": "^5.1.1", | ||||||
|         "ganache-core": "0xProject/ganache-core#monorepo-dep", |         "ganache-core": "^2.2.1", | ||||||
|         "hdkey": "^0.7.1", |         "hdkey": "^0.7.1", | ||||||
|         "json-rpc-error": "2.0.0", |         "json-rpc-error": "2.0.0", | ||||||
|         "lodash": "^4.17.5", |         "lodash": "^4.17.5", | ||||||
|   | |||||||
| @@ -27,6 +27,7 @@ export { Subprovider } from './subproviders/subprovider'; | |||||||
| export { NonceTrackerSubprovider } from './subproviders/nonce_tracker'; | export { NonceTrackerSubprovider } from './subproviders/nonce_tracker'; | ||||||
| export { PrivateKeyWalletSubprovider } from './subproviders/private_key_wallet'; | export { PrivateKeyWalletSubprovider } from './subproviders/private_key_wallet'; | ||||||
| export { MnemonicWalletSubprovider } from './subproviders/mnemonic_wallet'; | export { MnemonicWalletSubprovider } from './subproviders/mnemonic_wallet'; | ||||||
|  | export { MetamaskSubprovider } from './subproviders/metamask_subprovider'; | ||||||
| export { EthLightwalletSubprovider } from './subproviders/eth_lightwallet_subprovider'; | export { EthLightwalletSubprovider } from './subproviders/eth_lightwallet_subprovider'; | ||||||
|  |  | ||||||
| export { | export { | ||||||
| @@ -47,6 +48,19 @@ export { | |||||||
|     LedgerGetAddressResult, |     LedgerGetAddressResult, | ||||||
| } from './types'; | } 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 getAccountsAsync(): Promise<string[]>; | ||||||
|     public abstract async signTransactionAsync(txParams: PartialTxParams): Promise<string>; |     public abstract async signTransactionAsync(txParams: PartialTxParams): Promise<string>; | ||||||
|     public abstract async signPersonalMessageAsync(data: string, address: string): 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. |      * 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> { |     public async handleRequest(payload: JSONRPCRequestPayload, next: Callback, end: ErrorCallback): Promise<void> { | ||||||
|         let accounts; |         let accounts; | ||||||
|         let txParams; |         let txParams; | ||||||
|  |         let address; | ||||||
|  |         let typedData; | ||||||
|         switch (payload.method) { |         switch (payload.method) { | ||||||
|             case 'eth_coinbase': |             case 'eth_coinbase': | ||||||
|                 try { |                 try { | ||||||
| @@ -86,7 +89,7 @@ export abstract class BaseWalletSubprovider extends Subprovider { | |||||||
|             case 'eth_sign': |             case 'eth_sign': | ||||||
|             case 'personal_sign': |             case 'personal_sign': | ||||||
|                 const data = payload.method === 'eth_sign' ? payload.params[1] : payload.params[0]; |                 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 { |                 try { | ||||||
|                     const ecSignatureHex = await this.signPersonalMessageAsync(data, address); |                     const ecSignatureHex = await this.signPersonalMessageAsync(data, address); | ||||||
|                     end(null, ecSignatureHex); |                     end(null, ecSignatureHex); | ||||||
| @@ -94,6 +97,15 @@ export abstract class BaseWalletSubprovider extends Subprovider { | |||||||
|                     end(err); |                     end(err); | ||||||
|                 } |                 } | ||||||
|                 return; |                 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: |             default: | ||||||
|                 next(); |                 next(); | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | import { EIP712TypedData } from '@0xproject/types'; | ||||||
| import * as lightwallet from 'eth-lightwallet'; | import * as lightwallet from 'eth-lightwallet'; | ||||||
|  |  | ||||||
| import { PartialTxParams } from '../types'; | import { PartialTxParams } from '../types'; | ||||||
| @@ -48,16 +49,16 @@ export class EthLightwalletSubprovider extends BaseWalletSubprovider { | |||||||
|         // Lightwallet loses the chain id information when hex encoding the transaction |         // Lightwallet loses the chain id information when hex encoding the transaction | ||||||
|         // this results in a different signature on certain networks. PrivateKeyWallet |         // this results in a different signature on certain networks. PrivateKeyWallet | ||||||
|         // respects this as it uses the parameters passed in |         // respects this as it uses the parameters passed in | ||||||
|         let privKey = this._keystore.exportPrivateKey(txParams.from, this._pwDerivedKey); |         let privateKey = this._keystore.exportPrivateKey(txParams.from, this._pwDerivedKey); | ||||||
|         const privKeyWallet = new PrivateKeyWalletSubprovider(privKey); |         const privateKeyWallet = new PrivateKeyWalletSubprovider(privateKey); | ||||||
|         privKey = ''; |         privateKey = ''; | ||||||
|         const privKeySignature = await privKeyWallet.signTransactionAsync(txParams); |         const privateKeySignature = await privateKeyWallet.signTransactionAsync(txParams); | ||||||
|         return privKeySignature; |         return privateKeySignature; | ||||||
|     } |     } | ||||||
|     /** |     /** | ||||||
|      * Sign a personal Ethereum signed message. The signing account will be the account |      * Sign a personal Ethereum signed message. The signing account will be the account | ||||||
|      * associated with the provided address. |      * 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. |      * 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. |      * If you are not using this via a ProviderEngine instance, you can call it directly. | ||||||
|      * @param data Hex string message to sign |      * @param data Hex string message to sign | ||||||
| @@ -65,10 +66,26 @@ export class EthLightwalletSubprovider extends BaseWalletSubprovider { | |||||||
|      * @return Signature hex string (order: rsv) |      * @return Signature hex string (order: rsv) | ||||||
|      */ |      */ | ||||||
|     public async signPersonalMessageAsync(data: string, address: string): Promise<string> { |     public async signPersonalMessageAsync(data: string, address: string): Promise<string> { | ||||||
|         let privKey = this._keystore.exportPrivateKey(address, this._pwDerivedKey); |         let privateKey = this._keystore.exportPrivateKey(address, this._pwDerivedKey); | ||||||
|         const privKeyWallet = new PrivateKeyWalletSubprovider(privKey); |         const privateKeyWallet = new PrivateKeyWalletSubprovider(privateKey); | ||||||
|         privKey = ''; |         privateKey = ''; | ||||||
|         const result = privKeyWallet.signPersonalMessageAsync(data, address); |         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; |         return result; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -187,6 +187,16 @@ export class LedgerSubprovider extends BaseWalletSubprovider { | |||||||
|             throw err; |             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> { |     private async _createLedgerClientAsync(): Promise<LedgerEthereumClient> { | ||||||
|         await this._connectionLock.acquire(); |         await this._connectionLock.acquire(); | ||||||
|         if (!_.isUndefined(this._ledgerClientIfExists)) { |         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 { assert } from '@0xproject/assert'; | ||||||
|  | import { EIP712TypedData } from '@0xproject/types'; | ||||||
| import { addressUtils } from '@0xproject/utils'; | import { addressUtils } from '@0xproject/utils'; | ||||||
| import * as bip39 from 'bip39'; | import * as bip39 from 'bip39'; | ||||||
| import HDNode = require('hdkey'); | 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 |      * Sign a personal Ethereum signed message. The signing account will be the account | ||||||
|      * associated with the provided address. |      * associated with the provided address. If you've added the MnemonicWalletSubprovider to | ||||||
|      * If you've added the MnemonicWalletSubprovider to your app's provider, you can simply send an `eth_sign` |      * your app's provider, you can simply send an `eth_sign` or `personal_sign` JSON RPC request, | ||||||
|      * or `personal_sign` JSON RPC request, and this method will be called auto-magically. |      * and this method will be called auto-magically. If you are not using this via a ProviderEngine | ||||||
|      * If you are not using this via a ProviderEngine instance, you can call it directly. |      * instance, you can call it directly. | ||||||
|      * @param data Hex string message to sign |      * @param data Hex string message to sign | ||||||
|      * @param address Address of the account to sign with |      * @param address Address of the account to sign with | ||||||
|      * @return Signature hex string (order: rsv) |      * @return Signature hex string (order: rsv) | ||||||
| @@ -108,6 +109,25 @@ export class MnemonicWalletSubprovider extends BaseWalletSubprovider { | |||||||
|         const sig = await privateKeyWallet.signPersonalMessageAsync(data, address); |         const sig = await privateKeyWallet.signPersonalMessageAsync(data, address); | ||||||
|         return sig; |         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 { |     private _privateKeyWalletForAddress(address: string): PrivateKeyWalletSubprovider { | ||||||
|         const derivedKeyInfo = this._findDerivedKeyInfoForAddress(address); |         const derivedKeyInfo = this._findDerivedKeyInfoForAddress(address); | ||||||
|         const privateKeyHex = derivedKeyInfo.hdKey.privateKey.toString('hex'); |         const privateKeyHex = derivedKeyInfo.hdKey.privateKey.toString('hex'); | ||||||
|   | |||||||
| @@ -1,4 +1,6 @@ | |||||||
| import { assert } from '@0xproject/assert'; | import { assert } from '@0xproject/assert'; | ||||||
|  | import { EIP712TypedData } from '@0xproject/types'; | ||||||
|  | import { signTypedDataUtils } from '@0xproject/utils'; | ||||||
| import EthereumTx = require('ethereumjs-tx'); | import EthereumTx = require('ethereumjs-tx'); | ||||||
| import * as ethUtil from 'ethereumjs-util'; | import * as ethUtil from 'ethereumjs-util'; | ||||||
| import * as _ from 'lodash'; | import * as _ from 'lodash'; | ||||||
| @@ -23,7 +25,7 @@ export class PrivateKeyWalletSubprovider extends BaseWalletSubprovider { | |||||||
|     constructor(privateKey: string) { |     constructor(privateKey: string) { | ||||||
|         assert.isString('privateKey', privateKey); |         assert.isString('privateKey', privateKey); | ||||||
|         super(); |         super(); | ||||||
|         this._privateKeyBuffer = new Buffer(privateKey, 'hex'); |         this._privateKeyBuffer = Buffer.from(privateKey, 'hex'); | ||||||
|         this._address = `0x${ethUtil.privateToAddress(this._privateKeyBuffer).toString('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); |         const rpcSig = ethUtil.toRpcSig(sig.v, sig.r, sig.s); | ||||||
|         return rpcSig; |         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 { | export class SignerSubprovider extends Subprovider { | ||||||
|     private readonly _web3Wrapper: Web3Wrapper; |     private readonly _web3Wrapper: Web3Wrapper; | ||||||
|     /** |     /** | ||||||
|      * Instantiates a new SignerSubprovider |      * Instantiates a new SignerSubprovider. | ||||||
|      * @param provider Web3 provider that should handle  all user account related requests |      * @param provider Web3 provider that should handle  all user account related requests | ||||||
|      */ |      */ | ||||||
|     constructor(provider: Provider) { |     constructor(provider: Provider) { | ||||||
| @@ -31,6 +31,8 @@ export class SignerSubprovider extends Subprovider { | |||||||
|      */ |      */ | ||||||
|     // tslint:disable-next-line:prefer-function-over-method async-suffix |     // tslint:disable-next-line:prefer-function-over-method async-suffix | ||||||
|     public async handleRequest(payload: JSONRPCRequestPayload, next: Callback, end: ErrorCallback): Promise<void> { |     public async handleRequest(payload: JSONRPCRequestPayload, next: Callback, end: ErrorCallback): Promise<void> { | ||||||
|  |         let message; | ||||||
|  |         let address; | ||||||
|         switch (payload.method) { |         switch (payload.method) { | ||||||
|             case 'web3_clientVersion': |             case 'web3_clientVersion': | ||||||
|                 try { |                 try { | ||||||
| @@ -59,7 +61,7 @@ export class SignerSubprovider extends Subprovider { | |||||||
|                 } |                 } | ||||||
|                 return; |                 return; | ||||||
|             case 'eth_sign': |             case 'eth_sign': | ||||||
|                 const [address, message] = payload.params; |                 [address, message] = payload.params; | ||||||
|                 try { |                 try { | ||||||
|                     const signature = await this._web3Wrapper.signMessageAsync(address, message); |                     const signature = await this._web3Wrapper.signMessageAsync(address, message); | ||||||
|                     end(null, signature); |                     end(null, signature); | ||||||
| @@ -67,6 +69,15 @@ export class SignerSubprovider extends Subprovider { | |||||||
|                     end(err); |                     end(err); | ||||||
|                 } |                 } | ||||||
|                 return; |                 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: |             default: | ||||||
|                 next(); |                 next(); | ||||||
|                 return; |                 return; | ||||||
|   | |||||||
| @@ -107,8 +107,10 @@ export interface ResponseWithTxParams { | |||||||
| export enum WalletSubproviderErrors { | export enum WalletSubproviderErrors { | ||||||
|     AddressNotFound = 'ADDRESS_NOT_FOUND', |     AddressNotFound = 'ADDRESS_NOT_FOUND', | ||||||
|     DataMissingForSignPersonalMessage = 'DATA_MISSING_FOR_SIGN_PERSONAL_MESSAGE', |     DataMissingForSignPersonalMessage = 'DATA_MISSING_FOR_SIGN_PERSONAL_MESSAGE', | ||||||
|  |     DataMissingForSignTypedData = 'DATA_MISSING_FOR_SIGN_TYPED_DATA', | ||||||
|     SenderInvalidOrNotSupplied = 'SENDER_INVALID_OR_NOT_SUPPLIED', |     SenderInvalidOrNotSupplied = 'SENDER_INVALID_OR_NOT_SUPPLIED', | ||||||
|     FromAddressMissingOrInvalid = 'FROM_ADDRESS_MISSING_OR_INVALID', |     FromAddressMissingOrInvalid = 'FROM_ADDRESS_MISSING_OR_INVALID', | ||||||
|  |     MethodNotSupported = 'METHOD_NOT_SUPPORTED', | ||||||
| } | } | ||||||
| export enum LedgerSubproviderErrors { | export enum LedgerSubproviderErrors { | ||||||
|     TooOldLedgerFirmware = 'TOO_OLD_LEDGER_FIRMWARE', |     TooOldLedgerFirmware = 'TOO_OLD_LEDGER_FIRMWARE', | ||||||
|   | |||||||
| @@ -73,6 +73,13 @@ describe('EthLightwalletSubprovider', () => { | |||||||
|                 const txHex = await ethLightwalletSubprovider.signTransactionAsync(fixtureData.TX_DATA); |                 const txHex = await ethLightwalletSubprovider.signTransactionAsync(fixtureData.TX_DATA); | ||||||
|                 expect(txHex).to.be.equal(fixtureData.TX_DATA_SIGNED_RESULT); |                 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', () => { |     describe('calls through a provider', () => { | ||||||
| @@ -129,6 +136,20 @@ describe('EthLightwalletSubprovider', () => { | |||||||
|                 }); |                 }); | ||||||
|                 provider.sendAsync(payload, callback); |                 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', () => { |         describe('failure cases', () => { | ||||||
|             it('should throw if `data` param not hex when calling eth_sign', (done: DoneCallback) => { |             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); |                 const txHex = await subprovider.signTransactionAsync(txData); | ||||||
|                 expect(txHex).to.be.equal(fixtureData.TX_DATA_ACCOUNT_1_SIGNED_RESULT); |                 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', () => { |         describe('failure cases', () => { | ||||||
|             it('throws an error if address is invalid ', async () => { |             it('throws an error if address is invalid ', async () => { | ||||||
| @@ -118,6 +125,20 @@ describe('MnemonicWalletSubprovider', () => { | |||||||
|                 }); |                 }); | ||||||
|                 provider.sendAsync(payload, callback); |                 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', () => { |         describe('failure cases', () => { | ||||||
|             it('should throw if `data` param not hex when calling eth_sign', (done: DoneCallback) => { |             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); |                 const txHex = await subprovider.signTransactionAsync(fixtureData.TX_DATA); | ||||||
|                 expect(txHex).to.be.equal(fixtureData.TX_DATA_SIGNED_RESULT); |                 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', () => { |     describe('calls through a provider', () => { | ||||||
| @@ -103,6 +110,20 @@ describe('PrivateKeyWalletSubprovider', () => { | |||||||
|                 }); |                 }); | ||||||
|                 provider.sendAsync(payload, callback); |                 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', () => { |         describe('failure cases', () => { | ||||||
|             it('should throw if `data` param not hex when calling eth_sign', (done: DoneCallback) => { |             it('should throw if `data` param not hex when calling eth_sign', (done: DoneCallback) => { | ||||||
|   | |||||||
| @@ -30,4 +30,35 @@ export const fixtureData = { | |||||||
|         '0xf85f8080822710940000000000000000000000000000000000000000808078a0712854c73c69445cc1b22a7c3d7312ff9a97fe4ffba35fd636e8236b211b6e7ca0647cee031615e52d916c7c707025bc64ad525d8f1b9876c3435a863b42743178', |         '0xf85f8080822710940000000000000000000000000000000000000000808078a0712854c73c69445cc1b22a7c3d7312ff9a97fe4ffba35fd636e8236b211b6e7ca0647cee031615e52d916c7c707025bc64ad525d8f1b9876c3435a863b42743178', | ||||||
|     TX_DATA_ACCOUNT_1_SIGNED_RESULT: |     TX_DATA_ACCOUNT_1_SIGNED_RESULT: | ||||||
|         '0xf85f8080822710940000000000000000000000000000000000000000808078a04b02af7ff3f18ce114b601542cc8ebdc50921354f75dd510d31793453a0710e6a0540082a01e475465801b8186a2edc79ec1a2dcf169b9781c25a58a417023c9ca', |         '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, |         "timestamp": 1538693146, | ||||||
|         "version": "1.1.4", |         "version": "1.1.4", | ||||||
|   | |||||||
| @@ -41,6 +41,15 @@ export interface SignedOrder extends Order { | |||||||
|     signature: string; |     signature: string; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * ZeroExTransaction for use with 0x Exchange executeTransaction | ||||||
|  |  */ | ||||||
|  | export interface ZeroExTransaction { | ||||||
|  |     salt: BigNumber; | ||||||
|  |     signerAddress: string; | ||||||
|  |     data: string; | ||||||
|  | } | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Elliptic Curve signature |  * Elliptic Curve signature | ||||||
|  */ |  */ | ||||||
| @@ -143,16 +152,6 @@ export enum SignatureType { | |||||||
|     NSignatureTypes, |     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 { | export enum AssetProxyId { | ||||||
|     ERC20 = '0xf47261b0', |     ERC20 = '0xf47261b0', | ||||||
|     ERC721 = '0x02571792', |     ERC721 = '0x02571792', | ||||||
| @@ -599,3 +598,25 @@ export interface Metadata { | |||||||
|     externalTypeToLink: ExternalTypeToLink; |     externalTypeToLink: ExternalTypeToLink; | ||||||
|     externalExportToLink: ExternalExportToLink; |     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 { NULL_BYTES } from './constants'; | ||||||
| export { errorUtils } from './error_utils'; | export { errorUtils } from './error_utils'; | ||||||
| export { fetchAsync } from './fetch_async'; | 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", |         "version": "3.0.3", | ||||||
|         "changes": [ |         "changes": [ | ||||||
|   | |||||||
| @@ -30,6 +30,7 @@ export { | |||||||
|     OpCode, |     OpCode, | ||||||
|     TxDataPayable, |     TxDataPayable, | ||||||
|     JSONRPCResponsePayload, |     JSONRPCResponsePayload, | ||||||
|  |     JSONRPCResponseError, | ||||||
|     RawLogEntry, |     RawLogEntry, | ||||||
|     DecodedLogEntryEvent, |     DecodedLogEntryEvent, | ||||||
|     LogWithDecodedArgs, |     LogWithDecodedArgs, | ||||||
|   | |||||||
| @@ -314,6 +314,21 @@ export class Web3Wrapper { | |||||||
|         }); |         }); | ||||||
|         return signData; |         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 |      * Fetches the latest block number | ||||||
|      * @returns Block number |      * @returns Block number | ||||||
| @@ -654,6 +669,9 @@ export class Web3Wrapper { | |||||||
|             ...payload, |             ...payload, | ||||||
|         }; |         }; | ||||||
|         const response = await promisify<JSONRPCResponsePayload>(sendAsync)(payloadWithDefaults); |         const response = await promisify<JSONRPCResponsePayload>(sendAsync)(payloadWithDefaults); | ||||||
|  |         if (response.error) { | ||||||
|  |             throw new Error(response.error.message); | ||||||
|  |         } | ||||||
|         const result = response.result; |         const result = response.result; | ||||||
|         return result; |         return result; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import * as chai from 'chai'; | import * as chai from 'chai'; | ||||||
| import { BlockParamLiteral } from 'ethereum-types'; | import { BlockParamLiteral, JSONRPCErrorCallback, JSONRPCRequestPayload } from 'ethereum-types'; | ||||||
| import * as Ganache from 'ganache-core'; | import * as Ganache from 'ganache-core'; | ||||||
| import * as _ from 'lodash'; | import * as _ from 'lodash'; | ||||||
| import 'mocha'; | import 'mocha'; | ||||||
| @@ -78,6 +78,19 @@ describe('Web3Wrapper tests', () => { | |||||||
|             const signatureLength = 132; |             const signatureLength = 132; | ||||||
|             expect(signature.length).to.be.equal(signatureLength); |             expect(signature.length).to.be.equal(signatureLength); | ||||||
|         }); |         }); | ||||||
|  |         it('should throw if the provider returns an error', async () => { | ||||||
|  |             const message = '0xdeadbeef'; | ||||||
|  |             const signer = addresses[1]; | ||||||
|  |             const fakeProvider = { | ||||||
|  |                 async sendAsync(payload: JSONRPCRequestPayload, callback: JSONRPCErrorCallback): Promise<void> { | ||||||
|  |                     callback(new Error('User denied message signature')); | ||||||
|  |                 }, | ||||||
|  |             }; | ||||||
|  |             const errorWeb3Wrapper = new Web3Wrapper(fakeProvider); | ||||||
|  |             expect(errorWeb3Wrapper.signMessageAsync(signer, message)).to.be.rejectedWith( | ||||||
|  |                 'User denied message signature', | ||||||
|  |             ); | ||||||
|  |         }); | ||||||
|     }); |     }); | ||||||
|     describe('#getBlockNumberAsync', () => { |     describe('#getBlockNumberAsync', () => { | ||||||
|         it('get block number', async () => { |         it('get block number', async () => { | ||||||
|   | |||||||
| @@ -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. | 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, |     ExchangeFillEventArgs, | ||||||
|     IndexedFilterValues, |     IndexedFilterValues, | ||||||
| } from '@0xproject/contract-wrappers'; | } 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 { EtherscanLinkSuffixes, utils as sharedUtils } from '@0xproject/react-shared'; | ||||||
| import { | import { | ||||||
|     ledgerEthereumBrowserClientFactoryAsync, |     ledgerEthereumBrowserClientFactoryAsync, | ||||||
|     LedgerSubprovider, |     LedgerSubprovider, | ||||||
|  |     MetamaskSubprovider, | ||||||
|     RedundantSubprovider, |     RedundantSubprovider, | ||||||
|     RPCSubprovider, |     RPCSubprovider, | ||||||
|     SignerSubprovider, |     SignerSubprovider, | ||||||
| @@ -27,8 +28,6 @@ import * as _ from 'lodash'; | |||||||
| import * as moment from 'moment'; | import * as moment from 'moment'; | ||||||
| import * as React from 'react'; | import * as React from 'react'; | ||||||
| import contract = require('truffle-contract'); | import contract = require('truffle-contract'); | ||||||
| import { tokenAddressOverrides } from 'ts/utils/token_address_overrides'; |  | ||||||
|  |  | ||||||
| import { BlockchainWatcher } from 'ts/blockchain_watcher'; | import { BlockchainWatcher } from 'ts/blockchain_watcher'; | ||||||
| import { AssetSendCompleted } from 'ts/components/flash_messages/asset_send_completed'; | import { AssetSendCompleted } from 'ts/components/flash_messages/asset_send_completed'; | ||||||
| import { TransactionSubmitted } from 'ts/components/flash_messages/transaction_submitted'; | import { TransactionSubmitted } from 'ts/components/flash_messages/transaction_submitted'; | ||||||
| @@ -54,6 +53,7 @@ import { backendClient } from 'ts/utils/backend_client'; | |||||||
| import { configs } from 'ts/utils/configs'; | import { configs } from 'ts/utils/configs'; | ||||||
| import { constants } from 'ts/utils/constants'; | import { constants } from 'ts/utils/constants'; | ||||||
| import { errorReporter } from 'ts/utils/error_reporter'; | import { errorReporter } from 'ts/utils/error_reporter'; | ||||||
|  | import { tokenAddressOverrides } from 'ts/utils/token_address_overrides'; | ||||||
| import { utils } from 'ts/utils/utils'; | import { utils } from 'ts/utils/utils'; | ||||||
| import FilterSubprovider = require('web3-provider-engine/subproviders/filters'); | import FilterSubprovider = require('web3-provider-engine/subproviders/filters'); | ||||||
|  |  | ||||||
| @@ -161,7 +161,13 @@ export class Blockchain { | |||||||
|             // We catch all requests involving a users account and send it to the injectedWeb3 |             // We catch all requests involving a users account and send it to the injectedWeb3 | ||||||
|             // instance. All other requests go to the public hosted node. |             // instance. All other requests go to the public hosted node. | ||||||
|             const provider = new Web3ProviderEngine(); |             const provider = new Web3ProviderEngine(); | ||||||
|             provider.addProvider(new 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()); |             provider.addProvider(new FilterSubprovider()); | ||||||
|             const rpcSubproviders = _.map(publicNodeUrlsIfExistsForNetworkId, publicNodeUrl => { |             const rpcSubproviders = _.map(publicNodeUrlsIfExistsForNetworkId, publicNodeUrl => { | ||||||
|                 return new RPCSubprovider(publicNodeUrl); |                 return new RPCSubprovider(publicNodeUrl); | ||||||
| @@ -432,21 +438,7 @@ export class Blockchain { | |||||||
|         } |         } | ||||||
|         this._showFlashMessageIfLedger(); |         this._showFlashMessageIfLedger(); | ||||||
|         const provider = this._contractWrappers.getProvider(); |         const provider = this._contractWrappers.getProvider(); | ||||||
|         const isLedgerSigner = !_.isUndefined(this._ledgerSubprovider); |         const ecSignatureString = await signatureUtils.ecSignHashAsync(provider, orderHash, makerAddress); | ||||||
|         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, |  | ||||||
|         ); |  | ||||||
|         this._dispatcher.updateSignature(ecSignatureString); |         this._dispatcher.updateSignature(ecSignatureString); | ||||||
|         return ecSignatureString; |         return ecSignatureString; | ||||||
|     } |     } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user