1. import { SchemaValidator } from '@0xproject/json-schemas';
    2. import { BigNumber, intervalUtils } from '@0xproject/utils';
    3. import { Web3Wrapper } from '@0xproject/web3-wrapper';
    4. import * as _ from 'lodash';
    5. import {
    6. ECSignature,
    7. Network,
    8. Order,
    9. SignedOrder,
    10. TransactionReceiptWithDecodedLogs,
    11. Web3Provider,
    12. WyvernProtocolConfig,
    13. WyvernProtocolError,
    14. } from './types';
    15. import { schemas } from './schemas';
    16. import { AbiDecoder } from './utils/abi_decoder';
    17. import { assert } from './utils/assert';
    18. import { constants } from './utils/constants';
    19. import { decorators } from './utils/decorators';
    20. import { signatureUtils } from './utils/signature_utils';
    21. import { utils } from './utils/utils';
    22. import { WyvernAtomicizerContract } from './abi_gen/wyvern_atomicizer';
    23. import { WyvernDAOContract } from './abi_gen/wyvern_d_a_o';
    24. import { WyvernExchangeContract } from './abi_gen/wyvern_exchange';
    25. import { WyvernProxyRegistryContract } from './abi_gen/wyvern_proxy_registry';
    26. import { WyvernTokenContract } from './abi_gen/wyvern_token';
    27. export class WyvernProtocol {
    28. public static NULL_ADDRESS = constants.NULL_ADDRESS;
    29. public static MAX_UINT_256 = new BigNumber(2).pow(256).sub(1);
    30. public wyvernExchange: WyvernExchangeContract;
    31. public wyvernProxyRegistry: WyvernProxyRegistryContract;
    32. public wyvernDAO: WyvernDAOContract;
    33. public wyvernToken: WyvernTokenContract;
    34. public wyvernAtomicizer: WyvernAtomicizerContract;
    35. private _web3Wrapper: Web3Wrapper;
    36. private _abiDecoder: AbiDecoder;
    37. public static getExchangeContractAddress(network: Network): string {
    38. return constants.DEPLOYED[network].WyvernExchange;
    39. }
    40. public static getProxyRegistryContractAddress(network: Network): string {
    41. return constants.DEPLOYED[network].WyvernProxyRegistry;
    42. }
    43. public static getTokenContractAddress(network: Network): string {
    44. return constants.DEPLOYED[network].WyvernToken;
    45. }
    46. public static getDAOContractAddress(network: Network): string {
    47. return constants.DEPLOYED[network].WyvernDAO;
    48. }
    49. public static getAtomicizerContractAddress(network: Network): string {
    50. return constants.DEPLOYED[network].WyvernAtomicizer;
    51. }
    52. public static getTokenTransferProxyAddress(network: Network): string {
    53. return constants.DEPLOYED[network].WyvernTokenTransferProxy;
    54. }
    55. /**
    56. * 验证椭圆曲线签名' signature '是通过使用
    57. * ' signerAddress '地址对应的私钥对' data '进行签名生成的。
    58. * @param data 由所提供的签名签名的十六进制编码数据。
    59. * @param signature 包含椭圆曲线签名参数的对象。
    60. * @param signerAddress 十六进制编码地址签名的数据,产生提供的签名。
    61. * @return 该签名对所提供的签名地址和数据有效。
    62. */
    63. public static isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean {
    64. assert.isHexString('data', data);
    65. assert.doesConformToSchema('signature', signature, schemas.ecSignatureSchema);
    66. assert.isETHAddressHex('signerAddress', signerAddress);
    67. const isValidSignature = signatureUtils.isValidSignature(data, signature, signerAddress);
    68. return isValidSignature;
    69. }
    70. /**
    71. * 生成一个伪随机256位盐。
    72. * salt可以包含在一个0x顺序中,以确保该顺序生成唯一的orderHash
    73. * 并且不会与其他所有参数相同的未完成订单发生冲突。
    74. * @return 一个伪随机的256位数字,可以用作salt。
    75. */
    76. public static generatePseudoRandomSalt(): BigNumber {
    77. // BigNumber.random returns a pseudo-random number between 0 & 1 with a passed in number of decimal places.
    78. // Source: https://mikemcl.github.io/bignumber.js/#random
    79. const randomNumber = BigNumber.random(constants.MAX_DIGITS_IN_UNSIGNED_256_INT);
    80. const factor = new BigNumber(10).pow(constants.MAX_DIGITS_IN_UNSIGNED_256_INT - 1);
    81. const salt = randomNumber.times(factor).round();
    82. return salt;
    83. }
    84. /**
    85. * 如果提供的十六进制编码的订单散列有效,则表示符合。
    86. * 注意:Valid表示它具有预期的格式,而不是表示存在带有orderHash的订单。
    87. * 在处理作为用户输入提交的orderhash时使用此方法。
    88. * @param orderHash 十六进制编码orderHash.
    89. * @return 提供的orderHash是否有预期的格式。
    90. */
    91. public static isValidOrderHash(orderHash: string): boolean {
    92. // Since this method can be called to check if any arbitrary string conforms to an orderHash's
    93. // format, we only assert that we were indeed passed a string.
    94. assert.isString('orderHash', orderHash);
    95. const schemaValidator = new SchemaValidator();
    96. const isValidOrderHash = schemaValidator.validate(orderHash, schemas.orderHashSchema).valid;
    97. return isValidOrderHash;
    98. }
    99. /**
    100. * 单位量定义为位于指定小数点(整数部分)上方的Token数量。
    101. * 例:如果一种货币有18位小数,1e18或该货币的1 × 10 × 10次方等于1个单位。
    102. * to 1 unit.
    103. * @param amount 您想要转换为单位的基本单位的数量。
    104. * @param decimals 单位数量的小数位数。
    105. * @return 金额单位
    106. */
    107. public static toUnitAmount(amount: BigNumber, decimals: number): BigNumber {
    108. assert.isValidBaseUnitAmount('amount', amount);
    109. assert.isNumber('decimals', decimals);
    110. const aUnit = new BigNumber(10).pow(decimals);
    111. const unit = amount.div(aUnit);
    112. return unit;
    113. }
    114. /**
    115. * baseUnit被定义为Token的最小单位。
    116. * 用基本单位表示的量是用最小的单位表示的量。
    117. * 例如:一个token的1个单位有18位小数,用baseUnits表示为1000000000000000000
    118. * @param amount 您想要转换为基本单位的单位数量。
    119. * @param decimals 单位数量的小数位数。
    120. * @return 金额单位为基础单位。
    121. */
    122. public static toBaseUnitAmount(amount: BigNumber, decimals: number): BigNumber {
    123. assert.isBigNumber('amount', amount);
    124. assert.isNumber('decimals', decimals);
    125. const unit = new BigNumber(10).pow(decimals);
    126. const baseUnitAmount = amount.times(unit);
    127. const hasDecimals = baseUnitAmount.decimalPlaces() !== 0;
    128. if (hasDecimals) {
    129. throw new Error(`Invalid unit amount: ${amount.toString()} - Too many decimal places`);
    130. }
    131. return baseUnitAmount;
    132. }
    133. /**
    134. * 计算所提供订单的orderHash。
    135. * @param order 符合Order或SignedOrder接口定义的对象。
    136. * @return 通过对提供的订单进行散列得到的orderHash。
    137. */
    138. @decorators.syncWyvernProtocolErrorHandler
    139. public static getOrderHashHex(order: Order | SignedOrder): string {
    140. assert.doesConformToSchema('order', order, schemas.orderSchema);
    141. const orderHashHex = utils.getOrderHashHex(order);
    142. return orderHashHex;
    143. }
    144. /**
    145. * 计算提供的资产的assetHash。
    146. */
    147. public static getAssetHashHex(assetHash: string, schema: string): string {
    148. const assetHashHex = utils.getAssetHashHex(assetHash, schema);
    149. return assetHashHex;
    150. }
    151. constructor(provider: Web3Provider, config: WyvernProtocolConfig) {
    152. assert.isWeb3Provider('provider', provider);
    153. // assert.doesConformToSchema('config', config, wyvernProtocolConfigSchema)
    154. this._web3Wrapper = new Web3Wrapper(provider, { gasPrice: config.gasPrice });
    155. const exchangeContractAddress = config.wyvernExchangeContractAddress || WyvernProtocol.getExchangeContractAddress(config.network);
    156. this.wyvernExchange = new WyvernExchangeContract(
    157. this._web3Wrapper.getContractInstance((constants.EXCHANGE_ABI as any), exchangeContractAddress),
    158. {},
    159. );
    160. // 代理注册合约地址
    161. const proxyRegistryContractAddress = config.wyvernProxyRegistryContractAddress || WyvernProtocol.getProxyRegistryContractAddress(config.network);
    162. this.wyvernProxyRegistry = new WyvernProxyRegistryContract(
    163. this._web3Wrapper.getContractInstance((constants.PROXY_REGISTRY_ABI as any), proxyRegistryContractAddress),
    164. {},
    165. );
    166. const daoContractAddress = config.wyvernDAOContractAddress || WyvernProtocol.getDAOContractAddress(config.network);
    167. this.wyvernDAO = new WyvernDAOContract(
    168. this._web3Wrapper.getContractInstance((constants.DAO_ABI as any), daoContractAddress),
    169. {},
    170. );
    171. // 代币合约地址
    172. const tokenContractAddress = config.wyvernTokenContractAddress || WyvernProtocol.getTokenContractAddress(config.network);
    173. this.wyvernToken = new WyvernTokenContract(
    174. this._web3Wrapper.getContractInstance((constants.TOKEN_ABI as any), tokenContractAddress),
    175. {},
    176. );
    177. // atomicizer 合约地址
    178. const atomicizerContractAddress = config.wyvernAtomicizerContractAddress || WyvernProtocol.getAtomicizerContractAddress(config.network);
    179. this.wyvernAtomicizer = new WyvernAtomicizerContract(
    180. this._web3Wrapper.getContractInstance((constants.ATOMICIZER_ABI as any), atomicizerContractAddress),
    181. {},
    182. );
    183. }
    184. /**
    185. * 为 wyvernProtocol.js 设置一个新的 web3 提供程序。
    186. * 更新提供程序将停止所有订阅,因此您需要在此调用后重新订阅与您的应用程序相关的所有事件。
    187. * @param provider wyvernProtocol.js 库从现在开始使用的 Web3Provider。
    188. * @param networkId 网络提供商连接到ID
    189. */
    190. public setProvider(provider: Web3Provider, networkId: number): void {
    191. this._web3Wrapper.setProvider(provider);
    192. (this.wyvernExchange as any)._invalidateContractInstances();
    193. (this.wyvernExchange as any)._setNetworkId(networkId);
    194. (this.wyvernProxyRegistry as any)._invalidateContractInstance();
    195. (this.wyvernProxyRegistry as any)._setNetworkId(networkId);
    196. }
    197. /**
    198. * 通过提供的用于发送交易的web3提供商获取可用的用户以太坊地址。
    199. * @return 可用的用户以太坊地址数组。
    200. */
    201. public async getAvailableAddressesAsync(): Promise<string[]> {
    202. const availableAddresses = await this._web3Wrapper.getAvailableAddressesAsync();
    203. return availableAddresses;
    204. }
    205. /**
    206. * 对orderHash进行签名并返回其椭圆曲线签名。
    207. * 方法目前支持TestRPC、Geth和奇偶校验V1.6.6或更高或更低
    208. * @param orderHash 要签名的已编码的orderHash。
    209. * @param signerAddress 用十六进制编码的以太网地址签名。此地址必须通过Web3可用。提供给wyvernProtocol.js的提供商。
    210. * @return 生成一个包含通过签名orderHash生成的椭圆曲线签名参数的对象。
    211. */
    212. public async signOrderHashAsync(orderHash: string, signerAddress: string): Promise<ECSignature> {
    213. assert.isHexString('orderHash', orderHash);
    214. /* await assert.isSenderAddressAsync('signerAddress', signerAddress, this._web3Wrapper); */
    215. /*
    216. let msgHashHex;
    217. const nodeVersion = await this._web3Wrapper.getNodeVersionAsync();
    218. const isParityNode = utils.isParityNode(nodeVersion);
    219. const isTestRpc = utils.isTestRpc(nodeVersion);
    220. if (isParityNode || isTestRpc) {
    221. // Parity and TestRpc nodes add the personalMessage prefix itself
    222. msgHashHex = orderHash;
    223. } else {
    224. const orderHashBuff = ethUtil.toBuffer(orderHash);
    225. const msgHashBuff = ethUtil.hashPersonalMessage(orderHashBuff);
    226. msgHashHex = ethUtil.bufferToHex(msgHashBuff);
    227. }
    228. */
    229. const msgHashHex = orderHash;
    230. const signature = await this._web3Wrapper.signTransactionAsync(signerAddress, msgHashHex);
    231. // HACK: There is no consensus on whether the signatureHex string should be formatted as
    232. // v + r + s OR r + s + v, and different clients (even different versions of the same client)
    233. // return the signature params in different orders. In order to support all client implementations,
    234. // we parse the signature in both ways, and evaluate if either one is a valid signature.
    235. const validVParamValues = [27, 28];
    236. const ecSignatureVRS = signatureUtils.parseSignatureHexAsVRS(signature);
    237. if (_.includes(validVParamValues, ecSignatureVRS.v)) {
    238. const isValidVRSSignature = WyvernProtocol.isValidSignature(orderHash, ecSignatureVRS, signerAddress);
    239. if (isValidVRSSignature) {
    240. return ecSignatureVRS;
    241. }
    242. }
    243. const ecSignatureRSV = signatureUtils.parseSignatureHexAsRSV(signature);
    244. if (_.includes(validVParamValues, ecSignatureRSV.v)) {
    245. const isValidRSVSignature = WyvernProtocol.isValidSignature(orderHash, ecSignatureRSV, signerAddress);
    246. if (isValidRSVSignature) {
    247. return ecSignatureRSV;
    248. }
    249. }
    250. throw new Error(WyvernProtocolError.InvalidSignature);
    251. }
    252. /**
    253. * 等待一个事务是mined并返回交易收据。
    254. * @param txHash 交易hash
    255. * @param pollingIntervalMs 我们应该多久检查一次交易是否被挖掘。
    256. * @param timeoutMs 为挖掘事务而轮询多长时间(以毫秒为单位),直到终止。
    257. * @return 事务日志参数接收与解码。
    258. */
    259. public async awaitTransactionMinedAsync(
    260. txHash: string,
    261. pollingIntervalMs = 1000,
    262. timeoutMs?: number,
    263. ): Promise<TransactionReceiptWithDecodedLogs> {
    264. let timeoutExceeded = false;
    265. if (timeoutMs) {
    266. setTimeout(() => (timeoutExceeded = true), timeoutMs);
    267. }
    268. const txReceiptPromise = new Promise(
    269. (resolve: (receipt: TransactionReceiptWithDecodedLogs) => void, reject) => {
    270. const intervalId = intervalUtils.setAsyncExcludingInterval(async () => {
    271. if (timeoutExceeded) {
    272. intervalUtils.clearAsyncExcludingInterval(intervalId);
    273. return reject(WyvernProtocolError.TransactionMiningTimeout);
    274. }
    275. const transactionReceipt = await this._web3Wrapper.getTransactionReceiptAsync(txHash);
    276. if (!_.isNull(transactionReceipt)) {
    277. intervalUtils.clearAsyncExcludingInterval(intervalId);
    278. const logsWithDecodedArgs = _.map(
    279. transactionReceipt.logs,
    280. this._abiDecoder.tryToDecodeLogOrNoop.bind(this._abiDecoder),
    281. );
    282. const transactionReceiptWithDecodedLogArgs: TransactionReceiptWithDecodedLogs = {
    283. ...transactionReceipt,
    284. logs: logsWithDecodedArgs,
    285. };
    286. resolve(transactionReceiptWithDecodedLogArgs);
    287. }
    288. }, pollingIntervalMs, () => ({}));
    289. },
    290. );
    291. return txReceiptPromise;
    292. }
    293. }