Merge pull request #2336 from 0xProject/feature/upgrade-instant-v3

Upgrade instant v3
This commit is contained in:
Jacob Evans 2019-11-26 20:43:58 +10:00 committed by GitHub
commit 4b5f2c36b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
109 changed files with 2594 additions and 3892 deletions

View File

@ -93,8 +93,8 @@ jobs:
keys:
- repo-{{ .Environment.CIRCLE_SHA1 }}
- run:
command: yarn test:publish:circleci
no_output_timeout: 1800
command: yarn test:publish:circleci
no_output_timeout: 1800
test-doc-generation:
docker:
- image: nikolaik/python-nodejs:python3.7-nodejs8
@ -104,8 +104,8 @@ jobs:
keys:
- repo-{{ .Environment.CIRCLE_SHA1 }}
- run:
command: yarn test:generate_docs:circleci
no_output_timeout: 1200
command: yarn test:generate_docs:circleci
no_output_timeout: 1200
test-rest:
docker:
- image: nikolaik/python-nodejs:python3.7-nodejs8
@ -125,7 +125,7 @@ jobs:
- run: yarn wsrun test:circleci @0x/dev-utils
- run: yarn wsrun test:circleci @0x/json-schemas
- run: yarn wsrun test:circleci @0x/order-utils
# - run: yarn wsrun test:circleci @0x/orderbook
- run: yarn wsrun test:circleci @0x/orderbook
- run: yarn wsrun test:circleci @0x/sol-compiler
- run: yarn wsrun test:circleci @0x/sol-tracing-utils
- run: yarn wsrun test:circleci @0x/sol-doc
@ -142,9 +142,9 @@ jobs:
paths:
- ~/repo/packages/assert/coverage/lcov.info
- save_cache:
key: coverage-asset-buyer-{{ .Environment.CIRCLE_SHA1 }}
key: coverage-asset-swapper-{{ .Environment.CIRCLE_SHA1 }}
paths:
- ~/repo/packages/asset-buyer/coverage/lcov.info
- ~/repo/packages/asset-swapper/coverage/lcov.info
- save_cache:
key: coverage-base-contract-{{ .Environment.CIRCLE_SHA1 }}
paths:
@ -362,7 +362,7 @@ jobs:
- coverage-assert-{{ .Environment.CIRCLE_SHA1 }}
- restore_cache:
keys:
- coverage-asset-buyer-{{ .Environment.CIRCLE_SHA1 }}
- coverage-asset-swapper-{{ .Environment.CIRCLE_SHA1 }}
- restore_cache:
keys:
- coverage-base-contract-{{ .Environment.CIRCLE_SHA1 }}

View File

@ -19,7 +19,7 @@ contracts: ['contracts']
@0x/sol-tracing-utils: ['packages/sol-tracing-utils']
@0x/utils: ['packages/utils']
@0x/tslint-config: ['packages/tslint-config']
@0x/asset-buyer: ['packages/asset-buyer']
@0x/asset-swapper: ['packages/asset-swapper']
@0x/order-utils: ['packages/order-utils']
@0x/assert: ['packages/assert']
@0x/base-contract: ['packages/base-contract']

View File

@ -5,8 +5,8 @@
# https://git-scm.com/docs/gitignore#_pattern_format
# Website
packages/asset-buyer/ @BMillman19 @fragosti @steveklebanoff
packages/instant/ @BMillman19 @fragosti @steveklebanoff
packages/asset-swapper/ @BMillman19 @fragosti @dave4506
packages/instant/ @BMillman19 @fragosti @dave4506
# Dev tools & setup
.circleci/ @LogvinovLeon

View File

@ -65,7 +65,6 @@ These packages are all under development. See [/contracts/README.md](/contracts/
| [`@0x/abi-gen-wrappers`](/packages/abi-gen-wrappers) | [![npm](https://img.shields.io/npm/v/@0x/abi-gen-wrappers.svg)](https://www.npmjs.com/package/@0x/abi-gen-wrappers) | Low-level 0x smart contract wrappers generated using `@0x/abi-gen` |
| [`@0x/sra-spec`](/packages/sra-spec) | [![npm](https://img.shields.io/npm/v/@0x/sra-spec.svg)](https://www.npmjs.com/package/@0x/sra-spec) | OpenAPI specification for the Standard Relayer API |
| [`@0x/connect`](/packages/connect) | [![npm](https://img.shields.io/npm/v/@0x/connect.svg)](https://www.npmjs.com/package/@0x/connect) | An HTTP/WS client for interacting with the Standard Relayer API |
| [`@0x/asset-buyer`](/packages/asset-buyer) | [![npm](https://img.shields.io/npm/v/@0x/asset-buyer.svg)](https://www.npmjs.com/package/@0x/asset-buyer) | Convenience package for discovering and buying assets with Ether |
| [`@0x/asset-swapper`](/packages/asset-swapper) | [![npm](https://img.shields.io/npm/v/@0x/asset-swapper.svg)](https://www.npmjs.com/package/@0x/asset-swapper) | Convenience package for discovering and performing swaps for any ERC20 Assets |
#### Ethereum tooling

View File

@ -16,7 +16,7 @@ export {
export { ERC20Wrapper } from './erc20_wrapper';
export { ERC721Wrapper } from './erc721_wrapper';
export { ERC1155ProxyWrapper } from './erc1155_proxy_wrapper';
export { Erc1155Wrapper, ERC1155MintableContract } from '@0x/contracts-erc1155';
export { ERC1155MintableContract, Erc1155Wrapper } from '@0x/contracts-erc1155';
export { DummyERC20TokenContract } from '@0x/contracts-erc20';
export { DummyERC721TokenContract } from '@0x/contracts-erc721';
export {

View File

@ -55,11 +55,11 @@
"truffle": "^5.0.32",
"tslint": "5.11.0",
"typedoc": "^0.15.0",
"ethereum-types": "^2.2.0-beta.2",
"typescript": "3.0.1"
},
"dependencies": {
"@0x/base-contract": "^5.5.0-beta.3",
"ethereum-types": "^2.2.0-beta.2"
"@0x/base-contract": "^5.5.0-beta.3"
},
"publishConfig": {
"access": "public"

View File

@ -63,6 +63,8 @@
"@types/lodash": "4.14.104",
"@types/mocha": "^5.2.7",
"@types/node": "*",
"@0x/typescript-typings": "^4.4.0-beta.2",
"ethereum-types": "^2.2.0-beta.2",
"chai": "^4.0.1",
"chai-as-promised": "^7.1.0",
"chai-bignumber": "^3.0.0",
@ -80,10 +82,8 @@
"dependencies": {
"@0x/base-contract": "^5.5.0-beta.3",
"@0x/contracts-test-utils": "^3.2.0-beta.3",
"@0x/typescript-typings": "^4.4.0-beta.2",
"@0x/utils": "^4.6.0-beta.2",
"@0x/web3-wrapper": "^6.1.0-beta.2",
"ethereum-types": "^2.2.0-beta.2",
"lodash": "^4.17.11"
},
"publishConfig": {

View File

@ -1,27 +1,16 @@
import { LogDecoder } from '@0x/contracts-test-utils';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import * as chai from 'chai';
import { LogWithDecodedArgs, Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types';
import * as _ from 'lodash';
import { ERC1155MintableContract, ERC1155TransferSingleEventArgs } from './wrappers';
import { artifacts } from './artifacts';
const expect = chai.expect;
export class Erc1155Wrapper {
private readonly _erc1155Contract: ERC1155MintableContract;
private readonly _web3Wrapper: Web3Wrapper;
private readonly _contractOwner: string;
private readonly _logDecoder: LogDecoder;
constructor(contractInstance: ERC1155MintableContract, provider: Provider, contractOwner: string) {
this._erc1155Contract = contractInstance;
this._web3Wrapper = new Web3Wrapper(provider);
this._contractOwner = contractOwner;
this._logDecoder = new LogDecoder(this._web3Wrapper, artifacts);
}
public getContract(): ERC1155MintableContract {
return this._erc1155Contract;
@ -40,11 +29,11 @@ export class Erc1155Wrapper {
): Promise<TransactionReceiptWithDecodedLogs> {
const spender = delegatedSpender === undefined ? from : delegatedSpender;
const callbackDataHex = callbackData === undefined ? '0x' : callbackData;
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(
await this._erc1155Contract.safeTransferFrom(from, to, token, value, callbackDataHex).sendTransactionAsync({
const tx = await this._erc1155Contract
.safeTransferFrom(from, to, token, value, callbackDataHex)
.awaitTransactionSuccessAsync({
from: spender,
}),
);
});
return tx;
}
public async safeBatchTransferFromAsync(
@ -57,11 +46,9 @@ export class Erc1155Wrapper {
): Promise<TransactionReceiptWithDecodedLogs> {
const spender = delegatedSpender === undefined ? from : delegatedSpender;
const callbackDataHex = callbackData === undefined ? '0x' : callbackData;
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(
await this._erc1155Contract
.safeBatchTransferFrom(from, to, tokens, values, callbackDataHex)
.sendTransactionAsync({ from: spender }),
);
const tx = await this._erc1155Contract
.safeBatchTransferFrom(from, to, tokens, values, callbackDataHex)
.awaitTransactionSuccessAsync({ from: spender });
return tx;
}
public async mintFungibleTokensAsync(
@ -70,11 +57,9 @@ export class Erc1155Wrapper {
): Promise<BigNumber> {
const tokenUri = 'dummyFungibleToken';
const tokenIsNonFungible = false;
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(
await this._erc1155Contract.create(tokenUri, tokenIsNonFungible).sendTransactionAsync({
from: this._contractOwner,
}),
);
const tx = await this._erc1155Contract.create(tokenUri, tokenIsNonFungible).awaitTransactionSuccessAsync({
from: this._contractOwner,
});
// tslint:disable-next-line no-unnecessary-type-assertion
const createFungibleTokenLog = tx.logs[0] as LogWithDecodedArgs<ERC1155TransferSingleEventArgs>;
const tokenId = createFungibleTokenLog.args.id;
@ -99,11 +84,9 @@ export class Erc1155Wrapper {
public async mintNonFungibleTokensAsync(beneficiaries: string[]): Promise<[BigNumber, BigNumber[]]> {
const tokenUri = 'dummyNonFungibleToken';
const tokenIsNonFungible = true;
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(
await this._erc1155Contract.create(tokenUri, tokenIsNonFungible).sendTransactionAsync({
from: this._contractOwner,
}),
);
const tx = await this._erc1155Contract.create(tokenUri, tokenIsNonFungible).awaitTransactionSuccessAsync({
from: this._contractOwner,
});
// tslint:disable-next-line no-unnecessary-type-assertion
const createFungibleTokenLog = tx.logs[0] as LogWithDecodedArgs<ERC1155TransferSingleEventArgs>;
const token = createFungibleTokenLog.args.id;
@ -125,11 +108,9 @@ export class Erc1155Wrapper {
beneficiary: string,
isApproved: boolean,
): Promise<TransactionReceiptWithDecodedLogs> {
const tx = await this._logDecoder.getTxWithDecodedLogsAsync(
await this._erc1155Contract.setApprovalForAll(beneficiary, isApproved).sendTransactionAsync({
from: owner,
}),
);
const tx = await this._erc1155Contract.setApprovalForAll(beneficiary, isApproved).awaitTransactionSuccessAsync({
from: owner,
});
return tx;
}
public async isApprovedForAllAsync(owner: string, beneficiary: string): Promise<boolean> {
@ -151,7 +132,9 @@ export class Erc1155Wrapper {
});
const balances = await this.getBalancesAsync(ownersExtended, tokensExtended);
_.each(balances, (balance: BigNumber, i: number) => {
expect(balance, `${ownersExtended[i]}${tokensExtended[i]}`).to.be.bignumber.equal(expectedBalances[i]);
if (!balance.isEqualTo(expectedBalances[i])) {
throw new Error(`${ownersExtended[i]}${tokensExtended[i]} balance not equal ${expectedBalances[i]}`);
}
});
}
public async isNonFungibleItemAsync(tokenId: BigNumber): Promise<boolean> {

View File

@ -61,6 +61,9 @@
"@0x/tslint-config": "^3.1.0-beta.2",
"@0x/utils": "^4.6.0-beta.2",
"@0x/web3-wrapper": "^6.1.0-beta.2",
"@0x/types": "^2.5.0-beta.2",
"@0x/typescript-typings": "^4.4.0-beta.2",
"ethereum-types": "^2.2.0-beta.2",
"@types/lodash": "4.14.104",
"@types/mocha": "^5.2.7",
"@types/node": "*",
@ -79,10 +82,7 @@
"typescript": "3.0.1"
},
"dependencies": {
"@0x/base-contract": "^5.5.0-beta.3",
"@0x/types": "^2.5.0-beta.2",
"@0x/typescript-typings": "^4.4.0-beta.2",
"ethereum-types": "^2.2.0-beta.2"
"@0x/base-contract": "^5.5.0-beta.3"
},
"publishConfig": {
"access": "public"

View File

@ -63,6 +63,8 @@
"@0x/types": "^2.5.0-beta.2",
"@0x/utils": "^4.6.0-beta.2",
"@0x/web3-wrapper": "^6.1.0-beta.2",
"@0x/typescript-typings": "^4.4.0-beta.2",
"ethereum-types": "^2.2.0-beta.2",
"@types/lodash": "4.14.104",
"@types/mocha": "^5.2.7",
"@types/node": "*",
@ -82,9 +84,7 @@
"typescript": "3.0.1"
},
"dependencies": {
"@0x/base-contract": "^5.5.0-beta.3",
"@0x/typescript-typings": "^4.4.0-beta.2",
"ethereum-types": "^2.2.0-beta.2"
"@0x/base-contract": "^5.5.0-beta.3"
},
"publishConfig": {
"access": "public"

View File

@ -65,6 +65,9 @@
"@0x/ts-doc-gen": "^0.0.22",
"@0x/tslint-config": "^3.1.0-beta.2",
"@0x/web3-wrapper": "^6.1.0-beta.2",
"@0x/types": "^2.5.0-beta.2",
"@0x/typescript-typings": "^4.4.0-beta.2",
"ethereum-types": "^2.2.0-beta.2",
"@types/lodash": "4.14.104",
"@types/mocha": "^5.2.7",
"@types/node": "*",
@ -91,10 +94,7 @@
"@0x/contracts-erc20": "^2.3.0-beta.3",
"@0x/contracts-erc721": "^2.2.0-beta.3",
"@0x/order-utils": "^8.5.0-beta.3",
"@0x/types": "^2.5.0-beta.2",
"@0x/typescript-typings": "^4.4.0-beta.2",
"@0x/utils": "^4.6.0-beta.2",
"ethereum-types": "^2.2.0-beta.2",
"lodash": "^4.17.11"
},
"publishConfig": {

View File

@ -90,5 +90,8 @@
"source-map-support": "^0.5.6",
"typescript": "3.0.1",
"wsrun": "^2.2.0"
},
"resolutions": {
"merkle-patricia-tree": "^2.3.2"
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,504 +0,0 @@
[
{
"version": "6.2.0-beta.3",
"changes": [
{
"note": "Dependencies updated"
}
],
"timestamp": 1574238768
},
{
"version": "6.2.0-beta.2",
"changes": [
{
"note": "Dependencies updated"
}
],
"timestamp": 1574030254
},
{
"version": "6.2.0-beta.1",
"changes": [
{
"note": "All references to network ID have been removed, and references to chain ID have been introduced instead",
"pr": 2313
}
],
"timestamp": 1573159180
},
{
"version": "6.2.0-beta.0",
"changes": [
{
"note": "Dependencies updated"
}
],
"timestamp": 1570135330
},
{
"timestamp": 1568744790,
"version": "6.1.14",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1567521715,
"version": "6.1.13",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1566446343,
"version": "6.1.12",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1565296576,
"version": "6.1.11",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1564604963,
"version": "6.1.10",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1563957393,
"version": "6.1.9",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1563193019,
"version": "6.1.8",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1563047529,
"version": "6.1.7",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1563006338,
"version": "6.1.6",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1558712885,
"version": "6.1.5",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1557961111,
"version": "6.1.4",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"version": "6.1.3",
"changes": [
{
"note": "Convert `metaData.remainingTakerAssetAmount` to BigNumber if present in APIOrder",
"pr": 1810
}
],
"timestamp": 1557799313
},
{
"timestamp": 1557507213,
"version": "6.1.1",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"version": "6.1.0",
"changes": [
{
"note": "Moved order_utils into `@0x/order-utils` package as `orderCalculationUtils`",
"pr": 1714
}
],
"timestamp": 1554997931
},
{
"timestamp": 1553183790,
"version": "6.0.5",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1553091633,
"version": "6.0.4",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1551479279,
"version": "6.0.3",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1551299797,
"version": "6.0.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1551220833,
"version": "6.0.1",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"version": "6.0.0",
"changes": [
{
"note": "Add support for EIP1193 providers & Web3.js providers >= 1.0-beta.38",
"pr": 1627
},
{
"note": "Update provider params to type SupportedProvider which outlines all supported providers",
"pr": 1627
}
],
"timestamp": 1551130135
},
{
"timestamp": 1549733923,
"version": "5.0.4",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"version": "5.0.3",
"changes": [
{
"note": "Dependencies updated"
}
],
"timestamp": 1549547375
},
{
"timestamp": 1549504360,
"version": "5.0.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1549452781,
"version": "5.0.1",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"version": "5.0.0",
"changes": [
{
"note": "Upgrade the bignumber.js to v8.0.2",
"pr": 1517
}
],
"timestamp": 1549373905
},
{
"version": "4.1.0",
"changes": [
{
"note": "Adds new public method getLiquidityForAssetDataAsync, and exposes getOrdersAndFillableAmountsAsync as public method",
"pr": 1512
}
]
},
{
"timestamp": 1547747677,
"version": "4.0.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1547561734,
"version": "4.0.1",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"version": "4.0.0",
"changes": [
{
"note": "Raise custom InsufficientAssetLiquidityError error with amountAvailableToFill attribute",
"pr": 1437
}
],
"timestamp": 1547225310
},
{
"timestamp": 1547040760,
"version": "3.0.5",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"version": "3.0.4",
"changes": [
{
"note": "Dependencies updated"
}
],
"timestamp": 1544739608
},
{
"version": "3.0.3",
"changes": [
{
"note": "Update SRA order provider to include Dai"
}
],
"timestamp": 1544570656
},
{
"timestamp": 1543401373,
"version": "3.0.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"version": "3.0.1",
"changes": [
{
"note": "Dependencies updated",
"pr": 1276
}
],
"timestamp": 1542821676
},
{
"version": "3.0.0",
"changes": [
{
"note": "update `getBuyQuoteAsync` to return eth spent on assets instead of per unit amount",
"pr": 1252
}
],
"timestamp": 1542208198
},
{
"timestamp": 1542134075,
"version": "2.2.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1542028948,
"version": "2.2.1",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"version": "2.2.0",
"changes": [
{
"note": "`getAssetBuyerForProvidedOrders` factory function now takes 3 args instead of 4",
"pr": 1187
},
{
"note": "the `OrderProvider` now requires a new method `getAvailableMakerAssetDatasAsync` and the `StandardRelayerAPIOrderProvider` requires the network id at init.",
"pr": 1203
},
{
"note": "No longer require that provided orders all have the same maker and taker asset data",
"pr": 1197
},
{
"note": "Fix bug where `BuyQuoteInfo` objects could return `totalEthAmount` and `feeEthAmount` that were not whole numbers",
"pr": 1207
},
{
"note": "Fix bug where default values for `AssetBuyer` public facing methods could get overriden by `undefined` values",
"pr": 1207
},
{
"note": "Lower default expiry buffer from 5 minutes to 2 minutes",
"pr": 1217
}
],
"timestamp": 1541740904
},
{
"version": "2.1.0",
"changes": [
{
"note": "Add `gasLimit` and `gasPrice` as optional properties on `BuyQuoteExecutionOpts`"
},
{
"note": "Export `BuyQuoteInfo` type",
"pr": 1131
},
{
"note": "Updated to use new modularized artifacts and the latest version of @0xproject/contract-wrappers",
"pr": 1105
},
{
"note": "Add `gasLimit` and `gasPrice` as optional properties on `BuyQuoteExecutionOpts`",
"pr": 1116
},
{
"note": "Add `docs:json` command to package.json",
"pr": 1139
},
{
"note": "Add missing types to public interface",
"pr": 1139
},
{
"note": "Throw `SignatureRequestDenied` and `TransactionValueTooLow` errors when executing buy",
"pr": 1147
}
],
"timestamp": 1539871071
},
{
"version": "2.0.0",
"changes": [
{
"note": "Expand AssetBuyer to work with multiple assets at once",
"pr": 1086
},
{
"note": "Fix minRate and maxRate calculation",
"pr": 1113
}
],
"timestamp": 1538693146
},
{
"timestamp": 1538475601,
"version": "1.0.3",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1538157789,
"version": "1.0.2",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1537907159,
"version": "1.0.1",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"timestamp": 1537875740,
"version": "1.0.0",
"changes": [
{
"note": "Dependencies updated"
}
]
},
{
"version": "1.0.0-rc.1",
"changes": [
{
"note": "Init"
}
]
}
]

View File

@ -1,215 +0,0 @@
<!--
changelogUtils.file is auto-generated using the monorepo-scripts package. Don't edit directly.
Edit the package's CHANGELOG.json file only.
-->
CHANGELOG
## v6.2.0-beta.3 - _November 20, 2019_
* Dependencies updated
## v6.2.0-beta.2 - _November 17, 2019_
* Dependencies updated
## v6.2.0-beta.1 - _November 7, 2019_
* All references to network ID have been removed, and references to chain ID have been introduced instead (#2313)
## v6.2.0-beta.0 - _October 3, 2019_
* Dependencies updated
## v6.1.14 - _September 17, 2019_
* Dependencies updated
## v6.1.13 - _September 3, 2019_
* Dependencies updated
## v6.1.12 - _August 22, 2019_
* Dependencies updated
## v6.1.11 - _August 8, 2019_
* Dependencies updated
## v6.1.10 - _July 31, 2019_
* Dependencies updated
## v6.1.9 - _July 24, 2019_
* Dependencies updated
## v6.1.8 - _July 15, 2019_
* Dependencies updated
## v6.1.7 - _July 13, 2019_
* Dependencies updated
## v6.1.6 - _July 13, 2019_
* Dependencies updated
## v6.1.5 - _May 24, 2019_
* Dependencies updated
## v6.1.4 - _May 15, 2019_
* Dependencies updated
## v6.1.3 - _May 14, 2019_
* Convert `metaData.remainingTakerAssetAmount` to BigNumber if present in APIOrder (#1810)
## v6.1.1 - _May 10, 2019_
* Dependencies updated
## v6.1.0 - _April 11, 2019_
* Moved order_utils into `@0x/order-utils` package as `orderCalculationUtils` (#1714)
## v6.0.5 - _March 21, 2019_
* Dependencies updated
## v6.0.4 - _March 20, 2019_
* Dependencies updated
## v6.0.3 - _March 1, 2019_
* Dependencies updated
## v6.0.2 - _February 27, 2019_
* Dependencies updated
## v6.0.1 - _February 26, 2019_
* Dependencies updated
## v6.0.0 - _February 25, 2019_
* Add support for EIP1193 providers & Web3.js providers >= 1.0-beta.38 (#1627)
* Update provider params to type SupportedProvider which outlines all supported providers (#1627)
## v5.0.4 - _February 9, 2019_
* Dependencies updated
## v5.0.3 - _February 7, 2019_
* Dependencies updated
## v5.0.2 - _February 7, 2019_
* Dependencies updated
## v5.0.1 - _February 6, 2019_
* Dependencies updated
## v5.0.0 - _February 5, 2019_
* Upgrade the bignumber.js to v8.0.2 (#1517)
## v4.1.0 - _Invalid date_
* Adds new public method getLiquidityForAssetDataAsync, and exposes getOrdersAndFillableAmountsAsync as public method (#1512)
## v4.0.2 - _January 17, 2019_
* Dependencies updated
## v4.0.1 - _January 15, 2019_
* Dependencies updated
## v4.0.0 - _January 11, 2019_
* Raise custom InsufficientAssetLiquidityError error with amountAvailableToFill attribute (#1437)
## v3.0.5 - _January 9, 2019_
* Dependencies updated
## v3.0.4 - _December 13, 2018_
* Dependencies updated
## v3.0.3 - _December 11, 2018_
* Update SRA order provider to include Dai
## v3.0.2 - _November 28, 2018_
* Dependencies updated
## v3.0.1 - _November 21, 2018_
* Dependencies updated (#1276)
## v3.0.0 - _November 14, 2018_
* update `getBuyQuoteAsync` to return eth spent on assets instead of per unit amount (#1252)
## v2.2.2 - _November 13, 2018_
* Dependencies updated
## v2.2.1 - _November 12, 2018_
* Dependencies updated
## v2.2.0 - _November 9, 2018_
* `getAssetBuyerForProvidedOrders` factory function now takes 3 args instead of 4 (#1187)
* the `OrderProvider` now requires a new method `getAvailableMakerAssetDatasAsync` and the `StandardRelayerAPIOrderProvider` requires the network id at init. (#1203)
* No longer require that provided orders all have the same maker and taker asset data (#1197)
* Fix bug where `BuyQuoteInfo` objects could return `totalEthAmount` and `feeEthAmount` that were not whole numbers (#1207)
* Fix bug where default values for `AssetBuyer` public facing methods could get overriden by `undefined` values (#1207)
* Lower default expiry buffer from 5 minutes to 2 minutes (#1217)
## v2.1.0 - _October 18, 2018_
* Add `gasLimit` and `gasPrice` as optional properties on `BuyQuoteExecutionOpts`
* Export `BuyQuoteInfo` type (#1131)
* Updated to use new modularized artifacts and the latest version of @0xproject/contract-wrappers (#1105)
* Add `gasLimit` and `gasPrice` as optional properties on `BuyQuoteExecutionOpts` (#1116)
* Add `docs:json` command to package.json (#1139)
* Add missing types to public interface (#1139)
* Throw `SignatureRequestDenied` and `TransactionValueTooLow` errors when executing buy (#1147)
## v2.0.0 - _October 4, 2018_
* Expand AssetBuyer to work with multiple assets at once (#1086)
* Fix minRate and maxRate calculation (#1113)
## v1.0.3 - _October 2, 2018_
* Dependencies updated
## v1.0.2 - _September 28, 2018_
* Dependencies updated
## v1.0.1 - _September 25, 2018_
* Dependencies updated
## v1.0.0 - _September 25, 2018_
* Dependencies updated
## v1.0.0-rc.1 - _Invalid date_
* Init

View File

@ -1,83 +0,0 @@
## @0x/asset-buyer
Convenience package for buying assets represented on the Ethereum blockchain using 0x. In its simplest form, the package helps in the usage of the [0x forwarder contract](https://github.com/0xProject/0x-protocol-specification/blob/master/v2/forwarder-specification.md), which allows users to execute [Wrapped Ether](https://weth.io/) based 0x orders without having to set allowances, wrap Ether or own ZRX, meaning they can buy tokens with Ether alone. Given some liquidity (0x signed orders), it helps estimate the Ether cost of buying a certain asset (giving a range) and then buying that asset.
In its more advanced and useful form, it integrates with the [Standard Relayer API](https://github.com/0xProject/standard-relayer-api) and takes care of sourcing liquidity for you given an SRA compliant endpoint. The final result is a library that tells you what assets are available, provides an Ether based quote for any asset desired, and allows you to buy that asset using Ether alone.
## Installation
```bash
yarn add @0x/asset-buyer
```
**Import**
```typescript
import { AssetBuyer } from '@0x/asset-buyer';
```
or
```javascript
var AssetBuyer = require('@0x/asset-buyer').AssetBuyer;
```
If your project is in [TypeScript](https://www.typescriptlang.org/), add the following to your `tsconfig.json`:
```json
"compilerOptions": {
"typeRoots": ["node_modules/@0x/typescript-typings/types", "node_modules/@types"],
}
```
## Contributing
We welcome improvements and fixes from the wider community! To report bugs within this package, please create an issue in this repository.
Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started.
### Install dependencies
If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them:
```bash
yarn config set workspaces-experimental true
```
Then install dependencies
```bash
yarn install
```
### Build
To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory:
```bash
PKG=@0x/asset-buyer yarn build
```
Or continuously rebuild on change:
```bash
PKG=@0x/asset-buyer yarn watch
```
### Clean
```bash
yarn clean
```
### Lint
```bash
yarn lint
```
### Run Tests
```bash
yarn test
```

View File

@ -1,75 +0,0 @@
{
"name": "@0x/asset-buyer",
"version": "6.2.0-beta.3",
"engines": {
"node": ">=6.12"
},
"description": "Convenience package for discovering and buying assets with Ether.",
"main": "lib/src/index.js",
"types": "lib/src/index.d.ts",
"scripts": {
"build": "yarn tsc -b",
"build:ci": "yarn build",
"lint": "tslint --format stylish --project .",
"fix": "tslint --fix --format stylish --project .",
"test": "yarn run_mocha",
"rebuild_and_test": "run-s clean build test",
"test:coverage": "nyc npm run test --all && yarn coverage:report:lcov",
"coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info",
"test:circleci": "yarn test:coverage",
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe lib/test/**/*_test.js --exit",
"clean": "shx rm -rf lib test_temp"
},
"config": {
"postpublish": {
"assets": []
}
},
"repository": {
"type": "git",
"url": "https://github.com/0xProject/0x-monorepo.git"
},
"author": "",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/0xProject/0x-monorepo/issues"
},
"homepage": "https://github.com/0xProject/0x-monorepo/packages/asset-buyer/README.md",
"dependencies": {
"@0x/assert": "^2.2.0-beta.2",
"@0x/connect": "^5.1.0-beta.2",
"@0x/contract-wrappers": "^12.2.0-beta.3",
"@0x/json-schemas": "^4.1.0-beta.2",
"@0x/order-utils": "^8.5.0-beta.3",
"@0x/subproviders": "^5.1.0-beta.2",
"@0x/types": "^2.5.0-beta.2",
"@0x/typescript-typings": "^4.4.0-beta.2",
"@0x/utils": "^4.6.0-beta.2",
"@0x/web3-wrapper": "^6.1.0-beta.2",
"ethereum-types": "^2.2.0-beta.2",
"lodash": "^4.17.11"
},
"devDependencies": {
"@0x/ts-doc-gen": "^0.0.22",
"@0x/tslint-config": "^3.1.0-beta.2",
"@types/lodash": "4.14.104",
"@types/mocha": "^5.2.7",
"@types/node": "*",
"chai": "^4.0.1",
"chai-as-promised": "^7.1.0",
"chai-bignumber": "^3.0.0",
"dirty-chai": "^2.0.1",
"make-promises-safe": "^1.1.0",
"mocha": "^6.2.0",
"npm-run-all": "^4.1.2",
"nyc": "^11.0.1",
"shx": "^0.2.2",
"tslint": "5.11.0",
"typedoc": "^0.15.0",
"typemoq": "^2.1.0",
"typescript": "3.0.1"
},
"publishConfig": {
"access": "public"
}
}

View File

@ -1,377 +0,0 @@
import { ContractError, ContractWrappers, ForwarderError } from '@0x/contract-wrappers';
import { schemas } from '@0x/json-schemas';
import { assetDataUtils, SignedOrder } from '@0x/order-utils';
import { ObjectMap } from '@0x/types';
import { BigNumber, providerUtils } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { SupportedProvider, ZeroExProvider } from 'ethereum-types';
import * as _ from 'lodash';
import { constants } from './constants';
import { BasicOrderProvider } from './order_providers/basic_order_provider';
import { StandardRelayerAPIOrderProvider } from './order_providers/standard_relayer_api_order_provider';
import {
AssetBuyerError,
AssetBuyerOpts,
BuyQuote,
BuyQuoteExecutionOpts,
BuyQuoteRequestOpts,
LiquidityForAssetData,
LiquidityRequestOpts,
OrderProvider,
OrdersAndFillableAmounts,
} from './types';
import { assert } from './utils/assert';
import { buyQuoteCalculator } from './utils/buy_quote_calculator';
import { calculateLiquidity } from './utils/calculate_liquidity';
import { numberPercentageToEtherTokenAmountPercentage } from './utils/number_percentage_to_ethertoken_amount_percentage';
import { orderProviderResponseProcessor } from './utils/order_provider_response_processor';
interface OrdersEntry {
ordersAndFillableAmounts: OrdersAndFillableAmounts;
lastRefreshTime: number;
}
export class AssetBuyer {
public readonly provider: ZeroExProvider;
public readonly orderProvider: OrderProvider;
public readonly chainId: number;
public readonly orderRefreshIntervalMs: number;
public readonly expiryBufferSeconds: number;
private readonly _contractWrappers: ContractWrappers;
// cache of orders along with the time last updated keyed by assetData
private readonly _ordersEntryMap: ObjectMap<OrdersEntry> = {};
/**
* Instantiates a new AssetBuyer instance given existing liquidity in the form of orders and feeOrders.
* @param supportedProvider The Provider instance you would like to use for interacting with the Ethereum network.
* @param orders A non-empty array of objects that conform to SignedOrder. All orders must have the same makerAssetData and takerAssetData (WETH).
* @param feeOrders A array of objects that conform to SignedOrder. All orders must have the same makerAssetData (ZRX) and takerAssetData (WETH). Defaults to an empty array.
* @param options Initialization options for the AssetBuyer. See type definition for details.
*
* @return An instance of AssetBuyer
*/
public static getAssetBuyerForProvidedOrders(
supportedProvider: SupportedProvider,
orders: SignedOrder[],
options: Partial<AssetBuyerOpts> = {},
): AssetBuyer {
assert.doesConformToSchema('orders', orders, schemas.signedOrdersSchema);
assert.assert(orders.length !== 0, `Expected orders to contain at least one order`);
const orderProvider = new BasicOrderProvider(orders);
const assetBuyer = new AssetBuyer(supportedProvider, orderProvider, options);
return assetBuyer;
}
/**
* Instantiates a new AssetBuyer instance given a [Standard Relayer API](https://github.com/0xProject/standard-relayer-api) endpoint
* @param supportedProvider The Provider instance you would like to use for interacting with the Ethereum network.
* @param sraApiUrl The standard relayer API base HTTP url you would like to source orders from.
* @param options Initialization options for the AssetBuyer. See type definition for details.
*
* @return An instance of AssetBuyer
*/
public static getAssetBuyerForStandardRelayerAPIUrl(
supportedProvider: SupportedProvider,
sraApiUrl: string,
options: Partial<AssetBuyerOpts> = {},
): AssetBuyer {
const provider = providerUtils.standardizeOrThrow(supportedProvider);
assert.isWebUri('sraApiUrl', sraApiUrl);
// HACK: asset-buy will be deleted, but do not pass in chainId to allow everything to compile.
const orderProvider = new StandardRelayerAPIOrderProvider(sraApiUrl);
const assetBuyer = new AssetBuyer(provider, orderProvider, options);
return assetBuyer;
}
/**
* Instantiates a new AssetBuyer instance
* @param supportedProvider The Provider instance you would like to use for interacting with the Ethereum network.
* @param orderProvider An object that conforms to OrderProvider, see type for definition.
* @param options Initialization options for the AssetBuyer. See type definition for details.
*
* @return An instance of AssetBuyer
*/
constructor(
supportedProvider: SupportedProvider,
orderProvider: OrderProvider,
options: Partial<AssetBuyerOpts> = {},
) {
const { chainId, orderRefreshIntervalMs, expiryBufferSeconds } = _.merge(
{},
constants.DEFAULT_ASSET_BUYER_OPTS,
options,
);
const provider = providerUtils.standardizeOrThrow(supportedProvider);
assert.isValidOrderProvider('orderProvider', orderProvider);
assert.isNumber('chainId', chainId);
assert.isNumber('orderRefreshIntervalMs', orderRefreshIntervalMs);
assert.isNumber('expiryBufferSeconds', expiryBufferSeconds);
this.provider = provider;
this.orderProvider = orderProvider;
this.chainId = chainId;
this.orderRefreshIntervalMs = orderRefreshIntervalMs;
this.expiryBufferSeconds = expiryBufferSeconds;
this._contractWrappers = new ContractWrappers(this.provider, {
chainId,
});
}
/**
* Get a `BuyQuote` containing all information relevant to fulfilling a buy given a desired assetData.
* You can then pass the `BuyQuote` to `executeBuyQuoteAsync` to execute the buy.
* @param assetData The assetData of the desired asset to buy (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
* @param assetBuyAmount The amount of asset to buy.
* @param options Options for the request. See type definition for more information.
*
* @return An object that conforms to BuyQuote that satisfies the request. See type definition for more information.
*/
public async getBuyQuoteAsync(
assetData: string,
assetBuyAmount: BigNumber,
options: Partial<BuyQuoteRequestOpts> = {},
): Promise<BuyQuote> {
const { feePercentage, shouldForceOrderRefresh, slippagePercentage } = _.merge(
{},
constants.DEFAULT_BUY_QUOTE_REQUEST_OPTS,
options,
);
assert.isString('assetData', assetData);
assert.isBigNumber('assetBuyAmount', assetBuyAmount);
assert.isValidPercentage('feePercentage', feePercentage);
assert.isBoolean('shouldForceOrderRefresh', shouldForceOrderRefresh);
assert.isNumber('slippagePercentage', slippagePercentage);
const zrxTokenAssetData = await this._getZrxTokenAssetDataOrThrowAsync();
const isMakerAssetZrxToken = assetData === zrxTokenAssetData;
// get the relevant orders for the makerAsset and fees
// if the requested assetData is ZRX, don't get the fee info
const [ordersAndFillableAmounts, feeOrdersAndFillableAmounts] = await Promise.all([
this.getOrdersAndFillableAmountsAsync(assetData, shouldForceOrderRefresh),
isMakerAssetZrxToken
? Promise.resolve(constants.EMPTY_ORDERS_AND_FILLABLE_AMOUNTS)
: this.getOrdersAndFillableAmountsAsync(zrxTokenAssetData, shouldForceOrderRefresh),
shouldForceOrderRefresh,
]);
if (ordersAndFillableAmounts.orders.length === 0) {
throw new Error(`${AssetBuyerError.AssetUnavailable}: For assetData ${assetData}`);
}
const buyQuote = buyQuoteCalculator.calculate(
ordersAndFillableAmounts,
feeOrdersAndFillableAmounts,
assetBuyAmount,
feePercentage,
slippagePercentage,
isMakerAssetZrxToken,
);
return buyQuote;
}
/**
* Get a `BuyQuote` containing all information relevant to fulfilling a buy given a desired ERC20 token address.
* You can then pass the `BuyQuote` to `executeBuyQuoteAsync` to execute the buy.
* @param tokenAddress The ERC20 token address.
* @param assetBuyAmount The amount of asset to buy.
* @param options Options for the request. See type definition for more information.
*
* @return An object that conforms to BuyQuote that satisfies the request. See type definition for more information.
*/
public async getBuyQuoteForERC20TokenAddressAsync(
tokenAddress: string,
assetBuyAmount: BigNumber,
options: Partial<BuyQuoteRequestOpts> = {},
): Promise<BuyQuote> {
assert.isETHAddressHex('tokenAddress', tokenAddress);
assert.isBigNumber('assetBuyAmount', assetBuyAmount);
const assetData = await this._contractWrappers.devUtils.encodeERC20AssetData(tokenAddress).callAsync();
const buyQuote = this.getBuyQuoteAsync(assetData, assetBuyAmount, options);
return buyQuote;
}
/**
* Returns information about available liquidity for an asset
* Does not factor in slippage or fees
* @param assetData The assetData of the desired asset to buy (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
* @param options Options for the request. See type definition for more information.
*
* @return An object that conforms to LiquidityForAssetData that satisfies the request. See type definition for more information.
*/
public async getLiquidityForAssetDataAsync(
assetData: string,
options: Partial<LiquidityRequestOpts> = {},
): Promise<LiquidityForAssetData> {
const shouldForceOrderRefresh =
options.shouldForceOrderRefresh !== undefined ? options.shouldForceOrderRefresh : false;
assert.isString('assetData', assetData);
assetDataUtils.decodeAssetDataOrThrow(assetData);
assert.isBoolean('options.shouldForceOrderRefresh', shouldForceOrderRefresh);
const assetPairs = await this.orderProvider.getAvailableMakerAssetDatasAsync(assetData);
const etherTokenAssetData = await this._getEtherTokenAssetDataOrThrowAsync();
if (!assetPairs.includes(etherTokenAssetData)) {
return {
tokensAvailableInBaseUnits: new BigNumber(0),
ethValueAvailableInWei: new BigNumber(0),
};
}
const ordersAndFillableAmounts = await this.getOrdersAndFillableAmountsAsync(
assetData,
shouldForceOrderRefresh,
);
return calculateLiquidity(ordersAndFillableAmounts);
}
/**
* Given a BuyQuote and desired rate, attempt to execute the buy.
* @param buyQuote An object that conforms to BuyQuote. See type definition for more information.
* @param options Options for the execution of the BuyQuote. See type definition for more information.
*
* @return A promise of the txHash.
*/
public async executeBuyQuoteAsync(
buyQuote: BuyQuote,
options: Partial<BuyQuoteExecutionOpts> = {},
): Promise<string> {
const { ethAmount, takerAddress, feeRecipient, gasLimit, gasPrice } = _.merge(
{},
constants.DEFAULT_BUY_QUOTE_EXECUTION_OPTS,
options,
);
assert.isValidBuyQuote('buyQuote', buyQuote);
if (ethAmount !== undefined) {
assert.isBigNumber('ethAmount', ethAmount);
}
if (takerAddress !== undefined) {
assert.isETHAddressHex('takerAddress', takerAddress);
}
assert.isETHAddressHex('feeRecipient', feeRecipient);
if (gasLimit !== undefined) {
assert.isNumber('gasLimit', gasLimit);
}
if (gasPrice !== undefined) {
assert.isBigNumber('gasPrice', gasPrice);
}
const { orders, feeOrders, feePercentage, assetBuyAmount, worstCaseQuoteInfo } = buyQuote; // tslint:disable-line:no-unused-variable
// if no takerAddress is provided, try to get one from the provider
let finalTakerAddress;
if (takerAddress !== undefined) {
finalTakerAddress = takerAddress;
} else {
const web3Wrapper = new Web3Wrapper(this.provider);
const availableAddresses = await web3Wrapper.getAvailableAddressesAsync();
const firstAvailableAddress = _.head(availableAddresses);
if (firstAvailableAddress !== undefined) {
finalTakerAddress = firstAvailableAddress;
} else {
throw new Error(AssetBuyerError.NoAddressAvailable);
}
}
try {
// format fee percentage
const formattedFeePercentage = numberPercentageToEtherTokenAmountPercentage(feePercentage || 0);
// if no ethAmount is provided, default to the worst ethAmount from buyQuote
const value = ethAmount || worstCaseQuoteInfo.totalEthAmount;
const txHash = await this._contractWrappers.forwarder
.marketBuyOrdersWithEth(
orders,
assetBuyAmount,
orders.map(o => o.signature),
formattedFeePercentage,
feeRecipient,
)
.sendTransactionAsync({
value,
from: finalTakerAddress.toLowerCase(),
gas: gasLimit,
gasPrice,
});
return txHash;
} catch (err) {
if (_.includes(err.message, ContractError.SignatureRequestDenied)) {
throw new Error(AssetBuyerError.SignatureRequestDenied);
} else if (_.includes(err.message, ForwarderError.CompleteFillFailed)) {
throw new Error(AssetBuyerError.TransactionValueTooLow);
} else {
throw err;
}
}
}
/**
* Get the asset data of all assets that are purchaseable with ether token (wETH) in the order provider passed in at init.
*
* @return An array of asset data strings that can be purchased using wETH.
*/
public async getAvailableAssetDatasAsync(): Promise<string[]> {
const etherTokenAssetData = await this._getEtherTokenAssetDataOrThrowAsync();
return this.orderProvider.getAvailableMakerAssetDatasAsync(etherTokenAssetData);
}
/**
* Grab orders from the map, if there is a miss or it is time to refresh, fetch and process the orders
* @param assetData The assetData of the desired asset to buy (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
* @param shouldForceOrderRefresh If set to true, new orders and state will be fetched instead of waiting for the next orderRefreshIntervalMs.
*/
public async getOrdersAndFillableAmountsAsync(
assetData: string,
shouldForceOrderRefresh: boolean,
): Promise<OrdersAndFillableAmounts> {
// try to get ordersEntry from the map
const ordersEntryIfExists = this._ordersEntryMap[assetData];
// we should refresh if:
// we do not have any orders OR
// we are forced to OR
// we have some last refresh time AND that time was sufficiently long ago
const shouldRefresh =
ordersEntryIfExists === undefined ||
shouldForceOrderRefresh ||
// tslint:disable:restrict-plus-operands
ordersEntryIfExists.lastRefreshTime + this.orderRefreshIntervalMs < Date.now();
if (!shouldRefresh) {
const result = ordersEntryIfExists.ordersAndFillableAmounts;
return result;
}
const etherTokenAssetData = await this._getEtherTokenAssetDataOrThrowAsync();
const zrxTokenAssetData = await this._getZrxTokenAssetDataOrThrowAsync();
// construct orderProvider request
const orderProviderRequest = {
makerAssetData: assetData,
takerAssetData: etherTokenAssetData,
chainId: this.chainId,
};
const request = orderProviderRequest;
// get provider response
const response = await this.orderProvider.getOrdersAsync(request);
// since the order provider is an injected dependency, validate that it respects the API
// ie. it should only return maker/taker assetDatas that are specified
orderProviderResponseProcessor.throwIfInvalidResponse(response, request);
// process the responses into one object
const isMakerAssetZrxToken = assetData === zrxTokenAssetData;
const ordersAndFillableAmounts = await orderProviderResponseProcessor.processAsync(
response,
isMakerAssetZrxToken,
this.expiryBufferSeconds,
this._contractWrappers.orderValidator,
);
const lastRefreshTime = Date.now();
const updatedOrdersEntry = {
ordersAndFillableAmounts,
lastRefreshTime,
};
this._ordersEntryMap[assetData] = updatedOrdersEntry;
return ordersAndFillableAmounts;
}
/**
* Get the assetData that represents the WETH token.
* Will throw if WETH does not exist for the current chain.
*/
private async _getEtherTokenAssetDataOrThrowAsync(): Promise<string> {
return this._contractWrappers.devUtils
.encodeERC20AssetData(this._contractWrappers.contractAddresses.etherToken)
.callAsync();
}
/**
* Get the assetData that represents the ZRX token.
* Will throw if ZRX does not exist for the current chain.
*/
private async _getZrxTokenAssetDataOrThrowAsync(): Promise<string> {
return this._contractWrappers.devUtils
.encodeERC20AssetData(this._contractWrappers.contractAddresses.zrxToken)
.callAsync();
}
}

View File

@ -1,40 +0,0 @@
import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { AssetBuyerOpts, BuyQuoteExecutionOpts, BuyQuoteRequestOpts, OrdersAndFillableAmounts } from './types';
const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
const MAINNET_CHAIN_ID = 1;
const DEFAULT_ASSET_BUYER_OPTS: AssetBuyerOpts = {
chainId: MAINNET_CHAIN_ID,
orderRefreshIntervalMs: 10000, // 10 seconds
expiryBufferSeconds: 120, // 2 minutes
};
const DEFAULT_BUY_QUOTE_REQUEST_OPTS: BuyQuoteRequestOpts = {
feePercentage: 0,
shouldForceOrderRefresh: false,
slippagePercentage: 0.2, // 20% slippage protection
};
// Other default values are dynamically determined
const DEFAULT_BUY_QUOTE_EXECUTION_OPTS: BuyQuoteExecutionOpts = {
feeRecipient: NULL_ADDRESS,
};
const EMPTY_ORDERS_AND_FILLABLE_AMOUNTS: OrdersAndFillableAmounts = {
orders: [] as SignedOrder[],
remainingFillableMakerAssetAmounts: [] as BigNumber[],
};
export const constants = {
ZERO_AMOUNT: new BigNumber(0),
NULL_ADDRESS,
MAINNET_CHAIN_ID,
ETHER_TOKEN_DECIMALS: 18,
DEFAULT_ASSET_BUYER_OPTS,
DEFAULT_BUY_QUOTE_EXECUTION_OPTS,
DEFAULT_BUY_QUOTE_REQUEST_OPTS,
EMPTY_ORDERS_AND_FILLABLE_AMOUNTS,
};

View File

@ -1,22 +0,0 @@
import { BigNumber } from '@0x/utils';
import { AssetBuyerError } from './types';
/**
* Error class representing insufficient asset liquidity
*/
export class InsufficientAssetLiquidityError extends Error {
/**
* The amount availabe to fill (in base units) factoring in slippage.
*/
public amountAvailableToFill: BigNumber;
/**
* @param amountAvailableToFill The amount availabe to fill (in base units) factoring in slippage
*/
constructor(amountAvailableToFill: BigNumber) {
super(AssetBuyerError.InsufficientAssetLiquidity);
this.amountAvailableToFill = amountAvailableToFill;
// Setting prototype so instanceof works. See https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work
Object.setPrototypeOf(this, InsufficientAssetLiquidityError.prototype);
}
}

View File

@ -1,6 +0,0 @@
declare module '*.json' {
const json: any;
/* tslint:disable */
export default json;
/* tslint:enable */
}

View File

@ -1,37 +0,0 @@
export {
JSONRPCRequestPayload,
JSONRPCResponsePayload,
JSONRPCResponseError,
JSONRPCErrorCallback,
SupportedProvider,
Web3JsProvider,
GanacheProvider,
EIP1193Provider,
ZeroExProvider,
EIP1193Event,
Web3JsV1Provider,
Web3JsV2Provider,
Web3JsV3Provider,
} from 'ethereum-types';
export { SignedOrder } from '@0x/types';
export { BigNumber } from '@0x/utils';
export { AssetBuyer } from './asset_buyer';
export { InsufficientAssetLiquidityError } from './errors';
export { BasicOrderProvider } from './order_providers/basic_order_provider';
export { StandardRelayerAPIOrderProvider } from './order_providers/standard_relayer_api_order_provider';
export {
AssetBuyerError,
AssetBuyerOpts,
BuyQuote,
BuyQuoteExecutionOpts,
BuyQuoteInfo,
BuyQuoteRequestOpts,
LiquidityForAssetData,
LiquidityRequestOpts,
OrdersAndFillableAmounts,
OrderProvider,
OrderProviderRequest,
OrderProviderResponse,
SignedOrderWithRemainingFillableMakerAssetAmount,
} from './types';

View File

@ -1,41 +0,0 @@
import { schemas } from '@0x/json-schemas';
import { SignedOrder } from '@0x/types';
import * as _ from 'lodash';
import { OrderProvider, OrderProviderRequest, OrderProviderResponse } from '../types';
import { assert } from '../utils/assert';
export class BasicOrderProvider implements OrderProvider {
public readonly orders: SignedOrder[];
/**
* Instantiates a new BasicOrderProvider instance
* @param orders An array of objects that conform to SignedOrder to fetch from.
* @return An instance of BasicOrderProvider
*/
constructor(orders: SignedOrder[]) {
assert.doesConformToSchema('orders', orders, schemas.signedOrdersSchema);
this.orders = orders;
}
/**
* Given an object that conforms to OrderFetcherRequest, return the corresponding OrderProviderResponse that satisfies the request.
* @param orderProviderRequest An instance of OrderFetcherRequest. See type for more information.
* @return An instance of OrderProviderResponse. See type for more information.
*/
public async getOrdersAsync(orderProviderRequest: OrderProviderRequest): Promise<OrderProviderResponse> {
assert.isValidOrderProviderRequest('orderProviderRequest', orderProviderRequest);
const { makerAssetData, takerAssetData } = orderProviderRequest;
const orders = _.filter(this.orders, order => {
return order.makerAssetData === makerAssetData && order.takerAssetData === takerAssetData;
});
return { orders };
}
/**
* Given a taker asset data string, return all availabled paired maker asset data strings.
* @param takerAssetData A string representing the taker asset data.
* @return An array of asset data strings that can be purchased using takerAssetData.
*/
public async getAvailableMakerAssetDatasAsync(takerAssetData: string): Promise<string[]> {
const ordersWithTakerAssetData = _.filter(this.orders, { takerAssetData });
return _.map(ordersWithTakerAssetData, order => order.makerAssetData);
}
}

View File

@ -1,109 +0,0 @@
import { HttpClient } from '@0x/connect';
import { orderCalculationUtils } from '@0x/order-utils';
import { APIOrder, AssetPairsResponse, OrderbookResponse } from '@0x/types';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import {
AssetBuyerError,
OrderProvider,
OrderProviderRequest,
OrderProviderResponse,
SignedOrderWithRemainingFillableMakerAssetAmount,
} from '../types';
import { assert } from '../utils/assert';
export class StandardRelayerAPIOrderProvider implements OrderProvider {
public readonly apiUrl: string;
private readonly _sraClient: HttpClient;
/**
* Given an array of APIOrder objects from a standard relayer api, return an array
* of SignedOrderWithRemainingFillableMakerAssetAmounts
*/
private static _getSignedOrderWithRemainingFillableMakerAssetAmountFromApi(
apiOrders: APIOrder[],
): SignedOrderWithRemainingFillableMakerAssetAmount[] {
const result = _.map(apiOrders, apiOrder => {
const { order, metaData } = apiOrder;
// The contents of metaData is not explicity defined in the spec
// We check for remainingTakerAssetAmount as a string and use this value if populated
const metaDataRemainingTakerAssetAmount = _.get(metaData, 'remainingTakerAssetAmount') as
| string
| undefined;
const remainingFillableTakerAssetAmount = metaDataRemainingTakerAssetAmount
? new BigNumber(metaDataRemainingTakerAssetAmount)
: order.takerAssetAmount;
const remainingFillableMakerAssetAmount = orderCalculationUtils.getMakerFillAmount(
order,
remainingFillableTakerAssetAmount,
);
const newOrder = {
...order,
remainingFillableMakerAssetAmount,
};
return newOrder;
});
return result;
}
/**
* Instantiates a new StandardRelayerAPIOrderProvider instance
* @param apiUrl The standard relayer API base HTTP url you would like to source orders from.
* @return An instance of StandardRelayerAPIOrderProvider
*/
constructor(apiUrl: string) {
assert.isWebUri('apiUrl', apiUrl);
this.apiUrl = apiUrl;
this._sraClient = new HttpClient(apiUrl);
}
/**
* Given an object that conforms to OrderProviderRequest, return the corresponding OrderProviderResponse that satisfies the request.
* @param orderProviderRequest An instance of OrderProviderRequest. See type for more information.
* @return An instance of OrderProviderResponse. See type for more information.
*/
public async getOrdersAsync(orderProviderRequest: OrderProviderRequest): Promise<OrderProviderResponse> {
assert.isValidOrderProviderRequest('orderProviderRequest', orderProviderRequest);
const { makerAssetData, takerAssetData } = orderProviderRequest;
const orderbookRequest = { baseAssetData: makerAssetData, quoteAssetData: takerAssetData };
let orderbook: OrderbookResponse;
try {
orderbook = await this._sraClient.getOrderbookAsync(orderbookRequest);
} catch (err) {
throw new Error(AssetBuyerError.StandardRelayerApiError);
}
const apiOrders = orderbook.asks.records;
const orders = StandardRelayerAPIOrderProvider._getSignedOrderWithRemainingFillableMakerAssetAmountFromApi(
apiOrders,
);
return {
orders,
};
}
/**
* Given a taker asset data string, return all availabled paired maker asset data strings.
* @param takerAssetData A string representing the taker asset data.
* @return An array of asset data strings that can be purchased using takerAssetData.
*/
public async getAvailableMakerAssetDatasAsync(takerAssetData: string): Promise<string[]> {
// Return a maximum of 1000 asset datas
const maxPerPage = 1000;
const requestOpts = { perPage: maxPerPage };
const assetPairsRequest = { assetDataA: takerAssetData };
const fullRequest = {
...requestOpts,
...assetPairsRequest,
};
let response: AssetPairsResponse;
try {
response = await this._sraClient.getAssetPairsAsync(fullRequest);
} catch (err) {
throw new Error(AssetBuyerError.StandardRelayerApiError);
}
return _.map(response.records, item => {
if (item.assetDataA.assetData === takerAssetData) {
return item.assetDataB.assetData;
} else {
return item.assetDataA.assetData;
}
});
}
}

View File

@ -1,142 +0,0 @@
import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
/**
* makerAssetData: The assetData representing the desired makerAsset.
* takerAssetData: The assetData representing the desired takerAsset.
* chainId: The chainId that the desired orders should be for.
*/
export interface OrderProviderRequest {
makerAssetData: string;
takerAssetData: string;
}
/**
* orders: An array of orders with optional remaining fillable makerAsset amounts. See type for more info.
*/
export interface OrderProviderResponse {
orders: SignedOrderWithRemainingFillableMakerAssetAmount[];
}
/**
* A normal SignedOrder with one extra optional property `remainingFillableMakerAssetAmount`
* remainingFillableMakerAssetAmount: The amount of the makerAsset that is available to be filled
*/
export interface SignedOrderWithRemainingFillableMakerAssetAmount extends SignedOrder {
remainingFillableMakerAssetAmount?: BigNumber;
}
/**
* gerOrdersAsync: Given an OrderProviderRequest, get an OrderProviderResponse.
* getAvailableMakerAssetDatasAsync: Given a taker asset data string, return all availabled paired maker asset data strings.
*/
export interface OrderProvider {
getOrdersAsync: (orderProviderRequest: OrderProviderRequest) => Promise<OrderProviderResponse>;
getAvailableMakerAssetDatasAsync: (takerAssetData: string) => Promise<string[]>;
}
/**
* assetData: String that represents a specific asset (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
* assetBuyAmount: The amount of asset to buy.
* orders: An array of objects conforming to SignedOrder. These orders can be used to cover the requested assetBuyAmount plus slippage.
* feeOrders: An array of objects conforming to SignedOrder. These orders can be used to cover the fees for the orders param above.
* feePercentage: Optional affiliate fee percentage used to calculate the eth amounts above.
* bestCaseQuoteInfo: Info about the best case price for the asset.
* worstCaseQuoteInfo: Info about the worst case price for the asset.
*/
export interface BuyQuote {
assetData: string;
assetBuyAmount: BigNumber;
orders: SignedOrder[];
feeOrders: SignedOrder[];
feePercentage?: number;
bestCaseQuoteInfo: BuyQuoteInfo;
worstCaseQuoteInfo: BuyQuoteInfo;
}
/**
* assetEthAmount: The amount of eth required to pay for the requested asset.
* feeEthAmount: The amount of eth required to pay the affiliate fee.
* totalEthAmount: The total amount of eth required to complete the buy (filling orders, feeOrders, and paying affiliate fee).
*/
export interface BuyQuoteInfo {
assetEthAmount: BigNumber;
feeEthAmount: BigNumber;
totalEthAmount: BigNumber;
}
/**
* feePercentage: The affiliate fee percentage. Defaults to 0.
* shouldForceOrderRefresh: If set to true, new orders and state will be fetched instead of waiting for the next orderRefreshIntervalMs. Defaults to false.
* slippagePercentage: The percentage buffer to add to account for slippage. Affects max ETH price estimates. Defaults to 0.2 (20%).
*/
export interface BuyQuoteRequestOpts {
feePercentage: number;
shouldForceOrderRefresh: boolean;
slippagePercentage: number;
}
/*
* Options for checking liquidity
*
* shouldForceOrderRefresh: If set to true, new orders and state will be fetched instead of waiting for the next orderRefreshIntervalMs. Defaults to false.
*/
export type LiquidityRequestOpts = Pick<BuyQuoteRequestOpts, 'shouldForceOrderRefresh'>;
/**
* ethAmount: The desired amount of eth to spend. Defaults to buyQuote.worstCaseQuoteInfo.totalEthAmount.
* takerAddress: The address to perform the buy. Defaults to the first available address from the provider.
* gasLimit: The amount of gas to send with a transaction (in Gwei). Defaults to an eth_estimateGas rpc call.
* gasPrice: Gas price in Wei to use for a transaction
* feeRecipient: The address where affiliate fees are sent. Defaults to null address (0x000...000).
*/
export interface BuyQuoteExecutionOpts {
ethAmount?: BigNumber;
takerAddress?: string;
gasLimit?: number;
gasPrice?: BigNumber;
feeRecipient: string;
}
/**
* chainId: The ethereum chain id. Defaults to 1 (mainnet).
* orderRefreshIntervalMs: The interval in ms that getBuyQuoteAsync should trigger an refresh of orders and order states. Defaults to 10000ms (10s).
* expiryBufferSeconds: The number of seconds to add when calculating whether an order is expired or not. Defaults to 300s (5m).
*/
export interface AssetBuyerOpts {
chainId: number;
orderRefreshIntervalMs: number;
expiryBufferSeconds: number;
}
/**
* Possible error messages thrown by an AssetBuyer instance or associated static methods.
*/
export enum AssetBuyerError {
NoEtherTokenContractFound = 'NO_ETHER_TOKEN_CONTRACT_FOUND',
NoZrxTokenContractFound = 'NO_ZRX_TOKEN_CONTRACT_FOUND',
StandardRelayerApiError = 'STANDARD_RELAYER_API_ERROR',
InsufficientAssetLiquidity = 'INSUFFICIENT_ASSET_LIQUIDITY',
InsufficientZrxLiquidity = 'INSUFFICIENT_ZRX_LIQUIDITY',
NoAddressAvailable = 'NO_ADDRESS_AVAILABLE',
InvalidOrderProviderResponse = 'INVALID_ORDER_PROVIDER_RESPONSE',
AssetUnavailable = 'ASSET_UNAVAILABLE',
SignatureRequestDenied = 'SIGNATURE_REQUEST_DENIED',
TransactionValueTooLow = 'TRANSACTION_VALUE_TOO_LOW',
}
/**
* orders: An array of signed orders
* remainingFillableMakerAssetAmounts: A list of fillable amounts for the signed orders. The index of an item in the array associates the amount with the corresponding order.
*/
export interface OrdersAndFillableAmounts {
orders: SignedOrder[];
remainingFillableMakerAssetAmounts: BigNumber[];
}
/**
* Represents available liquidity for a given assetData
*/
export interface LiquidityForAssetData {
tokensAvailableInBaseUnits: BigNumber;
ethValueAvailableInWei: BigNumber;
}

View File

@ -1,38 +0,0 @@
import { assert as sharedAssert } from '@0x/assert';
import { schemas } from '@0x/json-schemas';
import { BuyQuote, BuyQuoteInfo, OrderProvider, OrderProviderRequest } from '../types';
export const assert = {
...sharedAssert,
isValidBuyQuote(variableName: string, buyQuote: BuyQuote): void {
sharedAssert.isHexString(`${variableName}.assetData`, buyQuote.assetData);
sharedAssert.doesConformToSchema(`${variableName}.orders`, buyQuote.orders, schemas.signedOrdersSchema);
sharedAssert.doesConformToSchema(`${variableName}.feeOrders`, buyQuote.feeOrders, schemas.signedOrdersSchema);
assert.isValidBuyQuoteInfo(`${variableName}.bestCaseQuoteInfo`, buyQuote.bestCaseQuoteInfo);
assert.isValidBuyQuoteInfo(`${variableName}.worstCaseQuoteInfo`, buyQuote.worstCaseQuoteInfo);
sharedAssert.isBigNumber(`${variableName}.assetBuyAmount`, buyQuote.assetBuyAmount);
if (buyQuote.feePercentage !== undefined) {
sharedAssert.isNumber(`${variableName}.feePercentage`, buyQuote.feePercentage);
}
},
isValidBuyQuoteInfo(variableName: string, buyQuoteInfo: BuyQuoteInfo): void {
sharedAssert.isBigNumber(`${variableName}.assetEthAmount`, buyQuoteInfo.assetEthAmount);
sharedAssert.isBigNumber(`${variableName}.feeEthAmount`, buyQuoteInfo.feeEthAmount);
sharedAssert.isBigNumber(`${variableName}.totalEthAmount`, buyQuoteInfo.totalEthAmount);
},
isValidOrderProvider(variableName: string, orderFetcher: OrderProvider): void {
sharedAssert.isFunction(`${variableName}.getOrdersAsync`, orderFetcher.getOrdersAsync);
},
isValidOrderProviderRequest(variableName: string, orderFetcherRequest: OrderProviderRequest): void {
sharedAssert.isHexString(`${variableName}.makerAssetData`, orderFetcherRequest.makerAssetData);
sharedAssert.isHexString(`${variableName}.takerAssetData`, orderFetcherRequest.takerAssetData);
},
isValidPercentage(variableName: string, percentage: number): void {
assert.isNumber(variableName, percentage);
assert.assert(
percentage >= 0 && percentage <= 1,
`Expected ${variableName} to be between 0 and 1, but is ${percentage}`,
);
},
};

View File

@ -1,219 +0,0 @@
import { marketUtils, orderCalculationUtils, SignedOrder } from '@0x/order-utils';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import { constants } from '../constants';
import { InsufficientAssetLiquidityError } from '../errors';
import { AssetBuyerError, BuyQuote, BuyQuoteInfo, OrdersAndFillableAmounts } from '../types';
// Calculates a buy quote for orders that have WETH as the takerAsset
export const buyQuoteCalculator = {
calculate(
ordersAndFillableAmounts: OrdersAndFillableAmounts,
feeOrdersAndFillableAmounts: OrdersAndFillableAmounts,
assetBuyAmount: BigNumber,
feePercentage: number,
slippagePercentage: number,
isMakerAssetZrxToken: boolean,
): BuyQuote {
const orders = ordersAndFillableAmounts.orders;
const remainingFillableMakerAssetAmounts = ordersAndFillableAmounts.remainingFillableMakerAssetAmounts;
const feeOrders = feeOrdersAndFillableAmounts.orders;
const remainingFillableFeeAmounts = feeOrdersAndFillableAmounts.remainingFillableMakerAssetAmounts;
const slippageBufferAmount = assetBuyAmount.multipliedBy(slippagePercentage).integerValue();
// find the orders that cover the desired assetBuyAmount (with slippage)
const {
resultOrders,
remainingFillAmount,
ordersRemainingFillableMakerAssetAmounts,
} = marketUtils.findOrdersThatCoverMakerAssetFillAmount(orders, assetBuyAmount, {
remainingFillableMakerAssetAmounts,
slippageBufferAmount,
});
// if we do not have enough orders to cover the desired assetBuyAmount, throw
if (remainingFillAmount.gt(constants.ZERO_AMOUNT)) {
// We needed the amount they requested to buy, plus the amount for slippage
const totalAmountRequested = assetBuyAmount.plus(slippageBufferAmount);
const amountAbleToFill = totalAmountRequested.minus(remainingFillAmount);
// multiplierNeededWithSlippage represents what we need to multiply the assetBuyAmount by
// in order to get the total amount needed considering slippage
// i.e. if slippagePercent was 0.2 (20%), multiplierNeededWithSlippage would be 1.2
const multiplierNeededWithSlippage = new BigNumber(1).plus(slippagePercentage);
// Given amountAvailableToFillConsideringSlippage * multiplierNeededWithSlippage = amountAbleToFill
// We divide amountUnableToFill by multiplierNeededWithSlippage to determine amountAvailableToFillConsideringSlippage
const amountAvailableToFillConsideringSlippage = amountAbleToFill
.div(multiplierNeededWithSlippage)
.integerValue(BigNumber.ROUND_FLOOR);
throw new InsufficientAssetLiquidityError(amountAvailableToFillConsideringSlippage);
}
// if we are not buying ZRX:
// given the orders calculated above, find the fee-orders that cover the desired assetBuyAmount (with slippage)
// TODO(bmillman): optimization
// update this logic to find the minimum amount of feeOrders to cover the worst case as opposed to
// finding order that cover all fees, this will help with estimating ETH and minimizing gas usage
let resultFeeOrders = [] as SignedOrder[];
let feeOrdersRemainingFillableMakerAssetAmounts = [] as BigNumber[];
if (!isMakerAssetZrxToken) {
const feeOrdersAndRemainingFeeAmount = marketUtils.findFeeOrdersThatCoverFeesForTargetOrders(
resultOrders,
feeOrders,
{
remainingFillableMakerAssetAmounts: ordersRemainingFillableMakerAssetAmounts,
remainingFillableFeeAmounts,
},
);
// if we do not have enough feeOrders to cover the fees, throw
if (feeOrdersAndRemainingFeeAmount.remainingFeeAmount.gt(constants.ZERO_AMOUNT)) {
throw new Error(AssetBuyerError.InsufficientZrxLiquidity);
}
resultFeeOrders = feeOrdersAndRemainingFeeAmount.resultFeeOrders;
feeOrdersRemainingFillableMakerAssetAmounts =
feeOrdersAndRemainingFeeAmount.feeOrdersRemainingFillableMakerAssetAmounts;
}
// assetData information for the result
const assetData = orders[0].makerAssetData;
// compile the resulting trimmed set of orders for makerAsset and feeOrders that are needed for assetBuyAmount
const trimmedOrdersAndFillableAmounts: OrdersAndFillableAmounts = {
orders: resultOrders,
remainingFillableMakerAssetAmounts: ordersRemainingFillableMakerAssetAmounts,
};
const trimmedFeeOrdersAndFillableAmounts: OrdersAndFillableAmounts = {
orders: resultFeeOrders,
remainingFillableMakerAssetAmounts: feeOrdersRemainingFillableMakerAssetAmounts,
};
const bestCaseQuoteInfo = calculateQuoteInfo(
trimmedOrdersAndFillableAmounts,
trimmedFeeOrdersAndFillableAmounts,
assetBuyAmount,
feePercentage,
isMakerAssetZrxToken,
);
// in order to calculate the maxRate, reverse the ordersAndFillableAmounts such that they are sorted from worst rate to best rate
const worstCaseQuoteInfo = calculateQuoteInfo(
reverseOrdersAndFillableAmounts(trimmedOrdersAndFillableAmounts),
reverseOrdersAndFillableAmounts(trimmedFeeOrdersAndFillableAmounts),
assetBuyAmount,
feePercentage,
isMakerAssetZrxToken,
);
return {
assetData,
orders: resultOrders,
feeOrders: resultFeeOrders,
bestCaseQuoteInfo,
worstCaseQuoteInfo,
assetBuyAmount,
feePercentage,
};
},
};
function calculateQuoteInfo(
ordersAndFillableAmounts: OrdersAndFillableAmounts,
feeOrdersAndFillableAmounts: OrdersAndFillableAmounts,
assetBuyAmount: BigNumber,
feePercentage: number,
isMakerAssetZrxToken: boolean,
): BuyQuoteInfo {
// find the total eth and zrx needed to buy assetAmount from the resultOrders from left to right
let assetEthAmount = constants.ZERO_AMOUNT;
let zrxEthAmount = constants.ZERO_AMOUNT;
if (isMakerAssetZrxToken) {
assetEthAmount = findEthAmountNeededToBuyZrx(ordersAndFillableAmounts, assetBuyAmount);
} else {
// find eth and zrx amounts needed to buy
const ethAndZrxAmountToBuyAsset = findEthAndZrxAmountNeededToBuyAsset(ordersAndFillableAmounts, assetBuyAmount);
assetEthAmount = ethAndZrxAmountToBuyAsset[0];
const zrxAmountToBuyAsset = ethAndZrxAmountToBuyAsset[1];
// find eth amount needed to buy zrx
zrxEthAmount = findEthAmountNeededToBuyZrx(feeOrdersAndFillableAmounts, zrxAmountToBuyAsset);
}
// eth amount needed to buy the affiliate fee
const affiliateFeeEthAmount = assetEthAmount.multipliedBy(feePercentage).integerValue(BigNumber.ROUND_CEIL);
// eth amount needed for fees is the sum of affiliate fee and zrx fee
const feeEthAmount = affiliateFeeEthAmount.plus(zrxEthAmount);
// eth amount needed in total is the sum of the amount needed for the asset and the amount needed for fees
const totalEthAmount = assetEthAmount.plus(feeEthAmount);
return {
assetEthAmount,
feeEthAmount,
totalEthAmount,
};
}
// given an OrdersAndFillableAmounts, reverse the orders and remainingFillableMakerAssetAmounts properties
function reverseOrdersAndFillableAmounts(ordersAndFillableAmounts: OrdersAndFillableAmounts): OrdersAndFillableAmounts {
const ordersCopy = _.clone(ordersAndFillableAmounts.orders);
const remainingFillableMakerAssetAmountsCopy = _.clone(ordersAndFillableAmounts.remainingFillableMakerAssetAmounts);
return {
orders: ordersCopy.reverse(),
remainingFillableMakerAssetAmounts: remainingFillableMakerAssetAmountsCopy.reverse(),
};
}
function findEthAmountNeededToBuyZrx(
feeOrdersAndFillableAmounts: OrdersAndFillableAmounts,
zrxBuyAmount: BigNumber,
): BigNumber {
const { orders, remainingFillableMakerAssetAmounts } = feeOrdersAndFillableAmounts;
const result = _.reduce(
orders,
(acc, order, index) => {
const { totalEthAmount, remainingZrxBuyAmount } = acc;
const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index];
const makerFillAmount = BigNumber.min(remainingZrxBuyAmount, remainingFillableMakerAssetAmount);
const [takerFillAmount, adjustedMakerFillAmount] = orderCalculationUtils.getTakerFillAmountForFeeOrder(
order,
makerFillAmount,
);
const extraFeeAmount = remainingFillableMakerAssetAmount.isGreaterThanOrEqualTo(adjustedMakerFillAmount)
? constants.ZERO_AMOUNT
: adjustedMakerFillAmount.minus(makerFillAmount);
return {
totalEthAmount: totalEthAmount.plus(takerFillAmount),
remainingZrxBuyAmount: BigNumber.max(
constants.ZERO_AMOUNT,
remainingZrxBuyAmount.minus(makerFillAmount).plus(extraFeeAmount),
),
};
},
{
totalEthAmount: constants.ZERO_AMOUNT,
remainingZrxBuyAmount: zrxBuyAmount,
},
);
return result.totalEthAmount;
}
function findEthAndZrxAmountNeededToBuyAsset(
ordersAndFillableAmounts: OrdersAndFillableAmounts,
assetBuyAmount: BigNumber,
): [BigNumber, BigNumber] {
const { orders, remainingFillableMakerAssetAmounts } = ordersAndFillableAmounts;
const result = _.reduce(
orders,
(acc, order, index) => {
const { totalEthAmount, totalZrxAmount, remainingAssetBuyAmount } = acc;
const remainingFillableMakerAssetAmount = remainingFillableMakerAssetAmounts[index];
const makerFillAmount = BigNumber.min(acc.remainingAssetBuyAmount, remainingFillableMakerAssetAmount);
const takerFillAmount = orderCalculationUtils.getTakerFillAmount(order, makerFillAmount);
const takerFeeAmount = orderCalculationUtils.getTakerFeeAmount(order, takerFillAmount);
return {
totalEthAmount: totalEthAmount.plus(takerFillAmount),
totalZrxAmount: totalZrxAmount.plus(takerFeeAmount),
remainingAssetBuyAmount: BigNumber.max(
constants.ZERO_AMOUNT,
remainingAssetBuyAmount.minus(makerFillAmount),
),
};
},
{
totalEthAmount: constants.ZERO_AMOUNT,
totalZrxAmount: constants.ZERO_AMOUNT,
remainingAssetBuyAmount: assetBuyAmount,
},
);
return [result.totalEthAmount, result.totalZrxAmount];
}

View File

@ -1,36 +0,0 @@
import { orderCalculationUtils } from '@0x/order-utils';
import { BigNumber } from '@0x/utils';
import { LiquidityForAssetData, OrdersAndFillableAmounts } from '../types';
export const calculateLiquidity = (ordersAndFillableAmounts: OrdersAndFillableAmounts): LiquidityForAssetData => {
const { orders, remainingFillableMakerAssetAmounts } = ordersAndFillableAmounts;
const liquidityInBigNumbers = orders.reduce(
(acc, order, curIndex) => {
const availableMakerAssetAmount = remainingFillableMakerAssetAmounts[curIndex];
if (availableMakerAssetAmount === undefined) {
throw new Error(`No corresponding fillableMakerAssetAmounts at index ${curIndex}`);
}
const tokensAvailableForCurrentOrder = availableMakerAssetAmount;
const ethValueAvailableForCurrentOrder = orderCalculationUtils.getTakerFillAmount(
order,
availableMakerAssetAmount,
);
return {
tokensAvailableInBaseUnits: acc.tokensAvailableInBaseUnits.plus(tokensAvailableForCurrentOrder),
ethValueAvailableInWei: acc.ethValueAvailableInWei.plus(ethValueAvailableForCurrentOrder),
};
},
{
tokensAvailableInBaseUnits: new BigNumber(0),
ethValueAvailableInWei: new BigNumber(0),
},
);
// Turn into regular numbers
return {
tokensAvailableInBaseUnits: liquidityInBigNumbers.tokensAvailableInBaseUnits,
ethValueAvailableInWei: liquidityInBigNumbers.ethValueAvailableInWei,
};
};

View File

@ -1,9 +0,0 @@
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
// HACK (xianny): copied from asset-swapper, which will replace this package
const ONE_AMOUNT = new BigNumber(1);
const ETHER_TOKEN_DECIMALS = 18;
export const numberPercentageToEtherTokenAmountPercentage = (percentage: number): BigNumber => {
return Web3Wrapper.toBaseUnitAmount(ONE_AMOUNT, ETHER_TOKEN_DECIMALS).multipliedBy(percentage);
};

View File

@ -1,180 +0,0 @@
import { OrderStatus, OrderValidatorContract } from '@0x/contract-wrappers';
import { orderCalculationUtils, sortingUtils } from '@0x/order-utils';
import { RemainingFillableCalculator } from '@0x/order-utils/lib/src/remaining_fillable_calculator';
import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import { constants } from '../constants';
import {
AssetBuyerError,
OrderProviderRequest,
OrderProviderResponse,
OrdersAndFillableAmounts,
SignedOrderWithRemainingFillableMakerAssetAmount,
} from '../types';
export const orderProviderResponseProcessor = {
throwIfInvalidResponse(response: OrderProviderResponse, request: OrderProviderRequest): void {
const { makerAssetData, takerAssetData } = request;
_.forEach(response.orders, order => {
if (order.makerAssetData !== makerAssetData || order.takerAssetData !== takerAssetData) {
throw new Error(AssetBuyerError.InvalidOrderProviderResponse);
}
});
},
/**
* Take the responses for the target orders to buy and fee orders and process them.
* Processing includes:
* - Drop orders that are expired or not open orders (null taker address)
* - If shouldValidateOnChain, attempt to grab fillable amounts from on-chain otherwise assume completely fillable
* - Sort by rate
*/
async processAsync(
orderProviderResponse: OrderProviderResponse,
isMakerAssetZrxToken: boolean,
expiryBufferSeconds: number,
orderValidator?: OrderValidatorContract,
): Promise<OrdersAndFillableAmounts> {
// drop orders that are expired or not open
const filteredOrders = filterOutExpiredAndNonOpenOrders(orderProviderResponse.orders, expiryBufferSeconds);
// set the orders to be sorted equal to the filtered orders
let unsortedOrders = filteredOrders;
// if an orderValidator is provided, use on chain information to calculate remaining fillable makerAsset amounts
if (orderValidator !== undefined) {
const takerAddresses = _.map(filteredOrders, () => constants.NULL_ADDRESS);
try {
const [ordersInfo, tradersInfo] = await orderValidator
.getOrdersAndTradersInfo(filteredOrders, takerAddresses)
.callAsync();
const ordersAndTradersInfo = ordersInfo.map((orderInfo, index) => {
return {
orderInfo,
traderInfo: tradersInfo[index],
};
});
// take orders + on chain information and find the valid orders and remaining fillable maker asset amounts
unsortedOrders = getValidOrdersWithRemainingFillableMakerAssetAmountsFromOnChain(
filteredOrders,
ordersAndTradersInfo,
isMakerAssetZrxToken,
);
} catch (err) {
// Sometimes we observe this call to orderValidator fail with response `0x`
// Because of differences in Parity / Geth implementations, its very hard to tell if this response is a "system error"
// or a revert. In this case we just swallow these errors and fallback to partial fill information from the SRA.
// TODO(bmillman): report these errors so we have an idea of how often we're getting these failures.
}
}
// sort orders by rate
// TODO(bmillman): optimization
// provide a feeRate to the sorting function to more accurately sort based on the current market for ZRX tokens
const sortedOrders = isMakerAssetZrxToken
? sortingUtils.sortFeeOrdersByFeeAdjustedRate(unsortedOrders)
: sortingUtils.sortOrdersByFeeAdjustedRate(unsortedOrders);
// unbundle orders and fillable amounts and compile final result
const result = unbundleOrdersWithAmounts(sortedOrders);
return result;
},
};
/**
* Given an array of orders, return a new array with expired and non open orders filtered out.
*/
function filterOutExpiredAndNonOpenOrders(
orders: SignedOrderWithRemainingFillableMakerAssetAmount[],
expiryBufferSeconds: number,
): SignedOrderWithRemainingFillableMakerAssetAmount[] {
const result = _.filter(orders, order => {
return (
orderCalculationUtils.isOpenOrder(order) &&
!orderCalculationUtils.willOrderExpire(order, expiryBufferSeconds)
);
});
return result;
}
/**
* Given an array of orders and corresponding on-chain infos, return a subset of the orders
* that are still fillable orders with their corresponding remainingFillableMakerAssetAmounts.
*/
function getValidOrdersWithRemainingFillableMakerAssetAmountsFromOnChain(
inputOrders: SignedOrder[],
ordersAndTradersInfo: any[],
isMakerAssetZrxToken: boolean,
): SignedOrderWithRemainingFillableMakerAssetAmount[] {
// iterate through the input orders and find the ones that are still fillable
// for the orders that are still fillable, calculate the remaining fillable maker asset amount
const result = _.reduce(
inputOrders,
(accOrders, order, index) => {
// get corresponding on-chain state for the order
const { orderInfo, traderInfo } = ordersAndTradersInfo[index];
// if the order IS NOT fillable, do not add anything to the accumulations and continue iterating
if (orderInfo.orderStatus !== OrderStatus.Fillable) {
return accOrders;
}
// if the order IS fillable, add the order and calculate the remaining fillable amount
const transferrableAssetAmount = BigNumber.min(traderInfo.makerAllowance, traderInfo.makerBalance);
const transferrableFeeAssetAmount = BigNumber.min(traderInfo.makerZrxAllowance, traderInfo.makerZrxBalance);
const remainingTakerAssetAmount = order.takerAssetAmount.minus(orderInfo.orderTakerAssetFilledAmount);
const remainingMakerAssetAmount = orderCalculationUtils.getMakerFillAmount(
order,
remainingTakerAssetAmount,
);
const remainingFillableCalculator = new RemainingFillableCalculator(
order.makerFee,
order.makerAssetAmount,
isMakerAssetZrxToken,
transferrableAssetAmount,
transferrableFeeAssetAmount,
remainingMakerAssetAmount,
);
const remainingFillableAmount = remainingFillableCalculator.computeRemainingFillable();
// if the order does not have any remaining fillable makerAsset, do not add anything to the accumulations and continue iterating
if (remainingFillableAmount.lte(constants.ZERO_AMOUNT)) {
return accOrders;
}
const orderWithRemainingFillableMakerAssetAmount = {
...order,
remainingFillableMakerAssetAmount: remainingFillableAmount,
};
const newAccOrders = _.concat(accOrders, orderWithRemainingFillableMakerAssetAmount);
return newAccOrders;
},
[] as SignedOrderWithRemainingFillableMakerAssetAmount[],
);
return result;
}
/**
* Given an array of orders with remaining fillable maker asset amounts. Unbundle into an instance of OrdersAndRemainingFillableMakerAssetAmounts.
* If an order is missing a corresponding remainingFillableMakerAssetAmount, assume it is completely fillable.
*/
function unbundleOrdersWithAmounts(
ordersWithAmounts: SignedOrderWithRemainingFillableMakerAssetAmount[],
): OrdersAndFillableAmounts {
const result = _.reduce(
ordersWithAmounts,
(acc, orderWithAmount) => {
const { orders, remainingFillableMakerAssetAmounts } = acc;
const { remainingFillableMakerAssetAmount, ...order } = orderWithAmount;
// if we are still missing a remainingFillableMakerAssetAmount, assume the order is completely fillable
const newRemainingAmount = remainingFillableMakerAssetAmount || order.makerAssetAmount;
// if remaining amount is less than or equal to zero, do not add it
if (newRemainingAmount.lte(constants.ZERO_AMOUNT)) {
return acc;
}
const newAcc = {
orders: _.concat(orders, order),
remainingFillableMakerAssetAmounts: _.concat(remainingFillableMakerAssetAmounts, newRemainingAmount),
};
return newAcc;
},
{
orders: [] as SignedOrder[],
remainingFillableMakerAssetAmounts: [] as BigNumber[],
},
);
return result;
}

View File

@ -1,216 +0,0 @@
import { orderFactory } from '@0x/order-utils/lib/src/order_factory';
import { Web3ProviderEngine } from '@0x/subproviders';
import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import * as chai from 'chai';
import 'mocha';
import * as TypeMoq from 'typemoq';
import { AssetBuyer } from '../src';
import { constants } from '../src/constants';
import { LiquidityForAssetData, OrderProvider, OrdersAndFillableAmounts } from '../src/types';
import { chaiSetup } from './utils/chai_setup';
import {
mockAvailableAssetDatas,
mockedAssetBuyerWithOrdersAndFillableAmounts,
orderProviderMock,
} from './utils/mocks';
chaiSetup.configure();
const expect = chai.expect;
const FAKE_SRA_URL = 'https://fakeurl.com';
const FAKE_ASSET_DATA = '0xf47261b00000000000000000000000001dc4c1cefef38a777b15aa20260a54e584b16c48';
const TOKEN_DECIMALS = 18;
const DAI_ASSET_DATA = '0xf47261b000000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359"';
const WETH_ASSET_DATA = '0xf47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
const WETH_DECIMALS = constants.ETHER_TOKEN_DECIMALS;
const baseUnitAmount = (unitAmount: number, decimals = TOKEN_DECIMALS): BigNumber => {
return Web3Wrapper.toBaseUnitAmount(new BigNumber(unitAmount), decimals);
};
const expectLiquidityResult = async (
web3Provider: Web3ProviderEngine,
orderProvider: OrderProvider,
ordersAndFillableAmounts: OrdersAndFillableAmounts,
expectedLiquidityResult: LiquidityForAssetData,
) => {
const mockedAssetBuyer = mockedAssetBuyerWithOrdersAndFillableAmounts(
web3Provider,
orderProvider,
FAKE_ASSET_DATA,
ordersAndFillableAmounts,
);
const liquidityResult = await mockedAssetBuyer.object.getLiquidityForAssetDataAsync(FAKE_ASSET_DATA);
expect(liquidityResult).to.deep.equal(expectedLiquidityResult);
};
// tslint:disable:custom-no-magic-numbers
describe('AssetBuyer', () => {
describe('getLiquidityForAssetDataAsync', () => {
const mockWeb3Provider = TypeMoq.Mock.ofType(Web3ProviderEngine);
const mockOrderProvider = orderProviderMock();
beforeEach(() => {
mockWeb3Provider.reset();
mockOrderProvider.reset();
});
afterEach(() => {
mockWeb3Provider.verifyAll();
mockOrderProvider.verifyAll();
});
describe('validation', () => {
it('should ensure assetData is a string', async () => {
const assetBuyer = AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(
mockWeb3Provider.object,
FAKE_SRA_URL,
);
expect(assetBuyer.getLiquidityForAssetDataAsync(false as any)).to.be.rejectedWith(
'Expected assetData to be of type string, encountered: false',
);
});
});
describe('asset pair not supported', () => {
it('should return 0s when no asset pair not supported', async () => {
mockAvailableAssetDatas(mockOrderProvider, FAKE_ASSET_DATA, []);
const assetBuyer = new AssetBuyer(mockWeb3Provider.object, mockOrderProvider.object);
const liquidityResult = await assetBuyer.getLiquidityForAssetDataAsync(FAKE_ASSET_DATA);
expect(liquidityResult).to.deep.equal({
tokensAvailableInBaseUnits: new BigNumber(0),
ethValueAvailableInWei: new BigNumber(0),
});
});
it('should return 0s when only other asset pair supported', async () => {
mockAvailableAssetDatas(mockOrderProvider, FAKE_ASSET_DATA, [DAI_ASSET_DATA]);
const assetBuyer = new AssetBuyer(mockWeb3Provider.object, mockOrderProvider.object);
const liquidityResult = await assetBuyer.getLiquidityForAssetDataAsync(FAKE_ASSET_DATA);
expect(liquidityResult).to.deep.equal({
tokensAvailableInBaseUnits: new BigNumber(0),
ethValueAvailableInWei: new BigNumber(0),
});
});
});
// TODO (xianny): needs to be updated to new SignedOrder interface
describe('assetData is supported', () => {
const chainId = 1;
// orders
const sellTwoTokensFor1Weth: SignedOrder = orderFactory.createSignedOrderFromPartial({
makerAssetAmount: baseUnitAmount(2),
takerAssetAmount: baseUnitAmount(1, WETH_DECIMALS),
chainId,
});
const sellTenTokensFor10Weth: SignedOrder = orderFactory.createSignedOrderFromPartial({
makerAssetAmount: baseUnitAmount(10),
takerAssetAmount: baseUnitAmount(10, WETH_DECIMALS),
chainId,
});
beforeEach(() => {
mockAvailableAssetDatas(mockOrderProvider, FAKE_ASSET_DATA, [WETH_ASSET_DATA]);
});
it('should return 0s when no orders available', async () => {
const ordersAndFillableAmounts: OrdersAndFillableAmounts = {
orders: [],
remainingFillableMakerAssetAmounts: [],
};
const expectedResult = {
tokensAvailableInBaseUnits: new BigNumber(0),
ethValueAvailableInWei: new BigNumber(0),
};
await expectLiquidityResult(
mockWeb3Provider.object,
mockOrderProvider.object,
ordersAndFillableAmounts,
expectedResult,
);
});
it('should return correct computed value when orders provided with full fillableAmounts', async () => {
const orders: SignedOrder[] = [sellTwoTokensFor1Weth, sellTenTokensFor10Weth];
const ordersAndFillableAmounts = {
orders: [sellTwoTokensFor1Weth, sellTenTokensFor10Weth],
remainingFillableMakerAssetAmounts: orders.map(o => o.makerAssetAmount),
};
const expectedTokensAvailable = orders[0].makerAssetAmount.plus(orders[1].makerAssetAmount);
const expectedEthValueAvailable = orders[0].takerAssetAmount.plus(orders[1].takerAssetAmount);
const expectedResult = {
tokensAvailableInBaseUnits: expectedTokensAvailable,
ethValueAvailableInWei: expectedEthValueAvailable,
};
await expectLiquidityResult(
mockWeb3Provider.object,
mockOrderProvider.object,
ordersAndFillableAmounts,
expectedResult,
);
});
it('should return correct computed value with one partial fillableAmounts', async () => {
const ordersAndFillableAmounts = {
orders: [sellTwoTokensFor1Weth],
remainingFillableMakerAssetAmounts: [baseUnitAmount(1)],
};
const expectedResult = {
tokensAvailableInBaseUnits: baseUnitAmount(1),
ethValueAvailableInWei: baseUnitAmount(0.5, WETH_DECIMALS),
};
await expectLiquidityResult(
mockWeb3Provider.object,
mockOrderProvider.object,
ordersAndFillableAmounts,
expectedResult,
);
});
it('should return correct computed value with multiple orders and fillable amounts', async () => {
const ordersAndFillableAmounts = {
orders: [sellTwoTokensFor1Weth, sellTenTokensFor10Weth],
remainingFillableMakerAssetAmounts: [baseUnitAmount(1), baseUnitAmount(3)],
};
const expectedResult = {
tokensAvailableInBaseUnits: baseUnitAmount(4),
ethValueAvailableInWei: baseUnitAmount(3.5, WETH_DECIMALS),
};
await expectLiquidityResult(
mockWeb3Provider.object,
mockOrderProvider.object,
ordersAndFillableAmounts,
expectedResult,
);
});
it('should return 0s when no amounts fillable', async () => {
const ordersAndFillableAmounts = {
orders: [sellTwoTokensFor1Weth, sellTenTokensFor10Weth],
remainingFillableMakerAssetAmounts: [baseUnitAmount(0), baseUnitAmount(0)],
};
const expectedResult = {
tokensAvailableInBaseUnits: baseUnitAmount(0),
ethValueAvailableInWei: baseUnitAmount(0, WETH_DECIMALS),
};
await expectLiquidityResult(
mockWeb3Provider.object,
mockOrderProvider.object,
ordersAndFillableAmounts,
expectedResult,
);
});
});
});
});

View File

@ -1,303 +0,0 @@
import { orderFactory } from '@0x/order-utils/lib/src/order_factory';
import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import * as chai from 'chai';
import * as _ from 'lodash';
import 'mocha';
import { AssetBuyerError, OrdersAndFillableAmounts } from '../src/types';
import { buyQuoteCalculator } from '../src/utils/buy_quote_calculator';
import { chaiSetup } from './utils/chai_setup';
import { testHelpers } from './utils/test_helpers';
chaiSetup.configure();
const expect = chai.expect;
// tslint:disable:custom-no-magic-numbers
describe('buyQuoteCalculator', () => {
describe('#calculate', () => {
let firstOrder: SignedOrder;
let firstRemainingFillAmount: BigNumber;
let secondOrder: SignedOrder;
let secondRemainingFillAmount: BigNumber;
let ordersAndFillableAmounts: OrdersAndFillableAmounts;
let smallFeeOrderAndFillableAmount: OrdersAndFillableAmounts;
let allFeeOrdersAndFillableAmounts: OrdersAndFillableAmounts;
const chainId = 1;
beforeEach(() => {
// generate two orders for our desired maker asset
// the first order has a rate of 4 makerAsset / WETH with a takerFee of 200 ZRX and has only 200 / 400 makerAsset units left to fill (half fillable)
// the second order has a rate of 2 makerAsset / WETH with a takerFee of 100 ZRX and has 200 / 200 makerAsset units left to fill (completely fillable)
// generate one order for fees
// the fee order has a rate of 1 ZRX / WETH with no taker fee and has 100 ZRX left to fill (completely fillable)
firstOrder = orderFactory.createSignedOrderFromPartial({
makerAssetAmount: new BigNumber(400),
takerAssetAmount: new BigNumber(100),
takerFee: new BigNumber(200),
chainId,
});
firstRemainingFillAmount = new BigNumber(200);
secondOrder = orderFactory.createSignedOrderFromPartial({
makerAssetAmount: new BigNumber(200),
takerAssetAmount: new BigNumber(100),
takerFee: new BigNumber(100),
chainId,
});
secondRemainingFillAmount = secondOrder.makerAssetAmount;
ordersAndFillableAmounts = {
orders: [firstOrder, secondOrder],
remainingFillableMakerAssetAmounts: [firstRemainingFillAmount, secondRemainingFillAmount],
};
const smallFeeOrder = orderFactory.createSignedOrderFromPartial({
makerAssetAmount: new BigNumber(100),
takerAssetAmount: new BigNumber(100),
chainId,
});
smallFeeOrderAndFillableAmount = {
orders: [smallFeeOrder],
remainingFillableMakerAssetAmounts: [smallFeeOrder.makerAssetAmount],
};
const largeFeeOrder = orderFactory.createSignedOrderFromPartial({
makerAssetAmount: new BigNumber(113),
takerAssetAmount: new BigNumber(200),
takerFee: new BigNumber(11),
chainId,
});
allFeeOrdersAndFillableAmounts = {
orders: [smallFeeOrder, largeFeeOrder],
remainingFillableMakerAssetAmounts: [
smallFeeOrder.makerAssetAmount,
largeFeeOrder.makerAssetAmount.minus(largeFeeOrder.takerFee),
],
};
});
describe('InsufficientLiquidityError', () => {
it('should throw if not enough maker asset liquidity (multiple orders)', () => {
// we have 400 makerAsset units available to fill but attempt to calculate a quote for 500 makerAsset units
const errorFunction = () => {
buyQuoteCalculator.calculate(
ordersAndFillableAmounts,
smallFeeOrderAndFillableAmount,
new BigNumber(500),
0,
0,
false,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(400));
});
it('should throw if not enough maker asset liquidity (multiple orders with 20% slippage)', () => {
// we have 400 makerAsset units available to fill but attempt to calculate a quote for 500 makerAsset units
const errorFunction = () => {
buyQuoteCalculator.calculate(
ordersAndFillableAmounts,
smallFeeOrderAndFillableAmount,
new BigNumber(500),
0,
0.2,
false,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(333));
});
it('should throw if not enough maker asset liquidity (multiple orders with 5% slippage)', () => {
// we have 400 makerAsset units available to fill but attempt to calculate a quote for 500 makerAsset units
const errorFunction = () => {
buyQuoteCalculator.calculate(
ordersAndFillableAmounts,
smallFeeOrderAndFillableAmount,
new BigNumber(600),
0,
0.05,
false,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(380));
});
it('should throw if not enough maker asset liquidity (partially filled order)', () => {
const firstOrderAndFillableAmount: OrdersAndFillableAmounts = {
orders: [firstOrder],
remainingFillableMakerAssetAmounts: [firstRemainingFillAmount],
};
const errorFunction = () => {
buyQuoteCalculator.calculate(
firstOrderAndFillableAmount,
smallFeeOrderAndFillableAmount,
new BigNumber(201),
0,
0,
false,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(200));
});
it('should throw if not enough maker asset liquidity (completely fillable order)', () => {
const completelyFillableOrder = orderFactory.createSignedOrderFromPartial({
makerAssetAmount: new BigNumber(123),
takerAssetAmount: new BigNumber(100),
takerFee: new BigNumber(200),
chainId,
});
const completelyFillableOrdersAndFillableAmount: OrdersAndFillableAmounts = {
orders: [completelyFillableOrder],
remainingFillableMakerAssetAmounts: [completelyFillableOrder.makerAssetAmount],
};
const errorFunction = () => {
buyQuoteCalculator.calculate(
completelyFillableOrdersAndFillableAmount,
smallFeeOrderAndFillableAmount,
new BigNumber(124),
0,
0,
false,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(123));
});
it('should throw with 1 amount available if no slippage', () => {
const smallOrder = orderFactory.createSignedOrderFromPartial({
makerAssetAmount: new BigNumber(1),
takerAssetAmount: new BigNumber(1),
takerFee: new BigNumber(0),
chainId,
});
const errorFunction = () => {
buyQuoteCalculator.calculate(
{ orders: [smallOrder], remainingFillableMakerAssetAmounts: [smallOrder.makerAssetAmount] },
smallFeeOrderAndFillableAmount,
new BigNumber(600),
0,
0,
false,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(1));
});
it('should throw with 0 available to fill if amount rounds to 0', () => {
const smallOrder = orderFactory.createSignedOrderFromPartial({
makerAssetAmount: new BigNumber(1),
takerAssetAmount: new BigNumber(1),
takerFee: new BigNumber(0),
chainId,
});
const errorFunction = () => {
buyQuoteCalculator.calculate(
{ orders: [smallOrder], remainingFillableMakerAssetAmounts: [smallOrder.makerAssetAmount] },
smallFeeOrderAndFillableAmount,
new BigNumber(600),
0,
0.2,
false,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, new BigNumber(0));
});
});
it('should not throw if order is fillable', () => {
expect(() =>
buyQuoteCalculator.calculate(
ordersAndFillableAmounts,
allFeeOrdersAndFillableAmounts,
new BigNumber(300),
0,
0,
false,
),
).to.not.throw();
});
it('should throw if not enough ZRX liquidity', () => {
// we request 300 makerAsset units but the ZRX order is only enough to fill the first order, which only has 200 makerAssetUnits available
expect(() =>
buyQuoteCalculator.calculate(
ordersAndFillableAmounts,
smallFeeOrderAndFillableAmount,
new BigNumber(300),
0,
0,
false,
),
).to.throw(AssetBuyerError.InsufficientZrxLiquidity);
});
it('calculates a correct buyQuote with no slippage', () => {
// we request 200 makerAsset units which can be filled using the first order
// the first order requires a fee of 100 ZRX from the taker which can be filled by the feeOrder
const assetBuyAmount = new BigNumber(200);
const feePercentage = 0.02;
const slippagePercentage = 0;
const buyQuote = buyQuoteCalculator.calculate(
ordersAndFillableAmounts,
smallFeeOrderAndFillableAmount,
assetBuyAmount,
feePercentage,
slippagePercentage,
false,
);
// test if orders are correct
expect(buyQuote.orders).to.deep.equal([ordersAndFillableAmounts.orders[0]]);
expect(buyQuote.feeOrders).to.deep.equal([smallFeeOrderAndFillableAmount.orders[0]]);
// test if rates are correct
// 50 eth to fill the first order + 100 eth for fees
const expectedEthAmountForAsset = new BigNumber(50);
const expectedEthAmountForZrxFees = new BigNumber(100);
const expectedFillEthAmount = expectedEthAmountForAsset;
const expectedAffiliateFeeEthAmount = expectedEthAmountForAsset.multipliedBy(feePercentage);
const expectedFeeEthAmount = expectedAffiliateFeeEthAmount.plus(expectedEthAmountForZrxFees);
const expectedTotalEthAmount = expectedFillEthAmount.plus(expectedFeeEthAmount);
expect(buyQuote.bestCaseQuoteInfo.assetEthAmount).to.bignumber.equal(expectedFillEthAmount);
expect(buyQuote.bestCaseQuoteInfo.feeEthAmount).to.bignumber.equal(expectedFeeEthAmount);
expect(buyQuote.bestCaseQuoteInfo.totalEthAmount).to.bignumber.equal(expectedTotalEthAmount);
// because we have no slippage protection, minRate is equal to maxRate
expect(buyQuote.worstCaseQuoteInfo.assetEthAmount).to.bignumber.equal(expectedFillEthAmount);
expect(buyQuote.worstCaseQuoteInfo.feeEthAmount).to.bignumber.equal(expectedFeeEthAmount);
expect(buyQuote.worstCaseQuoteInfo.totalEthAmount).to.bignumber.equal(expectedTotalEthAmount);
// test if feePercentage gets passed through
expect(buyQuote.feePercentage).to.equal(feePercentage);
});
it('calculates a correct buyQuote with with slippage', () => {
// we request 200 makerAsset units which can be filled using the first order
// however with 50% slippage we are protecting the buy with 100 extra makerAssetUnits
// so we need enough orders to fill 300 makerAssetUnits
// 300 makerAssetUnits can only be filled using both orders
// the first order requires a fee of 100 ZRX from the taker which can be filled by the feeOrder
const assetBuyAmount = new BigNumber(200);
const feePercentage = 0.02;
const slippagePercentage = 0.5;
const buyQuote = buyQuoteCalculator.calculate(
ordersAndFillableAmounts,
allFeeOrdersAndFillableAmounts,
assetBuyAmount,
feePercentage,
slippagePercentage,
false,
);
// test if orders are correct
expect(buyQuote.orders).to.deep.equal(ordersAndFillableAmounts.orders);
expect(buyQuote.feeOrders).to.deep.equal(allFeeOrdersAndFillableAmounts.orders);
// test if rates are correct
// 50 eth to fill the first order + 100 eth for fees
const expectedEthAmountForAsset = new BigNumber(50);
const expectedEthAmountForZrxFees = new BigNumber(100);
const expectedFillEthAmount = expectedEthAmountForAsset;
const expectedAffiliateFeeEthAmount = expectedEthAmountForAsset.multipliedBy(feePercentage);
const expectedFeeEthAmount = expectedAffiliateFeeEthAmount.plus(expectedEthAmountForZrxFees);
const expectedTotalEthAmount = expectedFillEthAmount.plus(expectedFeeEthAmount);
expect(buyQuote.bestCaseQuoteInfo.assetEthAmount).to.bignumber.equal(expectedFillEthAmount);
expect(buyQuote.bestCaseQuoteInfo.feeEthAmount).to.bignumber.equal(expectedFeeEthAmount);
expect(buyQuote.bestCaseQuoteInfo.totalEthAmount).to.bignumber.equal(expectedTotalEthAmount);
// 100 eth to fill the first order + 208 eth for fees
const expectedWorstEthAmountForAsset = new BigNumber(100);
const expectedWorstEthAmountForZrxFees = new BigNumber(208);
const expectedWorstFillEthAmount = expectedWorstEthAmountForAsset;
const expectedWorstAffiliateFeeEthAmount = expectedWorstEthAmountForAsset.multipliedBy(feePercentage);
const expectedWorstFeeEthAmount = expectedWorstAffiliateFeeEthAmount.plus(expectedWorstEthAmountForZrxFees);
const expectedWorstTotalEthAmount = expectedWorstFillEthAmount.plus(expectedWorstFeeEthAmount);
expect(buyQuote.worstCaseQuoteInfo.assetEthAmount).to.bignumber.equal(expectedWorstFillEthAmount);
expect(buyQuote.worstCaseQuoteInfo.feeEthAmount).to.bignumber.equal(expectedWorstFeeEthAmount);
expect(buyQuote.worstCaseQuoteInfo.totalEthAmount).to.bignumber.equal(expectedWorstTotalEthAmount);
// test if feePercentage gets passed through
expect(buyQuote.feePercentage).to.equal(feePercentage);
});
});
});

View File

@ -1,13 +0,0 @@
import * as chai from 'chai';
import chaiAsPromised = require('chai-as-promised');
import ChaiBigNumber = require('chai-bignumber');
import * as dirtyChai from 'dirty-chai';
export const chaiSetup = {
configure(): void {
chai.config.includeStack = true;
chai.use(ChaiBigNumber());
chai.use(dirtyChai);
chai.use(chaiAsPromised);
},
};

View File

@ -1,68 +0,0 @@
import { Web3ProviderEngine } from '@0x/subproviders';
import * as TypeMoq from 'typemoq';
import { AssetBuyer } from '../../src/asset_buyer';
import { OrderProvider, OrderProviderResponse, OrdersAndFillableAmounts } from '../../src/types';
// tslint:disable:promise-function-async
// Implementing dummy class for using in mocks, see https://github.com/florinn/typemoq/issues/3
class OrderProviderClass implements OrderProvider {
// tslint:disable-next-line:prefer-function-over-method
public async getOrdersAsync(): Promise<OrderProviderResponse> {
return Promise.resolve({ orders: [] });
}
// tslint:disable-next-line:prefer-function-over-method
public async getAvailableMakerAssetDatasAsync(takerAssetData: string): Promise<string[]> {
return Promise.resolve([]);
}
}
export const orderProviderMock = () => {
return TypeMoq.Mock.ofType(OrderProviderClass, TypeMoq.MockBehavior.Strict);
};
export const mockAvailableAssetDatas = (
mockOrderProvider: TypeMoq.IMock<OrderProviderClass>,
assetData: string,
availableAssetDatas: string[],
) => {
mockOrderProvider
.setup(op => op.getAvailableMakerAssetDatasAsync(TypeMoq.It.isValue(assetData)))
.returns(() => {
return Promise.resolve(availableAssetDatas);
})
.verifiable(TypeMoq.Times.once());
};
const partiallyMockedAssetBuyer = (
provider: Web3ProviderEngine,
orderProvider: OrderProvider,
): TypeMoq.IMock<AssetBuyer> => {
const rawAssetBuyer = new AssetBuyer(provider, orderProvider);
const mockedAssetBuyer = TypeMoq.Mock.ofInstance(rawAssetBuyer, TypeMoq.MockBehavior.Loose, false);
mockedAssetBuyer.callBase = true;
return mockedAssetBuyer;
};
const mockGetOrdersAndAvailableAmounts = (
mockedAssetBuyer: TypeMoq.IMock<AssetBuyer>,
assetData: string,
ordersAndFillableAmounts: OrdersAndFillableAmounts,
): void => {
mockedAssetBuyer
.setup(a => a.getOrdersAndFillableAmountsAsync(assetData, false))
.returns(() => Promise.resolve(ordersAndFillableAmounts))
.verifiable(TypeMoq.Times.once());
};
export const mockedAssetBuyerWithOrdersAndFillableAmounts = (
provider: Web3ProviderEngine,
orderProvider: OrderProvider,
assetData: string,
ordersAndFillableAmounts: OrdersAndFillableAmounts,
): TypeMoq.IMock<AssetBuyer> => {
const mockedAssetBuyer = partiallyMockedAssetBuyer(provider, orderProvider);
mockGetOrdersAndAvailableAmounts(mockedAssetBuyer, assetData, ordersAndFillableAmounts);
return mockedAssetBuyer;
};

View File

@ -1,26 +0,0 @@
import { BigNumber } from '@0x/utils';
import { InsufficientAssetLiquidityError } from '../../src/errors';
export const testHelpers = {
expectInsufficientLiquidityError: (
expect: Chai.ExpectStatic,
functionWhichTriggersError: () => void,
expectedAmountAvailableToFill: BigNumber,
): void => {
let wasErrorThrown = false;
try {
functionWhichTriggersError();
} catch (e) {
wasErrorThrown = true;
expect(e).to.be.instanceOf(InsufficientAssetLiquidityError);
if (expectedAmountAvailableToFill) {
expect(e.amountAvailableToFill).to.be.bignumber.equal(expectedAmountAvailableToFill);
} else {
expect(e.amountAvailableToFill).to.be.undefined();
}
}
expect(wasErrorThrown).to.be.true();
},
};

View File

@ -1,8 +0,0 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"outDir": "lib",
"rootDir": "."
},
"include": ["./src/**/*", "./test/**/*"]
}

View File

@ -1,3 +0,0 @@
{
"extends": ["@0x/tslint-config"]
}

View File

@ -1,7 +0,0 @@
{
"extends": "../../typedoc-tsconfig",
"compilerOptions": {
"outDir": "lib"
},
"include": ["./src/**/*", "./test/**/*"]
}

View File

@ -3366,4 +3366,3 @@ ___
## Type aliases

View File

@ -42,24 +42,21 @@
"dependencies": {
"@0x/assert": "^2.2.0-beta.2",
"@0x/contract-addresses": "^3.3.0-beta.4",
"@0x/contracts-dev-utils": "^0.1.0-beta.3",
"@0x/contracts-erc20": "^2.3.0-beta.3",
"@0x/contracts-exchange": "^2.2.0-beta.3",
"@0x/contracts-exchange-forwarder": "^3.1.0-beta.3",
"@0x/abi-gen-wrappers": "^5.4.0-beta.3",
"@0x/json-schemas": "^4.1.0-beta.2",
"@0x/order-utils": "^8.5.0-beta.3",
"@0x/orderbook": "^0.1.0-beta.3",
"@0x/types": "^2.5.0-beta.2",
"@0x/utils": "^4.6.0-beta.2",
"@0x/web3-wrapper": "^6.1.0-beta.2",
"ethereum-types": "^2.2.0-beta.2",
"lodash": "^4.17.11"
},
"devDependencies": {
"ethereum-types": "^2.2.0-beta.2",
"@0x/types": "^2.5.0-beta.2",
"@0x/migrations": "^4.4.0-beta.3",
"@0x/contracts-test-utils": "^3.2.0-beta.3",
"@0x/dev-utils": "^2.4.0-beta.3",
"@0x/mesh-rpc-client": "^7.0.4-beta-0xv3",
"@0x/migrations": "^4.4.0-beta.3",
"@0x/subproviders": "^5.1.0-beta.2",
"@0x/ts-doc-gen": "^0.0.22",
"@0x/tslint-config": "^3.1.0-beta.2",

View File

@ -1,6 +1,7 @@
import { BigNumber } from '@0x/utils';
import {
ExtensionContractType,
ForwarderExtensionContractOpts,
OrderPrunerOpts,
OrderPrunerPermittedFeeTypes,
@ -17,7 +18,6 @@ const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
const MAINNET_CHAIN_ID = 1;
const ONE_SECOND_MS = 1000;
const DEFAULT_PER_PAGE = 1000;
const PROTOCOL_FEE_MULTIPLIER = 150000;
const DEFAULT_ORDER_PRUNER_OPTS: OrderPrunerOpts = {
expiryBufferMs: 120000, // 2 minutes
@ -35,13 +35,17 @@ const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
...DEFAULT_ORDER_PRUNER_OPTS,
};
const DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS: SwapQuoteGetOutputOpts & ForwarderExtensionContractOpts = {
const DEFAULT_FORWARDER_EXTENSION_CONTRACT_OPTS: ForwarderExtensionContractOpts = {
feePercentage: 0,
feeRecipient: NULL_ADDRESS,
};
const DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS: SwapQuoteExecutionOpts &
ForwarderExtensionContractOpts = DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS;
const DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS: SwapQuoteGetOutputOpts = {
useExtensionContract: ExtensionContractType.Forwarder,
extensionContractOpts: DEFAULT_FORWARDER_EXTENSION_CONTRACT_OPTS,
};
const DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS: SwapQuoteExecutionOpts = DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS;
const DEFAULT_SWAP_QUOTE_REQUEST_OPTS: SwapQuoteRequestOpts = {
slippagePercentage: 0.2, // 20% slippage protection,
@ -63,6 +67,5 @@ export const constants = {
DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS,
DEFAULT_SWAP_QUOTE_REQUEST_OPTS,
DEFAULT_PER_PAGE,
PROTOCOL_FEE_MULTIPLIER,
NULL_ERC20_ASSET_DATA,
};

View File

@ -1,66 +1,62 @@
export { WSOpts } from '@0x/mesh-rpc-client';
export {
JSONRPCRequestPayload,
JSONRPCResponsePayload,
JSONRPCResponseError,
JSONRPCErrorCallback,
SupportedProvider,
Web3JsProvider,
GanacheProvider,
EIP1193Provider,
ZeroExProvider,
AcceptedRejectedOrders,
AddedRemovedOrders,
BaseOrderProvider,
MeshOrderProviderOpts,
Orderbook,
OrderSet,
OrderStore,
RejectedOrder,
SRAPollingOrderProviderOpts,
SRAWebsocketOrderProviderOpts,
} from '@0x/orderbook';
export { APIOrder, Asset, AssetPairsItem, Order, SignedOrder } from '@0x/types';
export { BigNumber } from '@0x/utils';
export {
ConstructorStateMutability,
DataItem,
EIP1193Event,
EIP1193Provider,
EventParameter,
GanacheProvider,
JSONRPCErrorCallback,
JSONRPCRequestPayload,
JSONRPCResponseError,
JSONRPCResponsePayload,
MethodAbi,
StateMutability,
SupportedProvider,
TupleDataItem,
Web3JsProvider,
Web3JsV1Provider,
Web3JsV2Provider,
Web3JsV3Provider,
MethodAbi,
DataItem,
StateMutability,
EventParameter,
TupleDataItem,
ConstructorStateMutability,
ZeroExProvider,
} from 'ethereum-types';
export { SignedOrder, AssetPairsItem, APIOrder, Asset, Order } from '@0x/types';
export { BigNumber } from '@0x/utils';
export { InsufficientAssetLiquidityError } from './errors';
export { SwapQuoteConsumer } from './quote_consumers/swap_quote_consumer';
export { SwapQuoter } from './swap_quoter';
export { protocolFeeUtils } from './utils/protocol_fee_utils';
export { affiliateFeeUtils } from './utils/affiliate_fee_utils';
export { InsufficientAssetLiquidityError } from './errors';
export {
SwapQuoterError,
SwapQuoterOpts,
SwapQuote,
SwapQuoteConsumerOpts,
CalldataInfo,
ExtensionContractType,
SwapQuoteConsumingOpts,
LiquidityForTakerMakerAssetDataPair,
SwapQuoteGetOutputOpts,
PrunedSignedOrder,
SwapQuoteExecutionOpts,
SwapQuoteInfo,
ForwarderExtensionContractOpts,
GetExtensionContractTypeOpts,
SmartContractParamsInfo,
LiquidityForTakerMakerAssetDataPair,
MarketBuySwapQuote,
MarketSellSwapQuote,
PrunedSignedOrder,
SmartContractParamsInfo,
SwapQuote,
SwapQuoteConsumerBase,
SwapQuoteConsumerOpts,
SwapQuoteExecutionOpts,
SwapQuoteGetOutputOpts,
SwapQuoteInfo,
SwapQuoteRequestOpts,
SwapQuoterError,
SwapQuoterOpts,
SwapQuoteConsumerError,
} from './types';
export {
Orderbook,
MeshOrderProviderOpts,
SRAPollingOrderProviderOpts,
SRAWebsocketOrderProviderOpts,
BaseOrderProvider,
OrderStore,
AcceptedRejectedOrders,
RejectedOrder,
AddedRemovedOrders,
OrderSet,
} from '@0x/orderbook';
export { WSOpts } from '@0x/mesh-rpc-client';
export { affiliateFeeUtils } from './utils/affiliate_fee_utils';
export { ProtocolFeeUtils } from './utils/protocol_fee_utils';

View File

@ -1,5 +1,5 @@
import { ExchangeContract } from '@0x/abi-gen-wrappers';
import { ContractAddresses } from '@0x/contract-addresses';
import { ExchangeContract } from '@0x/contracts-exchange';
import { AbiEncoder, providerUtils } from '@0x/utils';
import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper';
import { MethodAbi } from 'ethereum-types';
@ -111,7 +111,7 @@ export class ExchangeSwapQuoteConsumer implements SwapQuoteConsumerBase<Exchange
params,
toAddress: this._exchangeContract.address,
methodAbi,
ethAmount: quote.worstCaseQuoteInfo.protocolFeeInEthAmount,
ethAmount: quote.worstCaseQuoteInfo.protocolFeeInWeiAmount,
};
}
@ -121,7 +121,7 @@ export class ExchangeSwapQuoteConsumer implements SwapQuoteConsumerBase<Exchange
): Promise<string> {
assert.isValidSwapQuote('quote', quote);
const { takerAddress, gasLimit, gasPrice, ethAmount } = opts;
const { takerAddress, gasLimit, ethAmount } = opts;
if (takerAddress !== undefined) {
assert.isETHAddressHex('takerAddress', takerAddress);
@ -129,16 +129,13 @@ export class ExchangeSwapQuoteConsumer implements SwapQuoteConsumerBase<Exchange
if (gasLimit !== undefined) {
assert.isNumber('gasLimit', gasLimit);
}
if (gasPrice !== undefined) {
assert.isBigNumber('gasPrice', gasPrice);
}
if (ethAmount !== undefined) {
assert.isBigNumber('ethAmount', ethAmount);
}
const { orders } = quote;
const { orders, gasPrice } = quote;
const finalTakerAddress = await swapQuoteConsumerUtils.getTakerAddressOrThrowAsync(this.provider, opts);
const value = ethAmount || quote.worstCaseQuoteInfo.protocolFeeInEthAmount;
const value = ethAmount || quote.worstCaseQuoteInfo.protocolFeeInWeiAmount;
let txHash: string;
if (quote.type === MarketOperation.Buy) {
const { makerAssetFillAmount } = quote;

View File

@ -1,6 +1,5 @@
import { DevUtilsContract, ForwarderContract } from '@0x/abi-gen-wrappers';
import { ContractAddresses } from '@0x/contract-addresses';
import { DevUtilsContract } from '@0x/contracts-dev-utils';
import { ForwarderContract } from '@0x/contracts-exchange-forwarder';
import { AbiEncoder, providerUtils } from '@0x/utils';
import { SupportedProvider, ZeroExProvider } from '@0x/web3-wrapper';
import { MethodAbi } from 'ethereum-types';
@ -9,7 +8,6 @@ import * as _ from 'lodash';
import { constants } from '../constants';
import {
CalldataInfo,
ForwarderExtensionContractOpts,
ForwarderSmartContractParams,
MarketOperation,
SmartContractParamsInfo,
@ -54,7 +52,7 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumerBase<Forward
*/
public async getCalldataOrThrowAsync(
quote: SwapQuote,
opts: Partial<SwapQuoteGetOutputOpts & ForwarderExtensionContractOpts> = {},
opts: Partial<SwapQuoteGetOutputOpts> = {},
): Promise<CalldataInfo> {
assert.isValidForwarderSwapQuote('quote', quote, await this._getEtherTokenAssetDataOrThrowAsync());
@ -86,21 +84,15 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumerBase<Forward
*/
public async getSmartContractParamsOrThrowAsync(
quote: SwapQuote,
opts: Partial<SwapQuoteGetOutputOpts & ForwarderExtensionContractOpts> = {},
opts: Partial<SwapQuoteGetOutputOpts> = {},
): Promise<SmartContractParamsInfo<ForwarderSmartContractParams>> {
assert.isValidForwarderSwapQuote('quote', quote, await this._getEtherTokenAssetDataOrThrowAsync());
const { ethAmount: providedEthAmount, feeRecipient, feePercentage } = _.merge(
{},
constants.DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS,
opts,
);
const { extensionContractOpts } = _.merge({}, constants.DEFAULT_FORWARDER_SWAP_QUOTE_GET_OPTS, opts);
assert.isValidPercentage('feePercentage', feePercentage);
assert.isETHAddressHex('feeRecipient', feeRecipient);
if (providedEthAmount !== undefined) {
assert.isBigNumber('ethAmount', providedEthAmount);
}
assert.isValidForwarderExtensionContractOpts('extensionContractOpts', extensionContractOpts);
const { feeRecipient, feePercentage } = extensionContractOpts;
const { orders, worstCaseQuoteInfo } = quote;
@ -146,7 +138,7 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumerBase<Forward
return {
params,
toAddress: this._forwarder.address,
ethAmount: providedEthAmount || ethAmountWithFees,
ethAmount: ethAmountWithFees,
methodAbi,
};
}
@ -158,18 +150,20 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumerBase<Forward
*/
public async executeSwapQuoteOrThrowAsync(
quote: SwapQuote,
opts: Partial<SwapQuoteExecutionOpts & ForwarderExtensionContractOpts>,
opts: Partial<SwapQuoteExecutionOpts>,
): Promise<string> {
assert.isValidForwarderSwapQuote('quote', quote, await this._getEtherTokenAssetDataOrThrowAsync());
const { ethAmount: providedEthAmount, takerAddress, gasLimit, gasPrice, feeRecipient, feePercentage } = _.merge(
const { ethAmount: providedEthAmount, takerAddress, gasLimit, extensionContractOpts } = _.merge(
{},
constants.DEFAULT_FORWARDER_SWAP_QUOTE_EXECUTE_OPTS,
opts,
);
assert.isValidPercentage('feePercentage', feePercentage);
assert.isETHAddressHex('feeRecipient', feeRecipient);
assert.isValidForwarderExtensionContractOpts('extensionContractOpts', extensionContractOpts);
const { feeRecipient, feePercentage } = extensionContractOpts;
if (providedEthAmount !== undefined) {
assert.isBigNumber('ethAmount', providedEthAmount);
}
@ -179,11 +173,7 @@ export class ForwarderSwapQuoteConsumer implements SwapQuoteConsumerBase<Forward
if (gasLimit !== undefined) {
assert.isNumber('gasLimit', gasLimit);
}
if (gasPrice !== undefined) {
assert.isBigNumber('gasPrice', gasPrice);
}
const { orders, worstCaseQuoteInfo } = quote; // tslint:disable-line:no-unused-variable
const { orders, worstCaseQuoteInfo, gasPrice } = quote; // tslint:disable-line:no-unused-variable
// get taker address
const finalTakerAddress = await swapQuoteConsumerUtils.getTakerAddressOrThrowAsync(this.provider, opts);

View File

@ -13,7 +13,6 @@ import {
SwapQuote,
SwapQuoteConsumerBase,
SwapQuoteConsumerOpts,
SwapQuoteConsumingOpts,
SwapQuoteExecutionOpts,
SwapQuoteGetOutputOpts,
} from '../types';
@ -57,7 +56,7 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase<SmartContractPar
*/
public async getCalldataOrThrowAsync(
quote: SwapQuote,
opts: Partial<SwapQuoteGetOutputOpts & SwapQuoteConsumingOpts> = {},
opts: Partial<SwapQuoteGetOutputOpts> = {},
): Promise<CalldataInfo> {
assert.isValidSwapQuote('quote', quote);
const consumer = await this._getConsumerForSwapQuoteAsync(opts);
@ -71,7 +70,7 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase<SmartContractPar
*/
public async getSmartContractParamsOrThrowAsync(
quote: SwapQuote,
opts: Partial<SwapQuoteGetOutputOpts & SwapQuoteConsumingOpts> = {},
opts: Partial<SwapQuoteGetOutputOpts> = {},
): Promise<SmartContractParamsInfo<SmartContractParams>> {
assert.isValidSwapQuote('quote', quote);
const consumer = await this._getConsumerForSwapQuoteAsync(opts);
@ -85,7 +84,7 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase<SmartContractPar
*/
public async executeSwapQuoteOrThrowAsync(
quote: SwapQuote,
opts: Partial<SwapQuoteExecutionOpts & SwapQuoteConsumingOpts> = {},
opts: Partial<SwapQuoteExecutionOpts> = {},
): Promise<string> {
assert.isValidSwapQuote('quote', quote);
const consumer = await this._getConsumerForSwapQuoteAsync(opts);
@ -110,7 +109,7 @@ export class SwapQuoteConsumer implements SwapQuoteConsumerBase<SmartContractPar
}
private async _getConsumerForSwapQuoteAsync(
opts: Partial<SwapQuoteConsumingOpts>,
opts: Partial<SwapQuoteGetOutputOpts>,
): Promise<SwapQuoteConsumerBase<SmartContractParams>> {
if (opts.useExtensionContract === ExtensionContractType.Forwarder) {
return this._forwarderConsumer;

View File

@ -1,5 +1,5 @@
import { DevUtilsContract } from '@0x/abi-gen-wrappers';
import { ContractAddresses, getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
import { DevUtilsContract } from '@0x/contracts-dev-utils';
import { schemas } from '@0x/json-schemas';
import { SignedOrder } from '@0x/order-utils';
import { MeshOrderProviderOpts, Orderbook, SRAPollingOrderProviderOpts } from '@0x/orderbook';
@ -23,7 +23,7 @@ import {
import { assert } from './utils/assert';
import { calculateLiquidity } from './utils/calculate_liquidity';
import { OrderPruner } from './utils/order_prune_utils';
import { protocolFeeUtils } from './utils/protocol_fee_utils';
import { ProtocolFeeUtils } from './utils/protocol_fee_utils';
import { sortingUtils } from './utils/sorting_utils';
import { swapQuoteCalculator } from './utils/swap_quote_calculator';
@ -31,8 +31,10 @@ export class SwapQuoter {
public readonly provider: ZeroExProvider;
public readonly orderbook: Orderbook;
public readonly expiryBufferMs: number;
public readonly chainId: number;
public readonly permittedOrderFeeTypes: Set<OrderPrunerPermittedFeeTypes>;
private readonly _contractAddresses: ContractAddresses;
private readonly _protocolFeeUtils: ProtocolFeeUtils;
private readonly _orderPruner: OrderPruner;
private readonly _devUtilsContract: DevUtilsContract;
/**
@ -146,12 +148,14 @@ export class SwapQuoter {
assert.isValidOrderbook('orderbook', orderbook);
assert.isNumber('chainId', chainId);
assert.isNumber('expiryBufferMs', expiryBufferMs);
this.chainId = chainId;
this.provider = provider;
this.orderbook = orderbook;
this.expiryBufferMs = expiryBufferMs;
this.permittedOrderFeeTypes = permittedOrderFeeTypes;
this._contractAddresses = getContractAddressesForChainOrThrow(chainId);
this._devUtilsContract = new DevUtilsContract(this._contractAddresses.devUtils, provider);
this._protocolFeeUtils = new ProtocolFeeUtils();
this._orderPruner = new OrderPruner(this._devUtilsContract, {
expiryBufferMs: this.expiryBufferMs,
permittedOrderFeeTypes: this.permittedOrderFeeTypes,
@ -224,7 +228,7 @@ export class SwapQuoter {
takerTokenAddress: string,
makerAssetBuyAmount: BigNumber,
options: Partial<SwapQuoteRequestOpts> = {},
): Promise<SwapQuote> {
): Promise<MarketBuySwapQuote> {
assert.isETHAddressHex('makerTokenAddress', makerTokenAddress);
assert.isETHAddressHex('takerTokenAddress', takerTokenAddress);
assert.isBigNumber('makerAssetBuyAmount', makerAssetBuyAmount);
@ -254,7 +258,7 @@ export class SwapQuoter {
takerTokenAddress: string,
takerAssetSellAmount: BigNumber,
options: Partial<SwapQuoteRequestOpts> = {},
): Promise<SwapQuote> {
): Promise<MarketSellSwapQuote> {
assert.isETHAddressHex('makerTokenAddress', makerTokenAddress);
assert.isETHAddressHex('takerTokenAddress', takerTokenAddress);
assert.isBigNumber('takerAssetSellAmount', takerAssetSellAmount);
@ -340,8 +344,8 @@ export class SwapQuoter {
): Promise<boolean> {
assert.isString('makerAssetData', makerAssetData);
assert.isString('takerAssetData', takerAssetData);
await this._devUtilsContract.revertIfInvalidAssetData(makerAssetData).callAsync();
await this._devUtilsContract.revertIfInvalidAssetData(takerAssetData).callAsync();
await this._devUtilsContract.revertIfInvalidAssetData(makerAssetData).callAsync();
const availableMakerAssetDatas = await this.getAvailableMakerAssetDatasAsync(takerAssetData);
return _.includes(availableMakerAssetDatas, makerAssetData);
}
@ -418,7 +422,7 @@ export class SwapQuoter {
gasPrice = options.gasPrice;
assert.isBigNumber('gasPrice', gasPrice);
} else {
gasPrice = await protocolFeeUtils.getGasPriceEstimationOrThrowAsync();
gasPrice = await this._protocolFeeUtils.getGasPriceEstimationOrThrowAsync();
}
// get the relevant orders for the makerAsset
const prunedOrders = await this.getPrunedSignedOrdersAsync(makerAssetData, takerAssetData);
@ -433,18 +437,20 @@ export class SwapQuoter {
let swapQuote: SwapQuote;
if (marketOperation === MarketOperation.Buy) {
swapQuote = swapQuoteCalculator.calculateMarketBuySwapQuote(
swapQuote = await swapQuoteCalculator.calculateMarketBuySwapQuoteAsync(
prunedOrders,
assetFillAmount,
slippagePercentage,
gasPrice,
this._protocolFeeUtils,
);
} else {
swapQuote = swapQuoteCalculator.calculateMarketSellSwapQuote(
swapQuote = await swapQuoteCalculator.calculateMarketSellSwapQuoteAsync(
prunedOrders,
assetFillAmount,
slippagePercentage,
gasPrice,
this._protocolFeeUtils,
);
}

View File

@ -167,19 +167,20 @@ export interface SwapQuoteConsumerOpts {
/**
* Represents the options provided to a generic SwapQuoteConsumer
*/
export interface SwapQuoteGetOutputOpts {}
export interface SwapQuoteGetOutputOpts {
useExtensionContract: ExtensionContractType;
extensionContractOpts?: ForwarderExtensionContractOpts | any;
}
/**
* ethAmount: The amount of eth sent with the execution of a swap.
* takerAddress: The address to perform the buy. Defaults to the first available address from the provider.
* gasLimit: The amount of gas to send with a transaction (in Gwei). Defaults to an eth_estimateGas rpc call.
* gasPrice: Gas price in Wei to use for a transaction
* ethAmount: The amount of eth sent with the execution of a swap
*/
export interface SwapQuoteExecutionOpts extends SwapQuoteGetOutputOpts {
ethAmount?: BigNumber;
takerAddress?: string;
gasLimit?: number;
gasPrice?: BigNumber;
ethAmount?: BigNumber;
}
/**
@ -188,18 +189,10 @@ export interface SwapQuoteExecutionOpts extends SwapQuoteGetOutputOpts {
* feeRecipient: address of the receiver of the feePercentage of taker asset
*/
export interface ForwarderExtensionContractOpts {
ethAmount?: BigNumber;
feePercentage: number;
feeRecipient: string;
}
/*
* Options for how SwapQuoteConsumer will generate output
*/
export interface SwapQuoteConsumingOpts {
useExtensionContract: ExtensionContractType;
}
export type SwapQuote = MarketBuySwapQuote | MarketSellSwapQuote;
export interface GetExtensionContractTypeOpts {
@ -210,6 +203,7 @@ export interface GetExtensionContractTypeOpts {
/**
* takerAssetData: String that represents a specific taker asset (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
* makerAssetData: String that represents a specific maker asset (for more info: https://github.com/0xProject/0x-protocol-specification/blob/master/v2/v2-specification.md).
* gasPrice: gas price used to determine protocolFee amount, default to ethGasStation fast amount.
* orders: An array of objects conforming to SignedOrder. These orders can be used to cover the requested assetBuyAmount plus slippage.
* bestCaseQuoteInfo: Info about the best case price for the asset.
* worstCaseQuoteInfo: Info about the worst case price for the asset.
@ -217,6 +211,7 @@ export interface GetExtensionContractTypeOpts {
export interface SwapQuoteBase {
takerAssetData: string;
makerAssetData: string;
gasPrice: BigNumber;
orders: SignedOrder[];
bestCaseQuoteInfo: SwapQuoteInfo;
worstCaseQuoteInfo: SwapQuoteInfo;
@ -245,14 +240,14 @@ export interface MarketBuySwapQuote extends SwapQuoteBase {
* takerAssetAmount: The amount of takerAsset swapped for desired makerAsset.
* totalTakerAssetAmount: The total amount of takerAsset required to complete the swap (filling orders, and paying takerFees).
* makerAssetAmount: The amount of makerAsset that will be acquired through the swap.
* protocolFeeInEthAmount: The amount of eth to pay as protocol fee to perform the swap for desired asset.
* protocolFeeInWeiAmount: The amount of ETH to pay (in WEI) as protocol fee to perform the swap for desired asset.
*/
export interface SwapQuoteInfo {
feeTakerAssetAmount: BigNumber;
takerAssetAmount: BigNumber;
totalTakerAssetAmount: BigNumber;
makerAssetAmount: BigNumber;
protocolFeeInEthAmount: BigNumber;
protocolFeeInWeiAmount: BigNumber;
}
/**

View File

@ -16,8 +16,9 @@ export const affiliateFeeUtils = {
feePercentage >= 0 && feePercentage <= constants.MAX_AFFILIATE_FEE_PERCENTAGE,
'feePercentage must be between range 0-0.05 (inclusive)',
);
const ethAmount = swapQuoteInfo.protocolFeeInEthAmount.plus(swapQuoteInfo.totalTakerAssetAmount);
const affiliateFeeAmount = ethAmount.multipliedBy(feePercentage);
const ethAmount = swapQuoteInfo.protocolFeeInWeiAmount.plus(swapQuoteInfo.totalTakerAssetAmount);
// HACK(dekz): This is actually in WEI amount not ETH
const affiliateFeeAmount = ethAmount.multipliedBy(feePercentage).toFixed(0, BigNumber.ROUND_UP);
const ethAmountWithFees = ethAmount.plus(affiliateFeeAmount);
return ethAmountWithFees;
},

View File

@ -96,4 +96,8 @@ export const assert = {
`Expected ${variableName} to be between 0 and 1, but is ${percentage}`,
);
},
isValidForwarderExtensionContractOpts(variableName: string, opts: any): void {
assert.isValidPercentage(`${variableName}.feePercentage`, opts.feePercentage);
assert.isETHAddressHex(`${variableName}.feeRecipient`, opts.feeRecipient);
},
};

View File

@ -1,4 +1,4 @@
import { DevUtilsContract } from '@0x/contracts-dev-utils';
import { DevUtilsContract } from '@0x/abi-gen-wrappers';
import { orderCalculationUtils } from '@0x/order-utils';
import { OrderStatus, SignedOrder } from '@0x/types';
import * as _ from 'lodash';

View File

@ -5,12 +5,14 @@ import * as _ from 'lodash';
import { constants } from '../constants';
import { SwapQuoterError } from '../types';
// tslint:disable:no-unnecessary-type-assertion
export const protocolFeeUtils = {
/**
* Gets 'fast' gas price from Eth Gas Station.
*/
async getGasPriceEstimationOrThrowAsync(): Promise<BigNumber> {
export class ProtocolFeeUtils {
// tslint:disable-next-line:prefer-function-over-method
public async getProtocolFeeMultiplierAsync(): Promise<BigNumber> {
return new BigNumber(150000);
}
// tslint:disable-next-line: prefer-function-over-method
public async getGasPriceEstimationOrThrowAsync(): Promise<BigNumber> {
try {
const res = await fetch(`${constants.ETH_GAS_STATION_API_BASE_URL}/json/ethgasAPI.json`);
const gasInfo = await res.json();
@ -20,12 +22,16 @@ export const protocolFeeUtils = {
} catch (e) {
throw new Error(SwapQuoterError.NoGasPriceProvidedOrEstimated);
}
},
}
/**
* Calculates protocol fee with protofol fee multiplier for each fill.
*/
calculateWorstCaseProtocolFee<T extends Order>(orders: T[], gasPrice: BigNumber): BigNumber {
const protocolFee = new BigNumber(orders.length * constants.PROTOCOL_FEE_MULTIPLIER).times(gasPrice);
public async calculateWorstCaseProtocolFeeAsync<T extends Order>(
orders: T[],
gasPrice: BigNumber,
): Promise<BigNumber> {
const protocolFeeMultiplier = await this.getProtocolFeeMultiplierAsync();
const protocolFee = new BigNumber(orders.length).times(protocolFeeMultiplier).times(gasPrice);
return protocolFee;
},
};
}
}

View File

@ -14,48 +14,53 @@ import {
} from '../types';
import { marketUtils } from './market_utils';
import { protocolFeeUtils } from './protocol_fee_utils';
import { ProtocolFeeUtils } from './protocol_fee_utils';
import { utils } from './utils';
// Calculates a swap quote for orders
export const swapQuoteCalculator = {
calculateMarketSellSwapQuote(
async calculateMarketSellSwapQuoteAsync(
prunedOrders: PrunedSignedOrder[],
takerAssetFillAmount: BigNumber,
slippagePercentage: number,
gasPrice: BigNumber,
): MarketSellSwapQuote {
return calculateSwapQuote(
protocolFeeUtils: ProtocolFeeUtils,
): Promise<MarketSellSwapQuote> {
return (await calculateSwapQuoteAsync(
prunedOrders,
takerAssetFillAmount,
slippagePercentage,
gasPrice,
protocolFeeUtils,
MarketOperation.Sell,
) as MarketSellSwapQuote;
)) as MarketSellSwapQuote;
},
calculateMarketBuySwapQuote(
async calculateMarketBuySwapQuoteAsync(
prunedOrders: PrunedSignedOrder[],
takerAssetFillAmount: BigNumber,
slippagePercentage: number,
gasPrice: BigNumber,
): MarketBuySwapQuote {
return calculateSwapQuote(
protocolFeeUtils: ProtocolFeeUtils,
): Promise<MarketBuySwapQuote> {
return (await calculateSwapQuoteAsync(
prunedOrders,
takerAssetFillAmount,
slippagePercentage,
gasPrice,
protocolFeeUtils,
MarketOperation.Buy,
) as MarketBuySwapQuote;
)) as MarketBuySwapQuote;
},
};
function calculateSwapQuote(
async function calculateSwapQuoteAsync(
prunedOrders: PrunedSignedOrder[],
assetFillAmount: BigNumber,
slippagePercentage: number,
gasPrice: BigNumber,
protocolFeeUtils: ProtocolFeeUtils,
marketOperation: MarketOperation,
): SwapQuote {
): Promise<SwapQuote> {
const slippageBufferAmount = assetFillAmount.multipliedBy(slippagePercentage).integerValue();
let resultOrders: PrunedSignedOrder[];
@ -98,12 +103,19 @@ function calculateSwapQuote(
const takerAssetData = resultOrders[0].takerAssetData;
const makerAssetData = resultOrders[0].makerAssetData;
const bestCaseQuoteInfo = calculateQuoteInfo(resultOrders, assetFillAmount, gasPrice, marketOperation);
const bestCaseQuoteInfo = await calculateQuoteInfoAsync(
resultOrders,
assetFillAmount,
gasPrice,
protocolFeeUtils,
marketOperation,
);
// in order to calculate the maxRate, reverse the ordersAndFillableAmounts such that they are sorted from worst rate to best rate
const worstCaseQuoteInfo = calculateQuoteInfo(
const worstCaseQuoteInfo = await calculateQuoteInfoAsync(
_.reverse(_.clone(resultOrders)),
assetFillAmount,
gasPrice,
protocolFeeUtils,
marketOperation,
);
@ -113,6 +125,7 @@ function calculateSwapQuote(
orders: resultOrders,
bestCaseQuoteInfo,
worstCaseQuoteInfo,
gasPrice,
};
if (marketOperation === MarketOperation.Buy) {
@ -130,24 +143,26 @@ function calculateSwapQuote(
}
}
function calculateQuoteInfo(
async function calculateQuoteInfoAsync(
prunedOrders: PrunedSignedOrder[],
assetFillAmount: BigNumber,
gasPrice: BigNumber,
protocolFeeUtils: ProtocolFeeUtils,
operation: MarketOperation,
): SwapQuoteInfo {
): Promise<SwapQuoteInfo> {
if (operation === MarketOperation.Buy) {
return calculateMarketBuyQuoteInfo(prunedOrders, assetFillAmount, gasPrice);
return calculateMarketBuyQuoteInfoAsync(prunedOrders, assetFillAmount, gasPrice, protocolFeeUtils);
} else {
return calculateMarketSellQuoteInfo(prunedOrders, assetFillAmount, gasPrice);
return calculateMarketSellQuoteInfoAsync(prunedOrders, assetFillAmount, gasPrice, protocolFeeUtils);
}
}
function calculateMarketSellQuoteInfo(
async function calculateMarketSellQuoteInfoAsync(
prunedOrders: PrunedSignedOrder[],
takerAssetSellAmount: BigNumber,
gasPrice: BigNumber,
): SwapQuoteInfo {
protocolFeeUtils: ProtocolFeeUtils,
): Promise<SwapQuoteInfo> {
const result = _.reduce(
prunedOrders,
(acc, order) => {
@ -190,20 +205,22 @@ function calculateMarketSellQuoteInfo(
remainingTakerAssetFillAmount: takerAssetSellAmount,
},
);
const protocolFeeInWeiAmount = await protocolFeeUtils.calculateWorstCaseProtocolFeeAsync(prunedOrders, gasPrice);
return {
feeTakerAssetAmount: result.totalFeeTakerAssetAmount,
takerAssetAmount: result.totalTakerAssetAmount,
totalTakerAssetAmount: result.totalFeeTakerAssetAmount.plus(result.totalTakerAssetAmount),
makerAssetAmount: result.totalMakerAssetAmount,
protocolFeeInEthAmount: protocolFeeUtils.calculateWorstCaseProtocolFee(prunedOrders, gasPrice),
protocolFeeInWeiAmount,
};
}
function calculateMarketBuyQuoteInfo(
async function calculateMarketBuyQuoteInfoAsync(
prunedOrders: PrunedSignedOrder[],
makerAssetBuyAmount: BigNumber,
gasPrice: BigNumber,
): SwapQuoteInfo {
protocolFeeUtils: ProtocolFeeUtils,
): Promise<SwapQuoteInfo> {
const result = _.reduce(
prunedOrders,
(acc, order) => {
@ -243,12 +260,13 @@ function calculateMarketBuyQuoteInfo(
remainingMakerAssetFillAmount: makerAssetBuyAmount,
},
);
const protocolFeeInWeiAmount = await protocolFeeUtils.calculateWorstCaseProtocolFeeAsync(prunedOrders, gasPrice);
return {
feeTakerAssetAmount: result.totalFeeTakerAssetAmount,
takerAssetAmount: result.totalTakerAssetAmount,
totalTakerAssetAmount: result.totalFeeTakerAssetAmount.plus(result.totalTakerAssetAmount),
makerAssetAmount: result.totalMakerAssetAmount,
protocolFeeInEthAmount: protocolFeeUtils.calculateWorstCaseProtocolFee(prunedOrders, gasPrice),
protocolFeeInWeiAmount,
};
}

View File

@ -1,6 +1,5 @@
import { DevUtilsContract, WETH9Contract } from '@0x/abi-gen-wrappers';
import { ContractAddresses } from '@0x/contract-addresses';
import { DevUtilsContract } from '@0x/contracts-dev-utils';
import { WETH9Contract } from '@0x/contracts-erc20';
import { SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { SupportedProvider, Web3Wrapper } from '@0x/web3-wrapper';
@ -81,7 +80,7 @@ export const swapQuoteConsumerUtils = {
}
const ethAmount =
opts.ethAmount ||
quote.worstCaseQuoteInfo.takerAssetAmount.plus(quote.worstCaseQuoteInfo.protocolFeeInEthAmount);
quote.worstCaseQuoteInfo.takerAssetAmount.plus(quote.worstCaseQuoteInfo.protocolFeeInWeiAmount);
const takerAddress = await swapQuoteConsumerUtils.getTakerAddressAsync(provider, opts);
const takerEthAndWethBalance =
takerAddress !== undefined

View File

@ -1,7 +1,5 @@
import { DevUtilsContract, ERC20TokenContract, ExchangeContract } from '@0x/abi-gen-wrappers';
import { ContractAddresses } from '@0x/contract-addresses';
import { DevUtilsContract } from '@0x/contracts-dev-utils';
import { ERC20TokenContract } from '@0x/contracts-erc20';
import { ExchangeContract } from '@0x/contracts-exchange';
import { constants as devConstants, OrderFactory } from '@0x/contracts-test-utils';
import { BlockchainLifecycle, tokenUtils } from '@0x/dev-utils';
import { migrateOnceAsync } from '@0x/migrations';
@ -20,9 +18,10 @@ import {
MarketSellSwapQuote,
PrunedSignedOrder,
} from '../src/types';
import { ProtocolFeeUtils } from '../src/utils/protocol_fee_utils';
import { chaiSetup } from './utils/chai_setup';
import { getFullyFillableSwapQuoteWithNoFees } from './utils/swap_quote';
import { getFullyFillableSwapQuoteWithNoFeesAsync } from './utils/swap_quote';
import { provider, web3Wrapper } from './utils/web3_wrapper';
chaiSetup.configure();
@ -67,6 +66,7 @@ const expectMakerAndTakerBalancesAsyncFactory = (
};
describe('ExchangeSwapQuoteConsumer', () => {
let protocolFeeUtils: ProtocolFeeUtils;
let userAddresses: string[];
let erc20MakerTokenContract: ERC20TokenContract;
let erc20TakerTokenContract: ERC20TokenContract;
@ -130,6 +130,7 @@ describe('ExchangeSwapQuoteConsumer', () => {
};
const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)];
orderFactory = new OrderFactory(privateKey, defaultOrderParams);
protocolFeeUtils = new ProtocolFeeUtils();
expectMakerAndTakerBalancesForTakerAssetAsync = expectMakerAndTakerBalancesAsyncFactory(
erc20TakerTokenContract,
makerAddress,
@ -156,20 +157,22 @@ describe('ExchangeSwapQuoteConsumer', () => {
orders.push(prunedOrder as PrunedSignedOrder);
}
marketSellSwapQuote = getFullyFillableSwapQuoteWithNoFees(
marketSellSwapQuote = await getFullyFillableSwapQuoteWithNoFeesAsync(
makerAssetData,
takerAssetData,
orders,
MarketOperation.Sell,
GAS_PRICE,
protocolFeeUtils,
);
marketBuySwapQuote = getFullyFillableSwapQuoteWithNoFees(
marketBuySwapQuote = await getFullyFillableSwapQuoteWithNoFeesAsync(
makerAssetData,
takerAssetData,
orders,
MarketOperation.Buy,
GAS_PRICE,
protocolFeeUtils,
);
swapQuoteConsumer = new ExchangeSwapQuoteConsumer(provider, contractAddresses, {
@ -212,7 +215,6 @@ describe('ExchangeSwapQuoteConsumer', () => {
);
await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketSellSwapQuote, {
takerAddress,
gasPrice: GAS_PRICE,
gasLimit: 4000000,
});
await expectMakerAndTakerBalancesForMakerAssetAsync(
@ -235,7 +237,6 @@ describe('ExchangeSwapQuoteConsumer', () => {
);
await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketBuySwapQuote, {
takerAddress,
gasPrice: GAS_PRICE,
gasLimit: 4000000,
});
await expectMakerAndTakerBalancesForMakerAssetAsync(

View File

@ -1,7 +1,5 @@
import { DevUtilsContract, ERC20TokenContract, ForwarderContract } from '@0x/abi-gen-wrappers';
import { ContractAddresses } from '@0x/contract-addresses';
import { DevUtilsContract } from '@0x/contracts-dev-utils';
import { ERC20TokenContract } from '@0x/contracts-erc20';
import { ForwarderContract } from '@0x/contracts-exchange-forwarder';
import { constants as devConstants, OrderFactory } from '@0x/contracts-test-utils';
import { BlockchainLifecycle, tokenUtils } from '@0x/dev-utils';
import { migrateOnceAsync } from '@0x/migrations';
@ -19,9 +17,10 @@ import {
MarketOperation,
PrunedSignedOrder,
} from '../src/types';
import { ProtocolFeeUtils } from '../src/utils/protocol_fee_utils';
import { chaiSetup } from './utils/chai_setup';
import { getFullyFillableSwapQuoteWithNoFees } from './utils/swap_quote';
import { getFullyFillableSwapQuoteWithNoFeesAsync } from './utils/swap_quote';
import { provider, web3Wrapper } from './utils/web3_wrapper';
chaiSetup.configure();
@ -33,7 +32,7 @@ const ONE_ETH_IN_WEI = new BigNumber(1000000000000000000);
const TESTRPC_CHAIN_ID = devConstants.TESTRPC_CHAIN_ID;
const UNLIMITED_ALLOWANCE_IN_BASE_UNITS = new BigNumber(2).pow(256).minus(1); // tslint:disable-line:custom-no-magic-numbers
const FEE_PERCENTAGE = 0.05;
const PARTIAL_PRUNED_SIGNED_ORDERS_FEELESS: Array<Partial<PrunedSignedOrder>> = [
{
takerAssetAmount: new BigNumber(2).multipliedBy(ONE_ETH_IN_WEI),
@ -67,7 +66,7 @@ const expectMakerAndTakerBalancesAsyncFactory = (
};
describe('ForwarderSwapQuoteConsumer', () => {
const FEE_PERCENTAGE = 0.05;
let protocolFeeUtils: ProtocolFeeUtils;
let userAddresses: string[];
let coinbaseAddress: string;
let makerAddress: string;
@ -133,6 +132,7 @@ describe('ForwarderSwapQuoteConsumer', () => {
};
const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)];
orderFactory = new OrderFactory(privateKey, defaultOrderParams);
protocolFeeUtils = new ProtocolFeeUtils();
expectMakerAndTakerBalancesAsync = expectMakerAndTakerBalancesAsyncFactory(
erc20TokenContract,
makerAddress,
@ -179,28 +179,31 @@ describe('ForwarderSwapQuoteConsumer', () => {
invalidOrders.push(prunedOrder as PrunedSignedOrder);
}
marketSellSwapQuote = getFullyFillableSwapQuoteWithNoFees(
marketSellSwapQuote = await getFullyFillableSwapQuoteWithNoFeesAsync(
makerAssetData,
wethAssetData,
orders,
MarketOperation.Sell,
GAS_PRICE,
protocolFeeUtils,
);
marketBuySwapQuote = getFullyFillableSwapQuoteWithNoFees(
marketBuySwapQuote = await getFullyFillableSwapQuoteWithNoFeesAsync(
makerAssetData,
wethAssetData,
orders,
MarketOperation.Buy,
GAS_PRICE,
protocolFeeUtils,
);
invalidMarketBuySwapQuote = getFullyFillableSwapQuoteWithNoFees(
invalidMarketBuySwapQuote = await getFullyFillableSwapQuoteWithNoFeesAsync(
makerAssetData,
takerAssetData,
invalidOrders,
MarketOperation.Buy,
GAS_PRICE,
protocolFeeUtils,
);
swapQuoteConsumer = new ForwarderSwapQuoteConsumer(provider, contractAddresses, {
@ -234,7 +237,6 @@ describe('ForwarderSwapQuoteConsumer', () => {
);
await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketBuySwapQuote, {
takerAddress,
gasPrice: GAS_PRICE,
gasLimit: 4000000,
ethAmount: new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
});
@ -251,7 +253,6 @@ describe('ForwarderSwapQuoteConsumer', () => {
);
await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketSellSwapQuote, {
takerAddress,
gasPrice: GAS_PRICE,
gasLimit: 4000000,
});
await expectMakerAndTakerBalancesAsync(
@ -268,10 +269,11 @@ describe('ForwarderSwapQuoteConsumer', () => {
const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketBuySwapQuote, {
takerAddress,
gasPrice: GAS_PRICE,
gasLimit: 4000000,
feePercentage: FEE_PERCENTAGE,
feeRecipient,
extensionContractOpts: {
feePercentage: 0.05,
feeRecipient,
},
});
await expectMakerAndTakerBalancesAsync(
constants.ZERO_AMOUNT,
@ -279,7 +281,7 @@ describe('ForwarderSwapQuoteConsumer', () => {
);
const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
const totalEthSpent = marketBuySwapQuote.bestCaseQuoteInfo.totalTakerAssetAmount.plus(
marketBuySwapQuote.bestCaseQuoteInfo.protocolFeeInEthAmount,
marketBuySwapQuote.bestCaseQuoteInfo.protocolFeeInWeiAmount,
);
expect(feeRecipientEthBalanceAfter.minus(feeRecipientEthBalanceBefore)).to.bignumber.equal(
new BigNumber(FEE_PERCENTAGE).times(totalEthSpent),
@ -294,10 +296,11 @@ describe('ForwarderSwapQuoteConsumer', () => {
const feeRecipientEthBalanceBefore = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(marketSellSwapQuote, {
takerAddress,
feePercentage: FEE_PERCENTAGE,
feeRecipient,
gasPrice: GAS_PRICE,
gasLimit: 4000000,
extensionContractOpts: {
feePercentage: 0.05,
feeRecipient,
},
});
await expectMakerAndTakerBalancesAsync(
constants.ZERO_AMOUNT,
@ -305,7 +308,7 @@ describe('ForwarderSwapQuoteConsumer', () => {
);
const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
const totalEthSpent = marketBuySwapQuote.bestCaseQuoteInfo.totalTakerAssetAmount.plus(
marketBuySwapQuote.bestCaseQuoteInfo.protocolFeeInEthAmount,
marketBuySwapQuote.bestCaseQuoteInfo.protocolFeeInWeiAmount,
);
expect(feeRecipientEthBalanceAfter.minus(feeRecipientEthBalanceBefore)).to.bignumber.equal(
new BigNumber(FEE_PERCENTAGE).times(totalEthSpent),
@ -370,8 +373,10 @@ describe('ForwarderSwapQuoteConsumer', () => {
const { toAddress, params } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(
marketSellSwapQuote,
{
feePercentage: 0.05,
feeRecipient,
extensionContractOpts: {
feePercentage: 0.05,
feeRecipient,
},
},
);
expect(toAddress).to.deep.equal(forwarderContract.address);
@ -391,8 +396,10 @@ describe('ForwarderSwapQuoteConsumer', () => {
const { toAddress, params } = await swapQuoteConsumer.getSmartContractParamsOrThrowAsync(
marketBuySwapQuote,
{
feePercentage: 0.05,
feeRecipient,
extensionContractOpts: {
feePercentage: 0.05,
feeRecipient,
},
},
);
expect(toAddress).to.deep.equal(forwarderContract.address);
@ -480,8 +487,10 @@ describe('ForwarderSwapQuoteConsumer', () => {
const { calldataHexString, toAddress, ethAmount } = await swapQuoteConsumer.getCalldataOrThrowAsync(
marketSellSwapQuote,
{
feePercentage: FEE_PERCENTAGE,
feeRecipient,
extensionContractOpts: {
feePercentage: 0.05,
feeRecipient,
},
},
);
expect(toAddress).to.deep.equal(contractAddresses.forwarder);
@ -498,7 +507,7 @@ describe('ForwarderSwapQuoteConsumer', () => {
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
);
const totalEthSpent = marketBuySwapQuote.bestCaseQuoteInfo.totalTakerAssetAmount.plus(
marketBuySwapQuote.bestCaseQuoteInfo.protocolFeeInEthAmount,
marketBuySwapQuote.bestCaseQuoteInfo.protocolFeeInWeiAmount,
);
const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
expect(feeRecipientEthBalanceAfter.minus(feeRecipientEthBalanceBefore)).to.bignumber.equal(
@ -514,8 +523,10 @@ describe('ForwarderSwapQuoteConsumer', () => {
const { calldataHexString, toAddress, ethAmount } = await swapQuoteConsumer.getCalldataOrThrowAsync(
marketBuySwapQuote,
{
feePercentage: FEE_PERCENTAGE,
feeRecipient,
extensionContractOpts: {
feePercentage: 0.05,
feeRecipient,
},
},
);
expect(toAddress).to.deep.equal(contractAddresses.forwarder);
@ -532,7 +543,7 @@ describe('ForwarderSwapQuoteConsumer', () => {
new BigNumber(10).multipliedBy(ONE_ETH_IN_WEI),
);
const totalEthSpent = marketBuySwapQuote.bestCaseQuoteInfo.totalTakerAssetAmount.plus(
marketBuySwapQuote.bestCaseQuoteInfo.protocolFeeInEthAmount,
marketBuySwapQuote.bestCaseQuoteInfo.protocolFeeInWeiAmount,
);
const feeRecipientEthBalanceAfter = await web3Wrapper.getBalanceInWeiAsync(feeRecipient);
expect(feeRecipientEthBalanceAfter.minus(feeRecipientEthBalanceBefore)).to.bignumber.equal(

View File

@ -1,7 +1,5 @@
import { DevUtilsContract, ERC20TokenContract, ExchangeContract } from '@0x/abi-gen-wrappers';
import { ContractAddresses } from '@0x/contract-addresses';
import { DevUtilsContract } from '@0x/contracts-dev-utils';
import { ERC20TokenContract } from '@0x/contracts-erc20';
import { ExchangeContract } from '@0x/contracts-exchange';
import { constants as devConstants, getLatestBlockTimestampAsync, OrderFactory } from '@0x/contracts-test-utils';
import { BlockchainLifecycle, tokenUtils } from '@0x/dev-utils';
import { migrateOnceAsync } from '@0x/migrations';
@ -25,7 +23,8 @@ const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
const ONE_ETH_IN_WEI = new BigNumber(1000000000000000000);
const TESTRPC_CHAIN_ID = devConstants.TESTRPC_CHAIN_ID;
const GAS_PRICE = new BigNumber(devConstants.DEFAULT_GAS_PRICE);
const PROTOCOL_FEE_PER_FILL = GAS_PRICE.times(constants.PROTOCOL_FEE_MULTIPLIER);
const PROTOCOL_FEE_MULTIPLIER = 150000;
const PROTOCOL_FEE_PER_FILL = GAS_PRICE.times(PROTOCOL_FEE_MULTIPLIER);
const UNLIMITED_ALLOWANCE_IN_BASE_UNITS = new BigNumber(2).pow(256).minus(1); // tslint:disable-line:custom-no-magic-numbers
// tslint:disable: no-unused-expression

View File

@ -3,10 +3,13 @@ import { BigNumber } from '@0x/utils';
import * as chai from 'chai';
import * as _ from 'lodash';
import 'mocha';
import * as TypeMoq from 'typemoq';
import { ProtocolFeeUtils } from '../src/utils/protocol_fee_utils';
import { swapQuoteCalculator } from '../src/utils/swap_quote_calculator';
import { chaiSetup } from './utils/chai_setup';
import { protocolFeeUtilsMock } from './utils/mocks';
import { testHelpers } from './utils/test_helpers';
import { testOrders } from './utils/test_orders';
import { baseUnitAmount } from './utils/utils';
@ -25,105 +28,120 @@ const MIXED_TEST_ORDERS = _.concat(
// tslint:disable:max-file-line-count
// tslint:disable:custom-no-magic-numbers
describe('swapQuoteCalculator', () => {
let mockProtocolFeeUtils: TypeMoq.IMock<ProtocolFeeUtils>;
before(async () => {
mockProtocolFeeUtils = protocolFeeUtilsMock();
});
describe('#calculateMarketSellSwapQuote', () => {
describe('InsufficientLiquidityError', () => {
it('should throw if not enough taker asset liquidity (multiple feeless orders)', () => {
const errorFunction = () => {
swapQuoteCalculator.calculateMarketSellSwapQuote(
it('should throw if not enough taker asset liquidity (multiple feeless orders)', async () => {
const errorFunction = async () => {
await swapQuoteCalculator.calculateMarketSellSwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEELESS,
baseUnitAmount(10),
0,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, baseUnitAmount(9));
await testHelpers.expectInsufficientLiquidityErrorAsync(expect, errorFunction, baseUnitAmount(9));
});
it('should throw if not enough taker asset liquidity (multiple feeless orders with 20% slippage)', () => {
const errorFunction = () => {
swapQuoteCalculator.calculateMarketSellSwapQuote(
it('should throw if not enough taker asset liquidity (multiple feeless orders with 20% slippage)', async () => {
const errorFunction = async () => {
await swapQuoteCalculator.calculateMarketSellSwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEELESS,
baseUnitAmount(10),
0.2,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, baseUnitAmount(7.5));
await testHelpers.expectInsufficientLiquidityErrorAsync(expect, errorFunction, baseUnitAmount(7.5));
});
it('should throw if not enough taker asset liquidity (multiple takerAsset denominated fee orders with no slippage)', () => {
const errorFunction = () => {
swapQuoteCalculator.calculateMarketSellSwapQuote(
it('should throw if not enough taker asset liquidity (multiple takerAsset denominated fee orders with no slippage)', async () => {
const errorFunction = async () => {
await swapQuoteCalculator.calculateMarketSellSwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_TAKER_ASSET,
baseUnitAmount(20),
0,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, baseUnitAmount(15));
await testHelpers.expectInsufficientLiquidityErrorAsync(expect, errorFunction, baseUnitAmount(15));
});
it('should throw if not enough taker asset liquidity (multiple takerAsset denominated fee orders with 20% slippage)', () => {
const errorFunction = () => {
swapQuoteCalculator.calculateMarketSellSwapQuote(
it('should throw if not enough taker asset liquidity (multiple takerAsset denominated fee orders with 20% slippage)', async () => {
const errorFunction = async () => {
await swapQuoteCalculator.calculateMarketSellSwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_TAKER_ASSET,
baseUnitAmount(20),
0.2,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, baseUnitAmount(12.5));
await testHelpers.expectInsufficientLiquidityErrorAsync(expect, errorFunction, baseUnitAmount(12.5));
});
it('should throw if not enough taker asset liquidity (multiple makerAsset denominated fee orders with no slippage)', () => {
const errorFunction = () => {
swapQuoteCalculator.calculateMarketSellSwapQuote(
it('should throw if not enough taker asset liquidity (multiple makerAsset denominated fee orders with no slippage)', async () => {
const errorFunction = async () => {
await swapQuoteCalculator.calculateMarketSellSwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_MAKER_ASSET,
baseUnitAmount(10),
0,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, baseUnitAmount(9));
await testHelpers.expectInsufficientLiquidityErrorAsync(expect, errorFunction, baseUnitAmount(9));
});
it('should throw if not enough taker asset liquidity (multiple makerAsset denominated fee orders with 20% slippage)', () => {
const errorFunction = () => {
swapQuoteCalculator.calculateMarketSellSwapQuote(
it('should throw if not enough taker asset liquidity (multiple makerAsset denominated fee orders with 20% slippage)', async () => {
const errorFunction = async () => {
await swapQuoteCalculator.calculateMarketSellSwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_MAKER_ASSET,
baseUnitAmount(10),
0.2,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, baseUnitAmount(7.5));
await testHelpers.expectInsufficientLiquidityErrorAsync(expect, errorFunction, baseUnitAmount(7.5));
});
it('should throw if not enough taker asset liquidity (multiple mixed feeType orders with no slippage)', () => {
const errorFunction = () => {
swapQuoteCalculator.calculateMarketSellSwapQuote(
it('should throw if not enough taker asset liquidity (multiple mixed feeType orders with no slippage)', async () => {
const errorFunction = async () => {
await swapQuoteCalculator.calculateMarketSellSwapQuoteAsync(
MIXED_TEST_ORDERS,
baseUnitAmount(40),
0,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, baseUnitAmount(33));
await testHelpers.expectInsufficientLiquidityErrorAsync(expect, errorFunction, baseUnitAmount(33));
});
it('should throw if not enough taker asset liquidity (multiple mixed feeTyoe orders with 20% slippage)', () => {
const errorFunction = () => {
swapQuoteCalculator.calculateMarketSellSwapQuote(
it('should throw if not enough taker asset liquidity (multiple mixed feeTyoe orders with 20% slippage)', async () => {
const errorFunction = async () => {
await swapQuoteCalculator.calculateMarketSellSwapQuoteAsync(
MIXED_TEST_ORDERS,
baseUnitAmount(40),
0.2,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, baseUnitAmount(27.5));
await testHelpers.expectInsufficientLiquidityErrorAsync(expect, errorFunction, baseUnitAmount(27.5));
});
});
it('calculates a correct swapQuote with no slippage (feeless orders)', () => {
it('calculates a correct swapQuote with no slippage (feeless orders)', async () => {
const assetSellAmount = baseUnitAmount(0.5);
const slippagePercentage = 0;
const swapQuote = swapQuoteCalculator.calculateMarketSellSwapQuote(
const swapQuote = await swapQuoteCalculator.calculateMarketSellSwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEELESS,
assetSellAmount,
slippagePercentage,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
// test if orders are correct
expect(swapQuote.orders).to.deep.equal([testOrders.PRUNED_SIGNED_ORDERS_FEELESS[0]]);
@ -134,25 +152,27 @@ describe('swapQuoteCalculator', () => {
takerAssetAmount: assetSellAmount,
totalTakerAssetAmount: assetSellAmount,
makerAssetAmount: baseUnitAmount(3),
protocolFeeInEthAmount: baseUnitAmount(15, 4),
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
});
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
feeTakerAssetAmount: baseUnitAmount(0),
takerAssetAmount: assetSellAmount,
totalTakerAssetAmount: assetSellAmount,
makerAssetAmount: baseUnitAmount(3),
protocolFeeInEthAmount: baseUnitAmount(15, 4),
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
});
});
it('calculates a correct swapQuote with slippage (feeless orders)', () => {
it('calculates a correct swapQuote with slippage (feeless orders)', async () => {
const assetSellAmount = baseUnitAmount(1);
const slippagePercentage = 0.2;
const swapQuote = swapQuoteCalculator.calculateMarketSellSwapQuote(
const swapQuote = await swapQuoteCalculator.calculateMarketSellSwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEELESS,
assetSellAmount,
slippagePercentage,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
// test if orders are correct
expect(swapQuote.orders).to.deep.equal([
testOrders.PRUNED_SIGNED_ORDERS_FEELESS[0],
@ -165,24 +185,25 @@ describe('swapQuoteCalculator', () => {
takerAssetAmount: assetSellAmount,
totalTakerAssetAmount: assetSellAmount,
makerAssetAmount: baseUnitAmount(6),
protocolFeeInEthAmount: baseUnitAmount(30, 4),
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
});
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
feeTakerAssetAmount: baseUnitAmount(0),
takerAssetAmount: assetSellAmount,
totalTakerAssetAmount: assetSellAmount,
makerAssetAmount: baseUnitAmount(0.4),
protocolFeeInEthAmount: baseUnitAmount(30, 4),
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
});
});
it('calculates a correct swapQuote with no slippage (takerAsset denominated fee orders)', () => {
it('calculates a correct swapQuote with no slippage (takerAsset denominated fee orders)', async () => {
const assetSellAmount = baseUnitAmount(4);
const slippagePercentage = 0;
const swapQuote = swapQuoteCalculator.calculateMarketSellSwapQuote(
const swapQuote = await swapQuoteCalculator.calculateMarketSellSwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_TAKER_ASSET,
assetSellAmount,
slippagePercentage,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
// test if orders are correct
expect(swapQuote.orders).to.deep.equal([testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_TAKER_ASSET[0]]);
@ -193,24 +214,25 @@ describe('swapQuoteCalculator', () => {
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(3)),
totalTakerAssetAmount: assetSellAmount,
makerAssetAmount: baseUnitAmount(6),
protocolFeeInEthAmount: baseUnitAmount(15, 4),
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
});
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
feeTakerAssetAmount: baseUnitAmount(3),
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(3)),
totalTakerAssetAmount: assetSellAmount,
makerAssetAmount: baseUnitAmount(6),
protocolFeeInEthAmount: baseUnitAmount(15, 4),
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
});
});
it('calculates a correct swapQuote with slippage (takerAsset denominated fee orders)', () => {
it('calculates a correct swapQuote with slippage (takerAsset denominated fee orders)', async () => {
const assetSellAmount = baseUnitAmount(3);
const slippagePercentage = 0.5;
const swapQuote = swapQuoteCalculator.calculateMarketSellSwapQuote(
const swapQuote = await swapQuoteCalculator.calculateMarketSellSwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_TAKER_ASSET,
assetSellAmount,
slippagePercentage,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
// test if orders are correct
expect(swapQuote.orders).to.deep.equal([
@ -224,24 +246,25 @@ describe('swapQuoteCalculator', () => {
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(2.25)),
totalTakerAssetAmount: assetSellAmount,
makerAssetAmount: baseUnitAmount(4.5),
protocolFeeInEthAmount: baseUnitAmount(30, 4),
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
});
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
feeTakerAssetAmount: baseUnitAmount(0.5),
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(0.5)),
totalTakerAssetAmount: assetSellAmount,
makerAssetAmount: baseUnitAmount(1),
protocolFeeInEthAmount: baseUnitAmount(30, 4),
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
});
});
it('calculates a correct swapQuote with no slippage (makerAsset denominated fee orders)', () => {
it('calculates a correct swapQuote with no slippage (makerAsset denominated fee orders)', async () => {
const assetSellAmount = baseUnitAmount(4);
const slippagePercentage = 0;
const swapQuote = swapQuoteCalculator.calculateMarketSellSwapQuote(
const swapQuote = await swapQuoteCalculator.calculateMarketSellSwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_MAKER_ASSET,
assetSellAmount,
slippagePercentage,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
// test if orders are correct
expect(swapQuote.orders).to.deep.equal([testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_MAKER_ASSET[0]]);
@ -252,24 +275,25 @@ describe('swapQuoteCalculator', () => {
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(2)),
totalTakerAssetAmount: assetSellAmount,
makerAssetAmount: baseUnitAmount(0.8),
protocolFeeInEthAmount: baseUnitAmount(15, 4),
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
});
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
feeTakerAssetAmount: baseUnitAmount(2),
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(2)),
totalTakerAssetAmount: assetSellAmount,
makerAssetAmount: baseUnitAmount(0.8),
protocolFeeInEthAmount: baseUnitAmount(15, 4),
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
});
});
it('calculates a correct swapQuote with slippage (makerAsset denominated fee orders)', () => {
it('calculates a correct swapQuote with slippage (makerAsset denominated fee orders)', async () => {
const assetSellAmount = baseUnitAmount(4);
const slippagePercentage = 0.5;
const swapQuote = swapQuoteCalculator.calculateMarketSellSwapQuote(
const swapQuote = await swapQuoteCalculator.calculateMarketSellSwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_MAKER_ASSET,
assetSellAmount,
slippagePercentage,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
// test if orders are correct
expect(swapQuote.orders).to.deep.equal([
@ -284,116 +308,125 @@ describe('swapQuoteCalculator', () => {
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(2)),
totalTakerAssetAmount: assetSellAmount,
makerAssetAmount: baseUnitAmount(0.8),
protocolFeeInEthAmount: baseUnitAmount(30, 4),
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
});
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
feeTakerAssetAmount: baseUnitAmount(2),
takerAssetAmount: assetSellAmount.minus(baseUnitAmount(2)),
totalTakerAssetAmount: assetSellAmount,
makerAssetAmount: baseUnitAmount(3.6),
protocolFeeInEthAmount: baseUnitAmount(30, 4),
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
});
});
});
describe('#calculateMarketBuySwapQuote', () => {
describe('#calculateMarketBuySwapQuoteAsync', () => {
describe('InsufficientLiquidityError', () => {
it('should throw if not enough maker asset liquidity (multiple feeless orders)', () => {
const errorFunction = () => {
swapQuoteCalculator.calculateMarketBuySwapQuote(
it('should throw if not enough maker asset liquidity (multiple feeless orders)', async () => {
const errorFunction = async () => {
await swapQuoteCalculator.calculateMarketBuySwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEELESS,
baseUnitAmount(12),
0,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, baseUnitAmount(10));
await testHelpers.expectInsufficientLiquidityErrorAsync(expect, errorFunction, baseUnitAmount(10));
});
it('should throw if not enough taker asset liquidity (multiple feeless orders with 20% slippage)', () => {
const errorFunction = () => {
swapQuoteCalculator.calculateMarketBuySwapQuote(
it('should throw if not enough taker asset liquidity (multiple feeless orders with 20% slippage)', async () => {
const errorFunction = async () => {
await swapQuoteCalculator.calculateMarketBuySwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEELESS,
baseUnitAmount(10),
0.6,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, baseUnitAmount(6.25));
await testHelpers.expectInsufficientLiquidityErrorAsync(expect, errorFunction, baseUnitAmount(6.25));
});
it('should throw if not enough taker asset liquidity (multiple takerAsset denominated fee orders with no slippage)', () => {
const errorFunction = () => {
swapQuoteCalculator.calculateMarketBuySwapQuote(
it('should throw if not enough taker asset liquidity (multiple takerAsset denominated fee orders with no slippage)', async () => {
const errorFunction = async () => {
await swapQuoteCalculator.calculateMarketBuySwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_TAKER_ASSET,
baseUnitAmount(12),
0,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, baseUnitAmount(10));
await testHelpers.expectInsufficientLiquidityErrorAsync(expect, errorFunction, baseUnitAmount(10));
});
it('should throw if not enough taker asset liquidity (multiple takerAsset denominated fee orders with 20% slippage)', () => {
const errorFunction = () => {
swapQuoteCalculator.calculateMarketBuySwapQuote(
it('should throw if not enough taker asset liquidity (multiple takerAsset denominated fee orders with 20% slippage)', async () => {
const errorFunction = async () => {
await swapQuoteCalculator.calculateMarketBuySwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_TAKER_ASSET,
baseUnitAmount(12),
0.6,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, baseUnitAmount(6.25));
await testHelpers.expectInsufficientLiquidityErrorAsync(expect, errorFunction, baseUnitAmount(6.25));
});
it('should throw if not enough taker asset liquidity (multiple makerAsset denominated fee orders with no slippage)', () => {
const errorFunction = () => {
swapQuoteCalculator.calculateMarketBuySwapQuote(
it('should throw if not enough taker asset liquidity (multiple makerAsset denominated fee orders with no slippage)', async () => {
const errorFunction = async () => {
await swapQuoteCalculator.calculateMarketBuySwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_MAKER_ASSET,
baseUnitAmount(6),
0,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, baseUnitAmount(5));
await testHelpers.expectInsufficientLiquidityErrorAsync(expect, errorFunction, baseUnitAmount(5));
});
it('should throw if not enough taker asset liquidity (multiple makerAsset denominated fee orders with 20% slippage)', () => {
const errorFunction = () => {
swapQuoteCalculator.calculateMarketBuySwapQuote(
it('should throw if not enough taker asset liquidity (multiple makerAsset denominated fee orders with 20% slippage)', async () => {
const errorFunction = async () => {
await swapQuoteCalculator.calculateMarketBuySwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_MAKER_ASSET,
baseUnitAmount(6),
0.6,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, baseUnitAmount(3.125));
await testHelpers.expectInsufficientLiquidityErrorAsync(expect, errorFunction, baseUnitAmount(3.125));
});
it('should throw if not enough taker asset liquidity (multiple mixed feeType orders with no slippage)', () => {
const errorFunction = () => {
swapQuoteCalculator.calculateMarketBuySwapQuote(
it('should throw if not enough taker asset liquidity (multiple mixed feeType orders with no slippage)', async () => {
const errorFunction = async () => {
await swapQuoteCalculator.calculateMarketBuySwapQuoteAsync(
MIXED_TEST_ORDERS,
baseUnitAmount(40),
0,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, baseUnitAmount(25));
await testHelpers.expectInsufficientLiquidityErrorAsync(expect, errorFunction, baseUnitAmount(25));
});
it('should throw if not enough taker asset liquidity (multiple mixed feeTyoe orders with 20% slippage)', () => {
const errorFunction = () => {
swapQuoteCalculator.calculateMarketBuySwapQuote(
it('should throw if not enough taker asset liquidity (multiple mixed feeTyoe orders with 20% slippage)', async () => {
const errorFunction = async () => {
await swapQuoteCalculator.calculateMarketBuySwapQuoteAsync(
MIXED_TEST_ORDERS,
baseUnitAmount(40),
0.6,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
};
testHelpers.expectInsufficientLiquidityError(expect, errorFunction, baseUnitAmount(15.625));
await testHelpers.expectInsufficientLiquidityErrorAsync(expect, errorFunction, baseUnitAmount(15.625));
});
});
it('calculates a correct swapQuote with no slippage (feeless orders)', () => {
it('calculates a correct swapQuote with no slippage (feeless orders)', async () => {
const assetBuyAmount = baseUnitAmount(3);
const slippagePercentage = 0;
const swapQuote = swapQuoteCalculator.calculateMarketBuySwapQuote(
const swapQuote = await swapQuoteCalculator.calculateMarketBuySwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEELESS,
assetBuyAmount,
slippagePercentage,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
// test if orders are correct
expect(swapQuote.orders).to.deep.equal([testOrders.PRUNED_SIGNED_ORDERS_FEELESS[0]]);
@ -404,24 +437,25 @@ describe('swapQuoteCalculator', () => {
takerAssetAmount: baseUnitAmount(0.5),
totalTakerAssetAmount: baseUnitAmount(0.5),
makerAssetAmount: assetBuyAmount,
protocolFeeInEthAmount: baseUnitAmount(15, 4),
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
});
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
feeTakerAssetAmount: baseUnitAmount(0),
takerAssetAmount: baseUnitAmount(0.5),
totalTakerAssetAmount: baseUnitAmount(0.5),
makerAssetAmount: assetBuyAmount,
protocolFeeInEthAmount: baseUnitAmount(15, 4),
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
});
});
it('calculates a correct swapQuote with slippage (feeless orders)', () => {
it('calculates a correct swapQuote with slippage (feeless orders)', async () => {
const assetBuyAmount = baseUnitAmount(5);
const slippagePercentage = 0.5;
const swapQuote = swapQuoteCalculator.calculateMarketBuySwapQuote(
const swapQuote = await swapQuoteCalculator.calculateMarketBuySwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEELESS,
assetBuyAmount,
slippagePercentage,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
// test if orders are correct
expect(swapQuote.orders).to.deep.equal([
@ -440,24 +474,25 @@ describe('swapQuoteCalculator', () => {
takerAssetAmount,
totalTakerAssetAmount: takerAssetAmount,
makerAssetAmount: assetBuyAmount,
protocolFeeInEthAmount: baseUnitAmount(30, 4),
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
});
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
feeTakerAssetAmount: baseUnitAmount(0),
takerAssetAmount: baseUnitAmount(5.5),
totalTakerAssetAmount: baseUnitAmount(5.5),
makerAssetAmount: assetBuyAmount,
protocolFeeInEthAmount: baseUnitAmount(30, 4),
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
});
});
it('calculates a correct swapQuote with no slippage (takerAsset denominated fee orders)', () => {
it('calculates a correct swapQuote with no slippage (takerAsset denominated fee orders)', async () => {
const assetBuyAmount = baseUnitAmount(3);
const slippagePercentage = 0;
const swapQuote = swapQuoteCalculator.calculateMarketBuySwapQuote(
const swapQuote = await swapQuoteCalculator.calculateMarketBuySwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_TAKER_ASSET,
assetBuyAmount,
slippagePercentage,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
// test if orders are correct
expect(swapQuote.orders).to.deep.equal([testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_TAKER_ASSET[0]]);
@ -468,24 +503,25 @@ describe('swapQuoteCalculator', () => {
takerAssetAmount: baseUnitAmount(0.5),
totalTakerAssetAmount: baseUnitAmount(2),
makerAssetAmount: assetBuyAmount,
protocolFeeInEthAmount: baseUnitAmount(15, 4),
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
});
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
feeTakerAssetAmount: baseUnitAmount(1.5),
takerAssetAmount: baseUnitAmount(0.5),
totalTakerAssetAmount: baseUnitAmount(2),
makerAssetAmount: assetBuyAmount,
protocolFeeInEthAmount: baseUnitAmount(15, 4),
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
});
});
it('calculates a correct swapQuote with slippage (takerAsset denominated fee orders)', () => {
it('calculates a correct swapQuote with slippage (takerAsset denominated fee orders)', async () => {
const assetBuyAmount = baseUnitAmount(5);
const slippagePercentage = 0.5;
const swapQuote = swapQuoteCalculator.calculateMarketBuySwapQuote(
const swapQuote = await swapQuoteCalculator.calculateMarketBuySwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_TAKER_ASSET,
assetBuyAmount,
slippagePercentage,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
const fiveSixthEthInWei = new BigNumber(5)
.div(new BigNumber(6))
@ -503,24 +539,25 @@ describe('swapQuoteCalculator', () => {
takerAssetAmount: fiveSixthEthInWei,
totalTakerAssetAmount: baseUnitAmount(2.5).plus(fiveSixthEthInWei),
makerAssetAmount: assetBuyAmount,
protocolFeeInEthAmount: baseUnitAmount(30, 4),
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
});
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
feeTakerAssetAmount: baseUnitAmount(2.5),
takerAssetAmount: baseUnitAmount(5.5),
totalTakerAssetAmount: baseUnitAmount(8),
makerAssetAmount: assetBuyAmount,
protocolFeeInEthAmount: baseUnitAmount(30, 4),
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
});
});
it('calculates a correct swapQuote with no slippage (makerAsset denominated fee orders)', () => {
it('calculates a correct swapQuote with no slippage (makerAsset denominated fee orders)', async () => {
const assetBuyAmount = baseUnitAmount(1);
const slippagePercentage = 0;
const swapQuote = swapQuoteCalculator.calculateMarketBuySwapQuote(
const swapQuote = await swapQuoteCalculator.calculateMarketBuySwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_MAKER_ASSET,
assetBuyAmount,
slippagePercentage,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
// test if orders are correct
expect(swapQuote.orders).to.deep.equal([testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_MAKER_ASSET[0]]);
@ -531,24 +568,25 @@ describe('swapQuoteCalculator', () => {
takerAssetAmount: baseUnitAmount(2.5),
totalTakerAssetAmount: baseUnitAmount(5),
makerAssetAmount: assetBuyAmount,
protocolFeeInEthAmount: baseUnitAmount(15, 4),
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
});
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
feeTakerAssetAmount: baseUnitAmount(2.5),
takerAssetAmount: baseUnitAmount(2.5),
totalTakerAssetAmount: baseUnitAmount(5),
makerAssetAmount: assetBuyAmount,
protocolFeeInEthAmount: baseUnitAmount(15, 4),
protocolFeeInWeiAmount: baseUnitAmount(15, 4),
});
});
it('calculates a correct swapQuote with slippage (makerAsset denominated fee orders)', () => {
it('calculates a correct swapQuote with slippage (makerAsset denominated fee orders)', async () => {
const assetBuyAmount = baseUnitAmount(2.5);
const slippagePercentage = 0.5;
const swapQuote = swapQuoteCalculator.calculateMarketBuySwapQuote(
const swapQuote = await swapQuoteCalculator.calculateMarketBuySwapQuoteAsync(
testOrders.PRUNED_SIGNED_ORDERS_FEE_IN_MAKER_ASSET,
assetBuyAmount,
slippagePercentage,
GAS_PRICE,
mockProtocolFeeUtils.object,
);
const totalTakerAssetAmount = new BigNumber(5)
.div(new BigNumber(6))
@ -567,14 +605,14 @@ describe('swapQuoteCalculator', () => {
takerAssetAmount: baseUnitAmount(2.75),
totalTakerAssetAmount: baseUnitAmount(5.5),
makerAssetAmount: assetBuyAmount,
protocolFeeInEthAmount: baseUnitAmount(30, 4),
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
});
expect(swapQuote.worstCaseQuoteInfo).to.deep.equal({
feeTakerAssetAmount: totalTakerAssetAmount.div(2),
takerAssetAmount: totalTakerAssetAmount.div(2),
totalTakerAssetAmount,
makerAssetAmount: assetBuyAmount,
protocolFeeInEthAmount: baseUnitAmount(30, 4),
protocolFeeInWeiAmount: baseUnitAmount(30, 4),
});
});
});

View File

@ -1,6 +1,5 @@
import { DevUtilsContract, WETH9Contract } from '@0x/abi-gen-wrappers';
import { ContractAddresses } from '@0x/contract-addresses';
import { DevUtilsContract } from '@0x/contracts-dev-utils';
import { WETH9Contract } from '@0x/contracts-erc20';
import { constants as devConstants, OrderFactory } from '@0x/contracts-test-utils';
import { BlockchainLifecycle, tokenUtils } from '@0x/dev-utils';
import { migrateOnceAsync } from '@0x/migrations';
@ -11,9 +10,10 @@ import 'mocha';
import { SwapQuote, SwapQuoteConsumer } from '../src';
import { constants } from '../src/constants';
import { ExtensionContractType, MarketOperation, PrunedSignedOrder } from '../src/types';
import { ProtocolFeeUtils } from '../src/utils/protocol_fee_utils';
import { chaiSetup } from './utils/chai_setup';
import { getFullyFillableSwapQuoteWithNoFees } from './utils/swap_quote';
import { getFullyFillableSwapQuoteWithNoFeesAsync } from './utils/swap_quote';
import { provider, web3Wrapper } from './utils/web3_wrapper';
chaiSetup.configure();
@ -68,6 +68,7 @@ const PARTIAL_LARGE_PRUNED_SIGNED_ORDERS: Array<Partial<PrunedSignedOrder>> = [
describe('swapQuoteConsumerUtils', () => {
let wethContract: WETH9Contract;
let protocolFeeUtils: ProtocolFeeUtils;
let userAddresses: string[];
let makerAddress: string;
let takerAddress: string;
@ -118,6 +119,7 @@ describe('swapQuoteConsumerUtils', () => {
};
const privateKey = devConstants.TESTRPC_PRIVATE_KEYS[userAddresses.indexOf(makerAddress)];
orderFactory = new OrderFactory(privateKey, defaultOrderParams);
protocolFeeUtils = new ProtocolFeeUtils();
forwarderOrderFactory = new OrderFactory(privateKey, defaultForwarderOrderParams);
swapQuoteConsumer = new SwapQuoteConsumer(provider, {
@ -173,28 +175,31 @@ describe('swapQuoteConsumerUtils', () => {
largeForwarderOrders.push(prunedOrder as PrunedSignedOrder);
}
forwarderSwapQuote = getFullyFillableSwapQuoteWithNoFees(
forwarderSwapQuote = await getFullyFillableSwapQuoteWithNoFeesAsync(
makerAssetData,
wethAssetData,
forwarderOrders,
MarketOperation.Sell,
GAS_PRICE,
protocolFeeUtils,
);
largeForwarderSwapQuote = getFullyFillableSwapQuoteWithNoFees(
largeForwarderSwapQuote = await getFullyFillableSwapQuoteWithNoFeesAsync(
makerAssetData,
wethAssetData,
largeForwarderOrders,
MarketOperation.Sell,
GAS_PRICE,
protocolFeeUtils,
);
exchangeSwapQuote = getFullyFillableSwapQuoteWithNoFees(
exchangeSwapQuote = await getFullyFillableSwapQuoteWithNoFeesAsync(
makerAssetData,
takerAssetData,
exchangeOrders,
MarketOperation.Sell,
GAS_PRICE,
protocolFeeUtils,
);
});

View File

@ -1,10 +1,17 @@
import { constants as devConstants } from '@0x/contracts-test-utils';
import { AcceptedRejectedOrders, Orderbook } from '@0x/orderbook';
import { Web3ProviderEngine } from '@0x/subproviders';
import { APIOrder, AssetPairsItem, SignedOrder } from '@0x/types';
import { BigNumber } from '@0x/utils';
import * as TypeMoq from 'typemoq';
import { SwapQuoter } from '../../src/swap_quoter';
import { PrunedSignedOrder } from '../../src/types';
import { ProtocolFeeUtils } from '../../src/utils/protocol_fee_utils';
const PROTOCOL_FEE_MULTIPLIER = 150000;
// tslint:disable: max-classes-per-file
class OrderbookClass extends Orderbook {
// tslint:disable-next-line:prefer-function-over-method
@ -49,6 +56,23 @@ const partiallyMockedSwapQuoter = (provider: Web3ProviderEngine, orderbook: Orde
return mockedSwapQuoter;
};
class ProtocolFeeUtilsClass extends ProtocolFeeUtils {
// tslint:disable-next-line:prefer-function-over-method
public async getProtocolFeeMultiplierAsync(): Promise<BigNumber> {
return Promise.resolve(new BigNumber(PROTOCOL_FEE_MULTIPLIER));
}
// tslint:disable-next-line:prefer-function-over-method
public async getGasPriceEstimationOrThrowAsync(): Promise<BigNumber> {
return Promise.resolve(new BigNumber(devConstants.DEFAULT_GAS_PRICE));
}
}
export const protocolFeeUtilsMock = (): TypeMoq.IMock<ProtocolFeeUtils> => {
const mockProtocolFeeUtils = TypeMoq.Mock.ofType(ProtocolFeeUtilsClass, TypeMoq.MockBehavior.Loose);
mockProtocolFeeUtils.callBase = true;
return mockProtocolFeeUtils;
};
const mockGetPrunedSignedOrdersAsync = (
mockedSwapQuoter: TypeMoq.IMock<SwapQuoter>,
makerAssetData: string,

View File

@ -4,15 +4,16 @@ import * as _ from 'lodash';
import { constants } from '../../src/constants';
import { MarketOperation, PrunedSignedOrder, SwapQuote } from '../../src/types';
import { protocolFeeUtils } from '../../src/utils/protocol_fee_utils';
import { ProtocolFeeUtils } from '../../src/utils/protocol_fee_utils';
export const getFullyFillableSwapQuoteWithNoFees = (
export const getFullyFillableSwapQuoteWithNoFeesAsync = async (
makerAssetData: string,
takerAssetData: string,
orders: PrunedSignedOrder[],
operation: MarketOperation,
gasPrice: BigNumber,
): SwapQuote => {
protocolFeeUtils: ProtocolFeeUtils,
): Promise<SwapQuote> => {
const makerAssetFillAmount = _.reduce(
orders,
(a: BigNumber, c: SignedOrder) => a.plus(c.makerAssetAmount),
@ -28,13 +29,14 @@ export const getFullyFillableSwapQuoteWithNoFees = (
feeTakerAssetAmount: constants.ZERO_AMOUNT,
takerAssetAmount: totalTakerAssetAmount,
totalTakerAssetAmount,
protocolFeeInEthAmount: protocolFeeUtils.calculateWorstCaseProtocolFee(orders, gasPrice),
protocolFeeInWeiAmount: await protocolFeeUtils.calculateWorstCaseProtocolFeeAsync(orders, gasPrice),
};
const quoteBase = {
makerAssetData,
takerAssetData,
orders,
gasPrice,
bestCaseQuoteInfo: quoteInfo,
worstCaseQuoteInfo: quoteInfo,
};

View File

@ -3,14 +3,14 @@ import { BigNumber } from '@0x/utils';
import { InsufficientAssetLiquidityError } from '../../src/errors';
export const testHelpers = {
expectInsufficientLiquidityError: (
expectInsufficientLiquidityErrorAsync: async (
expect: Chai.ExpectStatic,
functionWhichTriggersError: () => void,
functionWhichTriggersErrorAsync: () => Promise<void>,
expectedAmountAvailableToFill: BigNumber,
): void => {
): Promise<void> => {
let wasErrorThrown = false;
try {
functionWhichTriggersError();
await functionWhichTriggersErrorAsync();
} catch (e) {
wasErrorThrown = true;
expect(e).to.be.instanceOf(InsufficientAssetLiquidityError);

View File

@ -33,6 +33,9 @@
"@0x/tslint-config": "^3.1.0-beta.2",
"@types/lodash": "4.14.104",
"@types/mocha": "^5.2.7",
"ethereum-types": "^2.2.0-beta.2",
"@0x/types": "^2.5.0-beta.2",
"@0x/typescript-typings": "^4.4.0-beta.2",
"chai": "^4.0.1",
"make-promises-safe": "^1.1.0",
"mocha": "^6.2.0",
@ -44,11 +47,8 @@
"dependencies": {
"@0x/assert": "^2.2.0-beta.2",
"@0x/json-schemas": "^4.1.0-beta.2",
"@0x/types": "^2.5.0-beta.2",
"@0x/typescript-typings": "^4.4.0-beta.2",
"@0x/utils": "^4.6.0-beta.2",
"@0x/web3-wrapper": "^6.1.0-beta.2",
"ethereum-types": "^2.2.0-beta.2",
"ethereumjs-account": "^3.0.0",
"ethereumjs-blockstream": "^7.0.0",
"ethereumjs-util": "^5.1.1",
@ -57,6 +57,9 @@
"js-sha3": "^0.7.0",
"uuid": "^3.3.2"
},
"resolutions": {
"merkle-patricia-tree": "^2.3.2"
},
"publishConfig": {
"access": "public"
}

File diff suppressed because one or more lines are too long

View File

@ -44,18 +44,15 @@
"homepage": "https://github.com/0xProject/0x-monorepo/packages/instant/README.md",
"dependencies": {
"@0x/assert": "^2.2.0-beta.2",
"@0x/asset-buyer": "6.1.8",
"@0x/asset-swapper": "^2.1.0-beta.3",
"@0x/json-schemas": "^4.1.0-beta.2",
"@0x/order-utils": "^8.5.0-beta.3",
"@0x/subproviders": "^5.1.0-beta.2",
"@0x/types": "^2.5.0-beta.2",
"@0x/typescript-typings": "^4.4.0-beta.2",
"@0x/utils": "^4.6.0-beta.2",
"@0x/web3-wrapper": "^6.1.0-beta.2",
"babel-runtime": "^6.26.0",
"bowser": "^1.9.4",
"copy-to-clipboard": "^3.0.8",
"ethereum-types": "^2.2.0-beta.2",
"lodash": "^4.17.11",
"polished": "^1.9.2",
"react": "^16.5.2",
@ -68,6 +65,9 @@
"ts-optchain": "^0.1.1"
},
"devDependencies": {
"ethereum-types": "^2.2.0-beta.2",
"@0x/types": "^2.5.0-beta.2",
"@0x/typescript-typings": "^4.4.0-beta.2",
"@0x/tslint-config": "^3.1.0-beta.2",
"@static/discharge": "https://github.com/0xProject/discharge.git",
"@types/enzyme": "^3.1.14",

View File

@ -1,4 +1,11 @@
import { AssetBuyer, AssetBuyerError, BuyQuote } from '@0x/asset-buyer';
import {
affiliateFeeUtils,
ExtensionContractType,
MarketBuySwapQuote,
SwapQuoteConsumer,
SwapQuoteConsumerError,
SwapQuoter,
} from '@0x/asset-swapper';
import { AssetProxyId } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
@ -19,17 +26,26 @@ import { Button } from './ui/button';
export interface BuyButtonProps {
accountAddress?: string;
accountEthBalanceInWei?: BigNumber;
buyQuote?: BuyQuote;
assetBuyer: AssetBuyer;
swapQuote?: MarketBuySwapQuote;
swapQuoter: SwapQuoter;
swapQuoteConsumer: SwapQuoteConsumer;
web3Wrapper: Web3Wrapper;
affiliateInfo?: AffiliateInfo;
selectedAsset?: Asset;
onValidationPending: (buyQuote: BuyQuote) => void;
onValidationFail: (buyQuote: BuyQuote, errorMessage: AssetBuyerError | ZeroExInstantError) => void;
onSignatureDenied: (buyQuote: BuyQuote) => void;
onBuyProcessing: (buyQuote: BuyQuote, txHash: string, startTimeUnix: number, expectedEndTimeUnix: number) => void;
onBuySuccess: (buyQuote: BuyQuote, txHash: string) => void;
onBuyFailure: (buyQuote: BuyQuote, txHash: string) => void;
onValidationPending: (swapQuote: MarketBuySwapQuote) => void;
onValidationFail: (
swapQuote: MarketBuySwapQuote,
errorMessage: SwapQuoteConsumerError | ZeroExInstantError,
) => void;
onSignatureDenied: (swapQuote: MarketBuySwapQuote) => void;
onBuyProcessing: (
swapQuote: MarketBuySwapQuote,
txHash: string,
startTimeUnix: number,
expectedEndTimeUnix: number,
) => void;
onBuySuccess: (swapQuote: MarketBuySwapQuote, txHash: string) => void;
onBuyFailure: (swapQuote: MarketBuySwapQuote, txHash: string) => void;
}
export class BuyButton extends React.PureComponent<BuyButtonProps> {
@ -39,8 +55,8 @@ export class BuyButton extends React.PureComponent<BuyButtonProps> {
onBuyFailure: util.boundNoop,
};
public render(): React.ReactNode {
const { buyQuote, accountAddress, selectedAsset } = this.props;
const shouldDisableButton = buyQuote === undefined || accountAddress === undefined;
const { swapQuote, accountAddress, selectedAsset } = this.props;
const shouldDisableButton = swapQuote === undefined || accountAddress === undefined;
const buttonText =
selectedAsset !== undefined && selectedAsset.metaData.assetProxyId === AssetProxyId.ERC20
? `Buy ${selectedAsset.metaData.symbol.toUpperCase()}`
@ -58,63 +74,81 @@ export class BuyButton extends React.PureComponent<BuyButtonProps> {
}
private readonly _handleClick = async () => {
// The button is disabled when there is no buy quote anyway.
const { buyQuote, assetBuyer, affiliateInfo, accountAddress, accountEthBalanceInWei, web3Wrapper } = this.props;
if (buyQuote === undefined || accountAddress === undefined) {
const {
swapQuote,
swapQuoteConsumer,
affiliateInfo,
accountAddress,
accountEthBalanceInWei,
web3Wrapper,
} = this.props;
if (swapQuote === undefined || accountAddress === undefined) {
return;
}
this.props.onValidationPending(buyQuote);
const ethNeededForBuy = buyQuote.worstCaseQuoteInfo.totalEthAmount;
this.props.onValidationPending(swapQuote);
const ethNeededForBuy = affiliateFeeUtils.getTotalEthAmountWithAffiliateFee(swapQuote.worstCaseQuoteInfo, 0);
// if we don't have a balance for the user, let the transaction through, it will be handled by the wallet
const hasSufficientEth = accountEthBalanceInWei === undefined || accountEthBalanceInWei.gte(ethNeededForBuy);
if (!hasSufficientEth) {
analytics.trackBuyNotEnoughEth(buyQuote);
this.props.onValidationFail(buyQuote, ZeroExInstantError.InsufficientETH);
analytics.trackBuyNotEnoughEth(swapQuote);
this.props.onValidationFail(swapQuote, ZeroExInstantError.InsufficientETH);
return;
}
let txHash: string | undefined;
const gasInfo = await gasPriceEstimator.getGasInfoAsync();
const feeRecipient = oc(affiliateInfo).feeRecipient();
const feePercentage = oc(affiliateInfo).feePercentage();
try {
analytics.trackBuyStarted(buyQuote);
txHash = await assetBuyer.executeBuyQuoteAsync(buyQuote, {
feeRecipient,
analytics.trackBuyStarted(swapQuote);
txHash = await swapQuoteConsumer.executeSwapQuoteOrThrowAsync(swapQuote, {
useExtensionContract: ExtensionContractType.Forwarder,
extensionContractOpts: {
feeRecipient,
feePercentage,
},
takerAddress: accountAddress,
gasPrice: gasInfo.gasPriceInWei,
});
} catch (e) {
if (e instanceof Error) {
if (e.message === AssetBuyerError.TransactionValueTooLow) {
analytics.trackBuySimulationFailed(buyQuote);
this.props.onValidationFail(buyQuote, AssetBuyerError.TransactionValueTooLow);
if (e.message === SwapQuoteConsumerError.TransactionValueTooLow) {
analytics.trackBuySimulationFailed(swapQuote);
this.props.onValidationFail(swapQuote, SwapQuoteConsumerError.TransactionValueTooLow);
return;
} else if (e.message === AssetBuyerError.SignatureRequestDenied) {
analytics.trackBuySignatureDenied(buyQuote);
this.props.onSignatureDenied(buyQuote);
} else if (e.message === SwapQuoteConsumerError.SignatureRequestDenied) {
analytics.trackBuySignatureDenied(swapQuote);
this.props.onSignatureDenied(swapQuote);
return;
} else {
errorReporter.report(e);
analytics.trackBuyUnknownError(buyQuote, e.message);
this.props.onValidationFail(buyQuote, ZeroExInstantError.CouldNotSubmitTransaction);
analytics.trackBuyUnknownError(swapQuote, e.message);
this.props.onValidationFail(swapQuote, ZeroExInstantError.CouldNotSubmitTransaction);
return;
}
}
// HACK(dekz): Wrappers no longer include decorators which map errors
// like transaction deny
if (e.message && e.message.includes('User denied transaction signature')) {
analytics.trackBuySignatureDenied(swapQuote);
this.props.onSignatureDenied(swapQuote);
return;
}
throw e;
}
const startTimeUnix = new Date().getTime();
const expectedEndTimeUnix = startTimeUnix + gasInfo.estimatedTimeMs;
this.props.onBuyProcessing(buyQuote, txHash, startTimeUnix, expectedEndTimeUnix);
this.props.onBuyProcessing(swapQuote, txHash, startTimeUnix, expectedEndTimeUnix);
try {
analytics.trackBuyTxSubmitted(buyQuote, txHash, startTimeUnix, expectedEndTimeUnix);
analytics.trackBuyTxSubmitted(swapQuote, txHash, startTimeUnix, expectedEndTimeUnix);
await web3Wrapper.awaitTransactionSuccessAsync(txHash);
} catch (e) {
if (e instanceof Error && e.message.startsWith(WEB_3_WRAPPER_TRANSACTION_FAILED_ERROR_MSG_PREFIX)) {
analytics.trackBuyTxFailed(buyQuote, txHash, startTimeUnix, expectedEndTimeUnix);
this.props.onBuyFailure(buyQuote, txHash);
analytics.trackBuyTxFailed(swapQuote, txHash, startTimeUnix, expectedEndTimeUnix);
this.props.onBuyFailure(swapQuote, txHash);
return;
}
throw e;
}
analytics.trackBuyTxSucceeded(buyQuote, txHash, startTimeUnix, expectedEndTimeUnix);
this.props.onBuySuccess(buyQuote, txHash);
analytics.trackBuyTxSucceeded(swapQuote, txHash, startTimeUnix, expectedEndTimeUnix);
this.props.onBuySuccess(swapQuote, txHash);
};
}

View File

@ -7,18 +7,18 @@ import { Container } from '../components/ui/container';
import { OrderProcessState, OrderState } from '../types';
export interface BuyOrderProgressProps {
buyOrderState: OrderState;
swapOrderState: OrderState;
}
export const BuyOrderProgress: React.StatelessComponent<BuyOrderProgressProps> = props => {
const { buyOrderState } = props;
const { swapOrderState } = props;
if (
buyOrderState.processState === OrderProcessState.Processing ||
buyOrderState.processState === OrderProcessState.Success ||
buyOrderState.processState === OrderProcessState.Failure
swapOrderState.processState === OrderProcessState.Processing ||
swapOrderState.processState === OrderProcessState.Success ||
swapOrderState.processState === OrderProcessState.Failure
) {
const progress = buyOrderState.progress;
const hasEnded = buyOrderState.processState !== OrderProcessState.Processing;
const progress = swapOrderState.progress;
const hasEnded = swapOrderState.processState !== OrderProcessState.Processing;
const expectedTimeMs = progress.expectedEndTimeUnix - progress.startTimeUnix;
return (
<Container width="100%" padding="20px 20px 0px 20px">

View File

@ -1,4 +1,4 @@
import { AssetBuyer, AssetBuyerError, BuyQuote } from '@0x/asset-buyer';
import { MarketBuySwapQuote, SwapQuoteConsumer, SwapQuoteConsumerError, SwapQuoter } from '@0x/asset-swapper';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import * as React from 'react';
@ -16,24 +16,33 @@ import { Flex } from './ui/flex';
export interface BuyOrderStateButtonProps {
accountAddress?: string;
accountEthBalanceInWei?: BigNumber;
buyQuote?: BuyQuote;
buyOrderProcessingState: OrderProcessState;
assetBuyer: AssetBuyer;
swapQuote?: MarketBuySwapQuote;
swapOrderProcessingState: OrderProcessState;
swapQuoter: SwapQuoter;
swapQuoteConsumer: SwapQuoteConsumer;
web3Wrapper: Web3Wrapper;
affiliateInfo?: AffiliateInfo;
selectedAsset?: Asset;
onViewTransaction: () => void;
onValidationPending: (buyQuote: BuyQuote) => void;
onValidationFail: (buyQuote: BuyQuote, errorMessage: AssetBuyerError | ZeroExInstantError) => void;
onSignatureDenied: (buyQuote: BuyQuote) => void;
onBuyProcessing: (buyQuote: BuyQuote, txHash: string, startTimeUnix: number, expectedEndTimeUnix: number) => void;
onBuySuccess: (buyQuote: BuyQuote, txHash: string) => void;
onBuyFailure: (buyQuote: BuyQuote, txHash: string) => void;
onValidationPending: (swapQuote: MarketBuySwapQuote) => void;
onValidationFail: (
swapQuote: MarketBuySwapQuote,
errorMessage: SwapQuoteConsumerError | ZeroExInstantError,
) => void;
onSignatureDenied: (swapQuote: MarketBuySwapQuote) => void;
onBuyProcessing: (
swapQuote: MarketBuySwapQuote,
txHash: string,
startTimeUnix: number,
expectedEndTimeUnix: number,
) => void;
onBuySuccess: (swapQuote: MarketBuySwapQuote, txHash: string) => void;
onBuyFailure: (swapQuote: MarketBuySwapQuote, txHash: string) => void;
onRetry: () => void;
}
export const BuyOrderStateButtons: React.StatelessComponent<BuyOrderStateButtonProps> = props => {
if (props.buyOrderProcessingState === OrderProcessState.Failure) {
if (props.swapOrderProcessingState === OrderProcessState.Failure) {
return (
<Flex justify="space-between">
<Button width="48%" onClick={props.onRetry} fontColor={ColorOption.white}>
@ -45,11 +54,11 @@ export const BuyOrderStateButtons: React.StatelessComponent<BuyOrderStateButtonP
</Flex>
);
} else if (
props.buyOrderProcessingState === OrderProcessState.Success ||
props.buyOrderProcessingState === OrderProcessState.Processing
props.swapOrderProcessingState === OrderProcessState.Success ||
props.swapOrderProcessingState === OrderProcessState.Processing
) {
return <SecondaryButton onClick={props.onViewTransaction}>View Transaction</SecondaryButton>;
} else if (props.buyOrderProcessingState === OrderProcessState.Validating) {
} else if (props.swapOrderProcessingState === OrderProcessState.Validating) {
return <PlacingOrderButton />;
}
@ -57,8 +66,9 @@ export const BuyOrderStateButtons: React.StatelessComponent<BuyOrderStateButtonP
<BuyButton
accountAddress={props.accountAddress}
accountEthBalanceInWei={props.accountEthBalanceInWei}
buyQuote={props.buyQuote}
assetBuyer={props.assetBuyer}
swapQuote={props.swapQuote}
swapQuoter={props.swapQuoter}
swapQuoteConsumer={props.swapQuoteConsumer}
web3Wrapper={props.web3Wrapper}
affiliateInfo={props.affiliateInfo}
selectedAsset={props.selectedAsset}

View File

@ -147,7 +147,7 @@ interface TokenSelectorRowIconProps {
const getTokenIcon = (symbol: string): React.StatelessComponent | undefined => {
try {
return require(`../assets/icons/${symbol}.svg`) as React.StatelessComponent;
return require(`../assets/icons/${symbol}.svg`).default as React.StatelessComponent;
} catch (e) {
// Can't find icon
return undefined;

View File

@ -22,7 +22,7 @@ export interface InstantHeadingProps {
totalEthBaseUnitAmount?: BigNumber;
ethUsdPrice?: BigNumber;
quoteRequestState: AsyncProcessState;
buyOrderState: OrderState;
swapOrderState: OrderState;
onSelectAssetClick?: (asset?: ERC20Asset) => void;
}
@ -127,7 +127,7 @@ export class InstantHeading extends React.PureComponent<InstantHeadingProps, {}>
}
private _renderIcon(): React.ReactNode {
const processState = this.props.buyOrderState.processState;
const processState = this.props.swapOrderState.processState;
if (processState === OrderProcessState.Failure) {
return <Icon icon="failed" width={ICON_WIDTH} height={ICON_HEIGHT} color={ICON_COLOR} />;
@ -140,7 +140,7 @@ export class InstantHeading extends React.PureComponent<InstantHeadingProps, {}>
}
private _renderTopText(): React.ReactNode {
const processState = this.props.buyOrderState.processState;
const processState = this.props.swapOrderState.processState;
if (processState === OrderProcessState.Failure) {
return 'Order failed';
} else if (processState === OrderProcessState.Processing) {

View File

@ -1,4 +1,4 @@
import { BuyQuoteInfo } from '@0x/asset-buyer';
import { SwapQuoteInfo } from '@0x/asset-swapper';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
import * as React from 'react';
@ -17,7 +17,7 @@ import { Flex } from './ui/flex';
import { Text, TextProps } from './ui/text';
export interface OrderDetailsProps {
buyQuoteInfo?: BuyQuoteInfo;
swapQuoteInfo?: SwapQuoteInfo;
selectedAssetUnitAmount?: BigNumber;
ethUsdPrice?: BigNumber;
isLoading: boolean;
@ -37,22 +37,27 @@ export class OrderDetails extends React.PureComponent<OrderDetailsProps> {
);
}
// TODO(dave4506) currently instant has affiliate fees disabled, until we resolve that, this displays only takerFees + protocolFees
private _renderRows(): React.ReactNode {
const { buyQuoteInfo } = this.props;
const { swapQuoteInfo } = this.props;
return (
<React.Fragment>
<OrderDetailsRow
labelText={this._assetAmountLabel()}
primaryValue={this._displayAmountOrPlaceholder(buyQuoteInfo && buyQuoteInfo.assetEthAmount)}
primaryValue={this._displayAmountOrPlaceholder(swapQuoteInfo && swapQuoteInfo.takerAssetAmount)}
/>
<OrderDetailsRow
labelText="Fee"
primaryValue={this._displayAmountOrPlaceholder(buyQuoteInfo && buyQuoteInfo.feeEthAmount)}
primaryValue={this._displayAmountOrPlaceholder(
swapQuoteInfo && swapQuoteInfo.feeTakerAssetAmount.plus(swapQuoteInfo.protocolFeeInWeiAmount),
)}
/>
<OrderDetailsRow
labelText="Total Cost"
isLabelBold={true}
primaryValue={this._displayAmountOrPlaceholder(buyQuoteInfo && buyQuoteInfo.totalEthAmount)}
primaryValue={this._displayAmountOrPlaceholder(
swapQuoteInfo && swapQuoteInfo.totalTakerAssetAmount.plus(swapQuoteInfo.protocolFeeInWeiAmount),
)}
isPrimaryValueBold={true}
secondaryValue={this._totalCostSecondaryValue()}
/>
@ -87,8 +92,11 @@ export class OrderDetails extends React.PureComponent<OrderDetailsProps> {
secondaryCurrency === BaseCurrency.ETH ||
(secondaryCurrency === BaseCurrency.USD && this.props.ethUsdPrice && !this._hadErrorFetchingUsdPrice());
if (this.props.buyQuoteInfo && canDisplayCurrency) {
return this._displayAmount(secondaryCurrency, this.props.buyQuoteInfo.totalEthAmount);
if (this.props.swapQuoteInfo && canDisplayCurrency) {
return this._displayAmount(
secondaryCurrency,
this.props.swapQuoteInfo.totalTakerAssetAmount.plus(this.props.swapQuoteInfo.protocolFeeInWeiAmount),
);
} else {
return undefined;
}
@ -150,8 +158,8 @@ export class OrderDetails extends React.PureComponent<OrderDetailsProps> {
}
private _pricePerTokenWei(): BigNumber | undefined {
const buyQuoteAccessor = oc(this.props.buyQuoteInfo);
const assetTotalInWei = buyQuoteAccessor.assetEthAmount();
const swapQuoteAccessor = oc(this.props.swapQuoteInfo);
const assetTotalInWei = swapQuoteAccessor.totalTakerAssetAmount();
const selectedAssetUnitAmount = this.props.selectedAssetUnitAmount;
return assetTotalInWei !== undefined &&
selectedAssetUnitAmount !== undefined &&

View File

@ -4,7 +4,7 @@ import * as _ from 'lodash';
import * as React from 'react';
import { Provider as ReduxProvider } from 'react-redux';
import { ACCOUNT_UPDATE_INTERVAL_TIME_MS, BUY_QUOTE_UPDATE_INTERVAL_TIME_MS } from '../constants';
import { ACCOUNT_UPDATE_INTERVAL_TIME_MS, SWAP_QUOTE_UPDATE_INTERVAL_TIME_MS } from '../constants';
import { SelectedAssetThemeProvider } from '../containers/selected_asset_theme_provider';
import { asyncData } from '../redux/async_data';
import { DEFAULT_STATE, DefaultState, State } from '../redux/reducer';
@ -17,7 +17,7 @@ import { errorFlasher } from '../util/error_flasher';
import { setupRollbar } from '../util/error_reporter';
import { gasPriceEstimator } from '../util/gas_price_estimator';
import { Heartbeater } from '../util/heartbeater';
import { generateAccountHeartbeater, generateBuyQuoteHeartbeater } from '../util/heartbeater_factory';
import { generateAccountHeartbeater, generateSwapQuoteHeartbeater } from '../util/heartbeater_factory';
import { providerStateFactory } from '../util/provider_state_factory';
export type ZeroExInstantProviderProps = ZeroExInstantBaseConfig;
@ -25,7 +25,7 @@ export type ZeroExInstantProviderProps = ZeroExInstantBaseConfig;
export class ZeroExInstantProvider extends React.PureComponent<ZeroExInstantProviderProps> {
private readonly _store: Store;
private _accountUpdateHeartbeat?: Heartbeater;
private _buyQuoteHeartbeat?: Heartbeater;
private _swapQuoteHeartbeat?: Heartbeater;
// TODO(fragosti): Write tests for this beast once we inject a provider.
private static _mergeDefaultStateWithProps(
@ -84,8 +84,8 @@ export class ZeroExInstantProvider extends React.PureComponent<ZeroExInstantProv
networkId,
),
assetMetaDataMap: completeAssetMetaDataMap,
affiliateInfo: props.affiliateInfo,
onSuccess: props.onSuccess,
affiliateInfo: props.affiliateInfo,
};
return storeStateFromProps;
}
@ -114,14 +114,14 @@ export class ZeroExInstantProvider extends React.PureComponent<ZeroExInstantProv
this._accountUpdateHeartbeat.start(ACCOUNT_UPDATE_INTERVAL_TIME_MS);
}
this._buyQuoteHeartbeat = generateBuyQuoteHeartbeater({
this._swapQuoteHeartbeat = generateSwapQuoteHeartbeater({
store: this._store,
shouldPerformImmediatelyOnStart: false,
});
this._buyQuoteHeartbeat.start(BUY_QUOTE_UPDATE_INTERVAL_TIME_MS);
this._swapQuoteHeartbeat.start(SWAP_QUOTE_UPDATE_INTERVAL_TIME_MS);
// Trigger first buyquote fetch
// tslint:disable-next-line:no-floating-promises
asyncData.fetchCurrentBuyQuoteAndDispatchToStore(state, dispatch, QuoteFetchOrigin.Manual, {
asyncData.fetchCurrentSwapQuoteAndDispatchToStore(state, dispatch, QuoteFetchOrigin.Manual, {
updateSilently: false,
});
// warm up the gas price estimator cache just in case we can't
@ -150,8 +150,8 @@ export class ZeroExInstantProvider extends React.PureComponent<ZeroExInstantProv
if (this._accountUpdateHeartbeat) {
this._accountUpdateHeartbeat.stop();
}
if (this._buyQuoteHeartbeat) {
this._buyQuoteHeartbeat.stop();
if (this._swapQuoteHeartbeat) {
this._swapQuoteHeartbeat.stop();
}
}
public render(): React.ReactNode {

View File

@ -16,12 +16,12 @@ export const ONE_SECOND_MS = 1000;
export const ONE_MINUTE_MS = ONE_SECOND_MS * 60;
export const GIT_SHA = process.env.GIT_SHA;
export const NODE_ENV = process.env.NODE_ENV;
export const ERC20_BUY_QUOTE_SLIPPAGE_PERCENTAGE = 0.2;
export const ERC721_BUY_QUOTE_SLIPPAGE_PERCENTAGE = 0;
export const ERC20_SWAP_QUOTE_SLIPPAGE_PERCENTAGE = 0.2;
export const ERC721_SWAP_QUOTE_SLIPPAGE_PERCENTAGE = 0;
export const NPM_PACKAGE_VERSION = process.env.NPM_PACKAGE_VERSION;
export const DEFAULT_UNKOWN_ASSET_NAME = '???';
export const ACCOUNT_UPDATE_INTERVAL_TIME_MS = ONE_SECOND_MS * 5;
export const BUY_QUOTE_UPDATE_INTERVAL_TIME_MS = ONE_SECOND_MS * 15;
export const SWAP_QUOTE_UPDATE_INTERVAL_TIME_MS = ONE_SECOND_MS * 15;
export const DEFAULT_GAS_PRICE = GWEI_IN_WEI.multipliedBy(6);
export const DEFAULT_ESTIMATED_TRANSACTION_TIME_MS = ONE_MINUTE_MS * 2;
export const MAGIC_TRIGGER_ERROR_INPUT = '0€';

View File

@ -28,7 +28,7 @@ interface ConnectedState extends BuyOrderProgressOrPaymentMethodProps {}
export interface ConnectedBuyOrderProgressOrPaymentMethodProps {}
const mapStateToProps = (state: State, _ownProps: ConnectedBuyOrderProgressOrPaymentMethodProps): ConnectedState => ({
orderProcessState: state.buyOrderState.processState,
orderProcessState: state.swapOrderState.processState,
});
export const ConnectedBuyOrderProgressOrPaymentMethod: React.ComponentClass<
ConnectedBuyOrderProgressOrPaymentMethodProps

View File

@ -16,7 +16,7 @@ type DispatchProperties = 'onBaseCurrencySwitchEth' | 'onBaseCurrencySwitchUsd';
interface ConnectedState extends Omit<OrderDetailsProps, DispatchProperties> {}
const mapStateToProps = (state: State, _ownProps: LatestBuyQuoteOrderDetailsProps): ConnectedState => ({
// use the worst case quote info
buyQuoteInfo: oc(state).latestBuyQuote.worstCaseQuoteInfo(),
swapQuoteInfo: oc(state).latestSwapQuote.worstCaseQuoteInfo(),
selectedAssetUnitAmount: state.selectedAssetUnitAmount,
ethUsdPrice: state.ethUsdPrice,
isLoading: state.quoteRequestState === AsyncProcessState.Pending,

View File

@ -5,9 +5,9 @@ import { State } from '../redux/reducer';
import { OrderState } from '../types';
interface ConnectedState {
buyOrderState: OrderState;
swapOrderState: OrderState;
}
const mapStateToProps = (state: State, _ownProps: {}): ConnectedState => ({
buyOrderState: state.buyOrderState,
swapOrderState: state.swapOrderState,
});
export const SelectedAssetBuyOrderProgress = connect(mapStateToProps)(BuyOrderProgress);

View File

@ -1,4 +1,4 @@
import { AssetBuyer, AssetBuyerError, BuyQuote } from '@0x/asset-buyer';
import { MarketBuySwapQuote, SwapQuoteConsumer, SwapQuoteConsumerError, SwapQuoter } from '@0x/asset-swapper';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import * as _ from 'lodash';
@ -16,9 +16,10 @@ import { etherscanUtil } from '../util/etherscan';
interface ConnectedState {
accountAddress?: string;
accountEthBalanceInWei?: BigNumber;
buyQuote?: BuyQuote;
buyOrderProcessingState: OrderProcessState;
assetBuyer: AssetBuyer;
swapQuote?: MarketBuySwapQuote;
swapOrderProcessingState: OrderProcessState;
swapQuoter: SwapQuoter;
swapQuoteConsumer: SwapQuoteConsumer;
web3Wrapper: Web3Wrapper;
affiliateInfo?: AffiliateInfo;
selectedAsset?: Asset;
@ -26,18 +27,29 @@ interface ConnectedState {
onSuccess?: (txHash: string) => void;
}
// TODO(dave4506) expand errors and failures to be richer + other errors introducted in v3 of the protocol
interface ConnectedDispatch {
onValidationPending: (buyQuote: BuyQuote) => void;
onSignatureDenied: (buyQuote: BuyQuote) => void;
onBuyProcessing: (buyQuote: BuyQuote, txHash: string, startTimeUnix: number, expectedEndTimeUnix: number) => void;
onBuySuccess: (buyQuote: BuyQuote, txHash: string) => void;
onBuyFailure: (buyQuote: BuyQuote, txHash: string) => void;
onValidationPending: (swapQuote: MarketBuySwapQuote) => void;
onSignatureDenied: (swapQuote: MarketBuySwapQuote) => void;
onBuyProcessing: (
swapQuote: MarketBuySwapQuote,
txHash: string,
startTimeUnix: number,
expectedEndTimeUnix: number,
) => void;
onBuySuccess: (swapQuote: MarketBuySwapQuote, txHash: string) => void;
onBuyFailure: (swapQuote: MarketBuySwapQuote, txHash: string) => void;
onRetry: () => void;
onValidationFail: (buyQuote: BuyQuote, errorMessage: AssetBuyerError | ZeroExInstantError) => void;
onValidationFail: (
swapQuote: MarketBuySwapQuote,
errorMessage: SwapQuoteConsumerError | ZeroExInstantError,
) => void;
}
export interface SelectedAssetBuyOrderStateButtons {}
const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyOrderStateButtons): ConnectedState => {
const assetBuyer = state.providerState.assetBuyer;
const swapQuoter = state.providerState.swapQuoter;
const swapQuoteConsumer = state.providerState.swapQuoteConsumer;
const chainId = swapQuoteConsumer.chainId;
const web3Wrapper = state.providerState.web3Wrapper;
const account = state.providerState.account;
const accountAddress = account.state === AccountState.Ready ? account.address : undefined;
@ -46,25 +58,23 @@ const mapStateToProps = (state: State, _ownProps: SelectedAssetBuyOrderStateButt
return {
accountAddress,
accountEthBalanceInWei,
buyOrderProcessingState: state.buyOrderState.processState,
assetBuyer,
swapOrderProcessingState: state.swapOrderState.processState,
swapQuoter,
swapQuoteConsumer,
web3Wrapper,
buyQuote: state.latestBuyQuote,
swapQuote: state.latestSwapQuote,
affiliateInfo: state.affiliateInfo,
selectedAsset,
onSuccess: state.onSuccess,
onViewTransaction: () => {
if (
state.buyOrderState.processState === OrderProcessState.Processing ||
state.buyOrderState.processState === OrderProcessState.Success ||
state.buyOrderState.processState === OrderProcessState.Failure
state.swapOrderState.processState === OrderProcessState.Processing ||
state.swapOrderState.processState === OrderProcessState.Success ||
state.swapOrderState.processState === OrderProcessState.Failure
) {
const etherscanUrl = etherscanUtil.getEtherScanTxnAddressIfExists(
state.buyOrderState.txHash,
assetBuyer.networkId,
);
const etherscanUrl = etherscanUtil.getEtherScanTxnAddressIfExists(state.swapOrderState.txHash, chainId);
if (etherscanUrl) {
analytics.trackTransactionViewed(state.buyOrderState.processState);
analytics.trackTransactionViewed(state.swapOrderState.processState);
window.open(etherscanUrl, '_blank');
return;
@ -78,21 +88,26 @@ const mapDispatchToProps = (
dispatch: Dispatch<Action>,
ownProps: SelectedAssetBuyOrderStateButtons,
): ConnectedDispatch => ({
onValidationPending: (buyQuote: BuyQuote) => {
dispatch(actions.setBuyOrderStateValidating());
onValidationPending: (swapQuote: MarketBuySwapQuote) => {
dispatch(actions.setSwapOrderStateValidating());
},
onBuyProcessing: (buyQuote: BuyQuote, txHash: string, startTimeUnix: number, expectedEndTimeUnix: number) => {
dispatch(actions.setBuyOrderStateProcessing(txHash, startTimeUnix, expectedEndTimeUnix));
onBuyProcessing: (
swapQuote: MarketBuySwapQuote,
txHash: string,
startTimeUnix: number,
expectedEndTimeUnix: number,
) => {
dispatch(actions.setSwapOrderStateProcessing(txHash, startTimeUnix, expectedEndTimeUnix));
},
onBuySuccess: (buyQuote: BuyQuote, txHash: string) => dispatch(actions.setBuyOrderStateSuccess(txHash)),
onBuyFailure: (buyQuote: BuyQuote, txHash: string) => dispatch(actions.setBuyOrderStateFailure(txHash)),
onBuySuccess: (swapQuote: MarketBuySwapQuote, txHash: string) => dispatch(actions.setSwapOrderStateSuccess(txHash)),
onBuyFailure: (swapQuote: MarketBuySwapQuote, txHash: string) => dispatch(actions.setSwapOrderStateFailure(txHash)),
onSignatureDenied: () => {
dispatch(actions.resetAmount());
const errorMessage = 'You denied this transaction';
errorFlasher.flashNewErrorMessage(dispatch, errorMessage);
},
onValidationFail: (buyQuote, error) => {
dispatch(actions.setBuyOrderStateNone());
onValidationFail: (swapQuote, error) => {
dispatch(actions.setSwapOrderStateNone());
if (error === ZeroExInstantError.InsufficientETH) {
const errorMessage = "You don't have enough ETH";
errorFlasher.flashNewErrorMessage(dispatch, errorMessage);
@ -117,8 +132,8 @@ const mergeProps = (
...ownProps,
...connectedState,
...connectedDispatch,
onBuySuccess: (buyQuote: BuyQuote, txHash: string) => {
connectedDispatch.onBuySuccess(buyQuote, txHash);
onBuySuccess: (swapQuote: MarketBuySwapQuote, txHash: string) => {
connectedDispatch.onBuySuccess(swapQuote, txHash);
if (connectedState.onSuccess) {
connectedState.onSuccess(txHash);
}

View File

@ -19,16 +19,16 @@ interface ConnectedState {
totalEthBaseUnitAmount?: BigNumber;
ethUsdPrice?: BigNumber;
quoteRequestState: AsyncProcessState;
buyOrderState: OrderState;
swapOrderState: OrderState;
}
const mapStateToProps = (state: State, _ownProps: InstantHeadingProps): ConnectedState => ({
selectedAsset: state.selectedAsset,
selectedAssetUnitAmount: state.selectedAssetUnitAmount,
totalEthBaseUnitAmount: oc(state).latestBuyQuote.worstCaseQuoteInfo.totalEthAmount(),
totalEthBaseUnitAmount: oc(state).latestSwapQuote.worstCaseQuoteInfo.totalTakerAssetAmount(),
ethUsdPrice: state.ethUsdPrice,
quoteRequestState: state.quoteRequestState,
buyOrderState: state.buyOrderState,
swapOrderState: state.swapOrderState,
});
export const SelectedAssetInstantHeading: React.ComponentClass<InstantHeadingProps> = connect(mapStateToProps)(

View File

@ -1,4 +1,4 @@
import { AssetBuyer } from '@0x/asset-buyer';
import { SwapQuoter } from '@0x/asset-swapper';
import { AssetProxyId } from '@0x/types';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
@ -10,8 +10,8 @@ import { ERC20AssetAmountInput, ERC20AssetAmountInputProps } from '../components
import { Action, actions } from '../redux/actions';
import { State } from '../redux/reducer';
import { ColorOption } from '../style/theme';
import { AffiliateInfo, ERC20Asset, Omit, OrderProcessState, QuoteFetchOrigin } from '../types';
import { buyQuoteUpdater } from '../util/buy_quote_updater';
import { ERC20Asset, Omit, OrderProcessState, QuoteFetchOrigin } from '../types';
import { swapQuoteUpdater } from '../util/swap_quote_updater';
export interface SelectedERC20AssetAmountInputProps {
fontColor?: ColorOption;
@ -20,22 +20,16 @@ export interface SelectedERC20AssetAmountInputProps {
}
interface ConnectedState {
assetBuyer: AssetBuyer;
swapQuoter: SwapQuoter;
value?: BigNumber;
asset?: ERC20Asset;
isInputDisabled: boolean;
numberOfAssetsAvailable?: number;
affiliateInfo?: AffiliateInfo;
canSelectOtherAsset: boolean;
}
interface ConnectedDispatch {
updateBuyQuote: (
assetBuyer: AssetBuyer,
value?: BigNumber,
asset?: ERC20Asset,
affiliateInfo?: AffiliateInfo,
) => void;
updateSwapQuote: (swapQuoter: SwapQuoter, value?: BigNumber, asset?: ERC20Asset) => void;
}
type ConnectedProps = Omit<ERC20AssetAmountInputProps, keyof SelectedERC20AssetAmountInputProps>;
@ -43,7 +37,7 @@ type ConnectedProps = Omit<ERC20AssetAmountInputProps, keyof SelectedERC20AssetA
type FinalProps = ConnectedProps & SelectedERC20AssetAmountInputProps;
const mapStateToProps = (state: State, _ownProps: SelectedERC20AssetAmountInputProps): ConnectedState => {
const processState = state.buyOrderState.processState;
const processState = state.swapOrderState.processState;
const isInputEnabled = processState === OrderProcessState.None || processState === OrderProcessState.Failure;
const isInputDisabled = !isInputEnabled;
const selectedAsset =
@ -56,42 +50,40 @@ const mapStateToProps = (state: State, _ownProps: SelectedERC20AssetAmountInputP
? isInputEnabled || processState === OrderProcessState.Success
: false;
const assetBuyer = state.providerState.assetBuyer;
const swapQuoter = state.providerState.swapQuoter;
return {
assetBuyer,
swapQuoter,
value: state.selectedAssetUnitAmount,
asset: selectedAsset,
isInputDisabled,
numberOfAssetsAvailable,
affiliateInfo: state.affiliateInfo,
canSelectOtherAsset,
};
};
const debouncedUpdateBuyQuoteAsync = _.debounce(buyQuoteUpdater.updateBuyQuoteAsync.bind(buyQuoteUpdater), 200, {
const debouncedUpdateSwapQuoteAsync = _.debounce(swapQuoteUpdater.updateSwapQuoteAsync.bind(swapQuoteUpdater), 200, {
trailing: true,
}) as typeof buyQuoteUpdater.updateBuyQuoteAsync;
}) as typeof swapQuoteUpdater.updateSwapQuoteAsync;
const mapDispatchToProps = (
dispatch: Dispatch<Action>,
_ownProps: SelectedERC20AssetAmountInputProps,
): ConnectedDispatch => ({
updateBuyQuote: (assetBuyer, value, asset, affiliateInfo) => {
updateSwapQuote: (swapQuoter, value, asset) => {
// Update the input
dispatch(actions.updateSelectedAssetAmount(value));
// invalidate the last buy quote.
dispatch(actions.updateLatestBuyQuote(undefined));
// reset our buy state
dispatch(actions.setBuyOrderStateNone());
// invalidate the last swap quote.
dispatch(actions.updateLatestSwapQuote(undefined));
// reset our swap state
dispatch(actions.setSwapOrderStateNone());
if (value !== undefined && value.isGreaterThan(0) && asset !== undefined) {
// even if it's debounced, give them the illusion it's loading
dispatch(actions.setQuoteRequestStatePending());
// tslint:disable-next-line:no-floating-promises
debouncedUpdateBuyQuoteAsync(assetBuyer, dispatch, asset, value, QuoteFetchOrigin.Manual, {
debouncedUpdateSwapQuoteAsync(swapQuoter, dispatch, asset, value, QuoteFetchOrigin.Manual, {
setPending: true,
dispatchErrors: true,
affiliateInfo,
});
}
},
@ -107,7 +99,7 @@ const mergeProps = (
asset: connectedState.asset,
value: connectedState.value,
onChange: (value, asset) => {
connectedDispatch.updateBuyQuote(connectedState.assetBuyer, value, asset, connectedState.affiliateInfo);
connectedDispatch.updateSwapQuote(connectedState.swapQuoter, value, asset);
},
isInputDisabled: connectedState.isInputDisabled,
numberOfAssetsAvailable: connectedState.numberOfAssetsAvailable,

View File

@ -1,4 +1,4 @@
import { AssetBuyer, BigNumber } from '@0x/asset-buyer';
import { BigNumber, SwapQuoter } from '@0x/asset-swapper';
import { assetDataUtils } from '@0x/order-utils';
import { AssetProxyId } from '@0x/types';
import { providerUtils } from '@0x/utils';
@ -172,30 +172,31 @@ export const hasMetaDataForAssetData = (assetData: string): boolean => {
};
export const hasLiquidityForAssetDataAsync = async (
assetData: string,
takerAssetData: string,
orderSource: OrderSource,
networkId: Network = Network.Mainnet,
chainId: Network = Network.Mainnet,
supportedProvider?: SupportedProvider,
): Promise<boolean> => {
assert.isHexString('assetData', assetData);
assert.isHexString('takerAssetData', takerAssetData);
assert.isValidOrderSource('orderSource', orderSource);
assert.isNumber('networkId', networkId);
assert.isNumber('chainId', chainId);
let provider = supportedProvider;
if (provider !== undefined) {
provider = providerUtils.standardizeOrThrow(provider);
}
const bestProvider: ZeroExProvider = provider || providerFactory.getFallbackNoSigningProvider(networkId);
const bestProvider: ZeroExProvider = provider || providerFactory.getFallbackNoSigningProvider(chainId);
const assetBuyerOptions = { networkId };
const swapQuoterOptions = { chainId };
const assetBuyer = _.isString(orderSource)
? AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(bestProvider, orderSource, assetBuyerOptions)
: AssetBuyer.getAssetBuyerForProvidedOrders(bestProvider, orderSource, assetBuyerOptions);
const swapQuoter = _.isString(orderSource)
? SwapQuoter.getSwapQuoterForStandardRelayerAPIUrl(bestProvider, orderSource, swapQuoterOptions)
: SwapQuoter.getSwapQuoterForProvidedOrders(bestProvider, orderSource, swapQuoterOptions);
const liquidity = await assetBuyer.getLiquidityForAssetDataAsync(assetData);
return liquidity.ethValueAvailableInWei.gt(new BigNumber(0));
const wethAssetData = await swapQuoter.getEtherTokenAssetDataOrThrowAsync();
const liquidity = await swapQuoter.getLiquidityForMakerTakerAssetDataPairAsync(wethAssetData, takerAssetData);
return liquidity.makerAssetAvailableInBaseUnits.gt(new BigNumber(0));
};
// Write version info to the exported object for debugging

View File

@ -1,4 +1,4 @@
import { BuyQuote } from '@0x/asset-buyer';
import { MarketBuySwapQuote } from '@0x/asset-swapper';
import { BigNumber } from '@0x/utils';
import { ActionsUnion, AddressAndEthBalanceInWei, Asset, BaseCurrency, StandardSlidingPanelContent } from '../types';
@ -26,12 +26,12 @@ export enum ActionTypes {
UpdateAccountEthBalance = 'UPDATE_ACCOUNT_ETH_BALANCE',
UpdateEthUsdPrice = 'UPDATE_ETH_USD_PRICE',
UpdateSelectedAssetUnitAmount = 'UPDATE_SELECTED_ASSET_UNIT_AMOUNT',
SetBuyOrderStateNone = 'SET_BUY_ORDER_STATE_NONE',
SetBuyOrderStateValidating = 'SET_BUY_ORDER_STATE_VALIDATING',
SetBuyOrderStateProcessing = 'SET_BUY_ORDER_STATE_PROCESSING',
SetBuyOrderStateFailure = 'SET_BUY_ORDER_STATE_FAILURE',
SetBuyOrderStateSuccess = 'SET_BUY_ORDER_STATE_SUCCESS',
UpdateLatestBuyQuote = 'UPDATE_LATEST_BUY_QUOTE',
SetSwapOrderStateNone = 'SET_SWAP_ORDER_STATE_NONE',
SetSwapOrderStateValidating = 'SET_SWAP_ORDER_STATE_VALIDATING',
SetSwapOrderStateProcessing = 'SET_SWAP_ORDER_STATE_PROCESSING',
SetSwapOrderStateFailure = 'SET_SWAP_ORDER_STATE_FAILURE',
SetSwapOrderStateSuccess = 'SET_SWAP_ORDER_STATE_SUCCESS',
UpdateLatestSwapQuote = 'UPDATE_LATEST_SWAP_QUOTE',
UpdateSelectedAsset = 'UPDATE_SELECTED_ASSET',
SetAvailableAssets = 'SET_AVAILABLE_ASSETS',
SetQuoteRequestStatePending = 'SET_QUOTE_REQUEST_STATE_PENDING',
@ -53,13 +53,14 @@ export const actions = {
createAction(ActionTypes.UpdateAccountEthBalance, addressAndBalance),
updateEthUsdPrice: (price?: BigNumber) => createAction(ActionTypes.UpdateEthUsdPrice, price),
updateSelectedAssetAmount: (amount?: BigNumber) => createAction(ActionTypes.UpdateSelectedAssetUnitAmount, amount),
setBuyOrderStateNone: () => createAction(ActionTypes.SetBuyOrderStateNone),
setBuyOrderStateValidating: () => createAction(ActionTypes.SetBuyOrderStateValidating),
setBuyOrderStateProcessing: (txHash: string, startTimeUnix: number, expectedEndTimeUnix: number) =>
createAction(ActionTypes.SetBuyOrderStateProcessing, { txHash, startTimeUnix, expectedEndTimeUnix }),
setBuyOrderStateFailure: (txHash: string) => createAction(ActionTypes.SetBuyOrderStateFailure, txHash),
setBuyOrderStateSuccess: (txHash: string) => createAction(ActionTypes.SetBuyOrderStateSuccess, txHash),
updateLatestBuyQuote: (buyQuote?: BuyQuote) => createAction(ActionTypes.UpdateLatestBuyQuote, buyQuote),
setSwapOrderStateNone: () => createAction(ActionTypes.SetSwapOrderStateNone),
setSwapOrderStateValidating: () => createAction(ActionTypes.SetSwapOrderStateValidating),
setSwapOrderStateProcessing: (txHash: string, startTimeUnix: number, expectedEndTimeUnix: number) =>
createAction(ActionTypes.SetSwapOrderStateProcessing, { txHash, startTimeUnix, expectedEndTimeUnix }),
setSwapOrderStateFailure: (txHash: string) => createAction(ActionTypes.SetSwapOrderStateFailure, txHash),
setSwapOrderStateSuccess: (txHash: string) => createAction(ActionTypes.SetSwapOrderStateSuccess, txHash),
updateLatestSwapQuote: (swapQuote?: MarketBuySwapQuote) =>
createAction(ActionTypes.UpdateLatestSwapQuote, swapQuote),
updateSelectedAsset: (asset: Asset) => createAction(ActionTypes.UpdateSelectedAsset, asset),
setAvailableAssets: (availableAssets: Asset[]) => createAction(ActionTypes.SetAvailableAssets, availableAssets),
setQuoteRequestStatePending: () => createAction(ActionTypes.SetQuoteRequestStatePending),

View File

@ -6,10 +6,10 @@ import { BIG_NUMBER_ZERO } from '../constants';
import { AccountState, BaseCurrency, OrderProcessState, ProviderState, QuoteFetchOrigin } from '../types';
import { analytics } from '../util/analytics';
import { assetUtils } from '../util/asset';
import { buyQuoteUpdater } from '../util/buy_quote_updater';
import { coinbaseApi } from '../util/coinbase_api';
import { errorFlasher } from '../util/error_flasher';
import { errorReporter } from '../util/error_reporter';
import { swapQuoteUpdater } from '../util/swap_quote_updater';
import { actions } from './actions';
import { State } from './reducer';
@ -30,9 +30,10 @@ export const asyncData = {
},
fetchAvailableAssetDatasAndDispatchToStore: async (state: State, dispatch: Dispatch) => {
const { providerState, assetMetaDataMap, network } = state;
const assetBuyer = providerState.assetBuyer;
const swapQuoter = providerState.swapQuoter;
try {
const assetDatas = await assetBuyer.getAvailableAssetDatasAsync();
const wethAssetData = await swapQuoter.getEtherTokenAssetDataOrThrowAsync();
const assetDatas = await swapQuoter.getAvailableMakerAssetDatasAsync(wethAssetData);
const deduplicatedAssetDatas = _.uniq(assetDatas);
const assets = assetUtils.createAssetsFromAssetDatas(deduplicatedAssetDatas, assetMetaDataMap, network);
dispatch(actions.setAvailableAssets(assets));
@ -87,22 +88,22 @@ export const asyncData = {
return;
}
},
fetchCurrentBuyQuoteAndDispatchToStore: async (
fetchCurrentSwapQuoteAndDispatchToStore: async (
state: State,
dispatch: Dispatch,
fetchOrigin: QuoteFetchOrigin,
options: { updateSilently: boolean },
) => {
const { buyOrderState, providerState, selectedAsset, selectedAssetUnitAmount, affiliateInfo } = state;
const assetBuyer = providerState.assetBuyer;
const { swapOrderState, providerState, selectedAsset, selectedAssetUnitAmount } = state;
const swapQuoter = providerState.swapQuoter;
if (
selectedAssetUnitAmount !== undefined &&
selectedAsset !== undefined &&
selectedAssetUnitAmount.isGreaterThan(BIG_NUMBER_ZERO) &&
buyOrderState.processState === OrderProcessState.None
swapOrderState.processState === OrderProcessState.None
) {
await buyQuoteUpdater.updateBuyQuoteAsync(
assetBuyer,
await swapQuoteUpdater.updateSwapQuoteAsync(
swapQuoter,
dispatch,
selectedAsset,
selectedAssetUnitAmount,
@ -110,7 +111,6 @@ export const asyncData = {
{
setPending: !options.updateSilently,
dispatchErrors: !options.updateSilently,
affiliateInfo,
},
);
}

View File

@ -1,4 +1,4 @@
import { BuyQuote } from '@0x/asset-buyer';
import { MarketBuySwapQuote } from '@0x/asset-swapper';
import { AssetProxyId, ObjectMap } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
@ -30,7 +30,7 @@ import { Action, ActionTypes } from './actions';
export interface DefaultState {
network: Network;
assetMetaDataMap: ObjectMap<AssetMetaData>;
buyOrderState: OrderState;
swapOrderState: OrderState;
latestErrorDisplayStatus: DisplayStatus;
quoteRequestState: AsyncProcessState;
standardSlidingPanelSettings: StandardSlidingPanelSettings;
@ -48,7 +48,7 @@ interface OptionalState {
availableAssets: Asset[];
selectedAssetUnitAmount: BigNumber;
ethUsdPrice: BigNumber;
latestBuyQuote: BuyQuote;
latestSwapQuote: MarketBuySwapQuote;
latestErrorMessage: string;
affiliateInfo: AffiliateInfo;
walletDisplayName: string;
@ -60,7 +60,7 @@ export type State = DefaultState & PropsDerivedState & Partial<OptionalState>;
export const DEFAULT_STATE: DefaultState = {
network: Network.Mainnet,
assetMetaDataMap,
buyOrderState: { processState: OrderProcessState.None },
swapOrderState: { processState: OrderProcessState.None },
latestErrorDisplayStatus: DisplayStatus.Hidden,
quoteRequestState: AsyncProcessState.None,
standardSlidingPanelSettings: {
@ -115,14 +115,14 @@ export const createReducer = (initialState: State) => {
...state,
selectedAssetUnitAmount: action.data,
};
case ActionTypes.UpdateLatestBuyQuote:
const newBuyQuoteIfExists = action.data;
case ActionTypes.UpdateLatestSwapQuote:
const newSwapQuoteIfExists = action.data;
const shouldUpdate =
newBuyQuoteIfExists === undefined || doesBuyQuoteMatchState(newBuyQuoteIfExists, state);
newSwapQuoteIfExists === undefined || doesSwapQuoteMatchState(newSwapQuoteIfExists, state);
if (shouldUpdate) {
return {
...state,
latestBuyQuote: newBuyQuoteIfExists,
latestSwapQuote: newSwapQuoteIfExists,
quoteRequestState: AsyncProcessState.Success,
};
} else {
@ -131,31 +131,31 @@ export const createReducer = (initialState: State) => {
case ActionTypes.SetQuoteRequestStatePending:
return {
...state,
latestBuyQuote: undefined,
latestSwapQuote: undefined,
quoteRequestState: AsyncProcessState.Pending,
};
case ActionTypes.SetQuoteRequestStateFailure:
return {
...state,
latestBuyQuote: undefined,
latestSwapQuote: undefined,
quoteRequestState: AsyncProcessState.Failure,
};
case ActionTypes.SetBuyOrderStateNone:
case ActionTypes.SetSwapOrderStateNone:
return {
...state,
buyOrderState: { processState: OrderProcessState.None },
swapOrderState: { processState: OrderProcessState.None },
};
case ActionTypes.SetBuyOrderStateValidating:
case ActionTypes.SetSwapOrderStateValidating:
return {
...state,
buyOrderState: { processState: OrderProcessState.Validating },
swapOrderState: { processState: OrderProcessState.Validating },
};
case ActionTypes.SetBuyOrderStateProcessing:
case ActionTypes.SetSwapOrderStateProcessing:
const processingData = action.data;
const { startTimeUnix, expectedEndTimeUnix } = processingData;
return {
...state,
buyOrderState: {
swapOrderState: {
processState: OrderProcessState.Processing,
txHash: processingData.txHash,
progress: {
@ -164,14 +164,14 @@ export const createReducer = (initialState: State) => {
},
},
};
case ActionTypes.SetBuyOrderStateFailure:
case ActionTypes.SetSwapOrderStateFailure:
const failureTxHash = action.data;
if ('txHash' in state.buyOrderState) {
if (state.buyOrderState.txHash === failureTxHash) {
const { txHash, progress } = state.buyOrderState;
if ('txHash' in state.swapOrderState) {
if (state.swapOrderState.txHash === failureTxHash) {
const { txHash, progress } = state.swapOrderState;
return {
...state,
buyOrderState: {
swapOrderState: {
processState: OrderProcessState.Failure,
txHash,
progress,
@ -180,14 +180,14 @@ export const createReducer = (initialState: State) => {
}
}
return state;
case ActionTypes.SetBuyOrderStateSuccess:
case ActionTypes.SetSwapOrderStateSuccess:
const successTxHash = action.data;
if ('txHash' in state.buyOrderState) {
if (state.buyOrderState.txHash === successTxHash) {
const { txHash, progress } = state.buyOrderState;
if ('txHash' in state.swapOrderState) {
if (state.swapOrderState.txHash === successTxHash) {
const { txHash, progress } = state.swapOrderState;
return {
...state,
buyOrderState: {
swapOrderState: {
processState: OrderProcessState.Success,
txHash,
progress,
@ -221,9 +221,9 @@ export const createReducer = (initialState: State) => {
case ActionTypes.ResetAmount:
return {
...state,
latestBuyQuote: undefined,
latestSwapQuote: undefined,
quoteRequestState: AsyncProcessState.None,
buyOrderState: { processState: OrderProcessState.None },
swapOrderState: { processState: OrderProcessState.None },
selectedAssetUnitAmount: undefined,
};
case ActionTypes.SetAvailableAssets:
@ -271,18 +271,18 @@ const reduceStateWithAccount = (state: State, account: Account) => {
};
};
const doesBuyQuoteMatchState = (buyQuote: BuyQuote, state: State): boolean => {
const doesSwapQuoteMatchState = (swapQuote: MarketBuySwapQuote, state: State): boolean => {
const selectedAssetIfExists = state.selectedAsset;
const selectedAssetUnitAmountIfExists = state.selectedAssetUnitAmount;
// if no selectedAsset or selectedAssetAmount exists on the current state, return false
if (selectedAssetIfExists === undefined || selectedAssetUnitAmountIfExists === undefined) {
return false;
}
// if buyQuote's assetData does not match that of the current selected asset, return false
if (selectedAssetIfExists.assetData !== buyQuote.assetData) {
// if swapQuote's assetData does not match that of the current selected asset, return false
if (selectedAssetIfExists.assetData !== swapQuote.makerAssetData) {
return false;
}
// if ERC20 and buyQuote's assetBuyAmount does not match selectedAssetAmount, return false
// if ERC20 and swapQuote's makerAssetFillAmount does not match selectedAssetAmount, return false
// if ERC721, return true
const selectedAssetMetaData = selectedAssetIfExists.metaData;
if (selectedAssetMetaData.assetProxyId === AssetProxyId.ERC20) {
@ -290,7 +290,8 @@ const doesBuyQuoteMatchState = (buyQuote: BuyQuote, state: State): boolean => {
selectedAssetUnitAmountIfExists,
selectedAssetMetaData.decimals,
);
const doesAssetAmountMatch = selectedAssetAmountBaseUnits.eq(buyQuote.assetBuyAmount);
const doesAssetAmountMatch = selectedAssetAmountBaseUnits.eq(swapQuote.makerAssetFillAmount);
return doesAssetAmountMatch;
} else {
return true;

View File

@ -1,4 +1,4 @@
import { AssetBuyer, BigNumber } from '@0x/asset-buyer';
import { BigNumber, SwapQuoteConsumer, SwapQuoter } from '@0x/asset-swapper';
import { AssetProxyId, ObjectMap, SignedOrder } from '@0x/types';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { SupportedProvider, ZeroExProvider } from 'ethereum-types';
@ -110,7 +110,8 @@ export interface ProviderState {
name: string;
displayName: string;
provider: ZeroExProvider;
assetBuyer: AssetBuyer;
swapQuoter: SwapQuoter;
swapQuoteConsumer: SwapQuoteConsumer;
web3Wrapper: Web3Wrapper;
account: Account;
}
@ -191,6 +192,11 @@ export interface ZeroExInstantRequiredBaseConfig {
orderSource: OrderSource;
}
export interface AffiliateInfo {
feeRecipient: string;
feePercentage: number;
}
export interface ZeroExInstantOptionalBaseConfig {
provider: SupportedProvider;
walletDisplayName: string;

View File

@ -1,4 +1,4 @@
import { BuyQuote } from '@0x/asset-buyer';
import { MarketBuySwapQuote } from '@0x/asset-swapper';
import { BigNumber } from '@0x/utils';
import * as _ from 'lodash';
@ -82,20 +82,21 @@ function trackingEventFnWithPayload(eventName: EventNames): (eventProperties: Ev
};
}
const buyQuoteEventProperties = (buyQuote: BuyQuote) => {
const assetBuyAmount = buyQuote.assetBuyAmount.toString();
const assetEthAmount = buyQuote.worstCaseQuoteInfo.assetEthAmount.toString();
const feeEthAmount = buyQuote.worstCaseQuoteInfo.feeEthAmount.toString();
const totalEthAmount = buyQuote.worstCaseQuoteInfo.totalEthAmount.toString();
const feePercentage = buyQuote.feePercentage !== undefined ? buyQuote.feePercentage.toString() : 0;
const hasFeeOrders = !_.isEmpty(buyQuote.feeOrders) ? 'true' : 'false';
const swapQuoteEventProperties = (swapQuote: MarketBuySwapQuote) => {
const makerAssetFillAmount = swapQuote.makerAssetFillAmount.toString();
const assetEthAmount = swapQuote.worstCaseQuoteInfo.takerAssetAmount.toString();
const feeEthAmount = swapQuote.worstCaseQuoteInfo.protocolFeeInWeiAmount
.plus(swapQuote.worstCaseQuoteInfo.feeTakerAssetAmount)
.toString();
const totalEthAmount = swapQuote.worstCaseQuoteInfo.totalTakerAssetAmount
.plus(swapQuote.worstCaseQuoteInfo.protocolFeeInWeiAmount)
.toString();
return {
assetBuyAmount,
makerAssetFillAmount,
assetEthAmount,
feeEthAmount,
totalEthAmount,
feePercentage,
hasFeeOrders,
gasPrice: swapQuote.gasPrice.toString(),
};
};
@ -182,35 +183,50 @@ export const analytics = {
trackPaymentMethodDropdownOpened: trackingEventFnWithoutPayload(EventNames.PaymentMethodDropdownOpened),
trackPaymentMethodOpenedEtherscan: trackingEventFnWithoutPayload(EventNames.PaymentMethodOpenedEtherscan),
trackPaymentMethodCopiedAddress: trackingEventFnWithoutPayload(EventNames.PaymentMethodCopiedAddress),
trackBuyNotEnoughEth: (buyQuote: BuyQuote) =>
trackingEventFnWithPayload(EventNames.BuyNotEnoughEth)(buyQuoteEventProperties(buyQuote)),
trackBuyStarted: (buyQuote: BuyQuote) =>
trackingEventFnWithPayload(EventNames.BuyStarted)(buyQuoteEventProperties(buyQuote)),
trackBuySignatureDenied: (buyQuote: BuyQuote) =>
trackingEventFnWithPayload(EventNames.BuySignatureDenied)(buyQuoteEventProperties(buyQuote)),
trackBuySimulationFailed: (buyQuote: BuyQuote) =>
trackingEventFnWithPayload(EventNames.BuySimulationFailed)(buyQuoteEventProperties(buyQuote)),
trackBuyUnknownError: (buyQuote: BuyQuote, errorMessage: string) =>
trackBuyNotEnoughEth: (swapQuote: MarketBuySwapQuote) =>
trackingEventFnWithPayload(EventNames.BuyNotEnoughEth)(swapQuoteEventProperties(swapQuote)),
trackBuyStarted: (swapQuote: MarketBuySwapQuote) =>
trackingEventFnWithPayload(EventNames.BuyStarted)(swapQuoteEventProperties(swapQuote)),
trackBuySignatureDenied: (swapQuote: MarketBuySwapQuote) =>
trackingEventFnWithPayload(EventNames.BuySignatureDenied)(swapQuoteEventProperties(swapQuote)),
trackBuySimulationFailed: (swapQuote: MarketBuySwapQuote) =>
trackingEventFnWithPayload(EventNames.BuySimulationFailed)(swapQuoteEventProperties(swapQuote)),
trackBuyUnknownError: (swapQuote: MarketBuySwapQuote, errorMessage: string) =>
trackingEventFnWithPayload(EventNames.BuyUnknownError)({
...buyQuoteEventProperties(buyQuote),
...swapQuoteEventProperties(swapQuote),
errorMessage,
}),
trackBuyTxSubmitted: (buyQuote: BuyQuote, txHash: string, startTimeUnix: number, expectedEndTimeUnix: number) =>
trackBuyTxSubmitted: (
swapQuote: MarketBuySwapQuote,
txHash: string,
startTimeUnix: number,
expectedEndTimeUnix: number,
) =>
trackingEventFnWithPayload(EventNames.BuyTxSubmitted)({
...buyQuoteEventProperties(buyQuote),
...swapQuoteEventProperties(swapQuote),
txHash,
expectedTxTimeMs: expectedEndTimeUnix - startTimeUnix,
}),
trackBuyTxSucceeded: (buyQuote: BuyQuote, txHash: string, startTimeUnix: number, expectedEndTimeUnix: number) =>
trackBuyTxSucceeded: (
swapQuote: MarketBuySwapQuote,
txHash: string,
startTimeUnix: number,
expectedEndTimeUnix: number,
) =>
trackingEventFnWithPayload(EventNames.BuyTxSucceeded)({
...buyQuoteEventProperties(buyQuote),
...swapQuoteEventProperties(swapQuote),
txHash,
expectedTxTimeMs: expectedEndTimeUnix - startTimeUnix,
actualTxTimeMs: new Date().getTime() - startTimeUnix,
}),
trackBuyTxFailed: (buyQuote: BuyQuote, txHash: string, startTimeUnix: number, expectedEndTimeUnix: number) =>
trackBuyTxFailed: (
swapQuote: MarketBuySwapQuote,
txHash: string,
startTimeUnix: number,
expectedEndTimeUnix: number,
) =>
trackingEventFnWithPayload(EventNames.BuyTxFailed)({
...buyQuoteEventProperties(buyQuote),
...swapQuoteEventProperties(swapQuote),
txHash,
expectedTxTimeMs: expectedEndTimeUnix - startTimeUnix,
actualTxTimeMs: new Date().getTime() - startTimeUnix,
@ -232,15 +248,15 @@ export const analytics = {
trackingEventFnWithPayload(EventNames.TokenSelectorSearched)({ searchText }),
trackTransactionViewed: (orderProcesState: OrderProcessState) =>
trackingEventFnWithPayload(EventNames.TransactionViewed)({ orderState: orderProcesState }),
trackQuoteFetched: (buyQuote: BuyQuote, fetchOrigin: QuoteFetchOrigin) =>
trackQuoteFetched: (swapQuote: MarketBuySwapQuote, fetchOrigin: QuoteFetchOrigin) =>
trackingEventFnWithPayload(EventNames.QuoteFetched)({
...buyQuoteEventProperties(buyQuote),
...swapQuoteEventProperties(swapQuote),
fetchOrigin,
}),
trackQuoteError: (errorMessage: string, assetBuyAmount: BigNumber, fetchOrigin: QuoteFetchOrigin) => {
trackQuoteError: (errorMessage: string, makerAssetFillAmount: BigNumber, fetchOrigin: QuoteFetchOrigin) => {
trackingEventFnWithPayload(EventNames.QuoteError)({
errorMessage,
assetBuyAmount: assetBuyAmount.toString(),
makerAssetFillAmount: makerAssetFillAmount.toString(),
fetchOrigin,
});
},

View File

@ -1,4 +1,4 @@
import { AssetBuyerError, InsufficientAssetLiquidityError } from '@0x/asset-buyer';
import { InsufficientAssetLiquidityError, SwapQuoterError } from '@0x/asset-swapper';
import { AssetProxyId, ObjectMap } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
@ -109,8 +109,8 @@ export const assetUtils = {
);
return _.compact(erc20sOrUndefined);
},
assetBuyerErrorMessage: (asset: Asset, error: Error): string | undefined => {
if (error.message === AssetBuyerError.InsufficientAssetLiquidity) {
swapQuoterErrorMessage: (asset: Asset, error: Error): string | undefined => {
if (error.message === SwapQuoterError.InsufficientAssetLiquidity) {
const assetName = assetUtils.bestNameForAsset(asset, 'of this asset');
if (
error instanceof InsufficientAssetLiquidityError &&
@ -131,11 +131,9 @@ export const assetUtils = {
}
return `Not enough ${assetName} available`;
} else if (error.message === AssetBuyerError.InsufficientZrxLiquidity) {
return 'Not enough ZRX available';
} else if (
error.message === AssetBuyerError.StandardRelayerApiError ||
error.message.startsWith(AssetBuyerError.AssetUnavailable)
error.message === SwapQuoterError.StandardRelayerApiError ||
error.message.startsWith(SwapQuoterError.AssetUnavailable)
) {
const assetName = assetUtils.bestNameForAsset(asset, 'This asset');
return `${assetName} is currently unavailable`;

View File

@ -1,17 +0,0 @@
import { AssetBuyer, AssetBuyerOpts } from '@0x/asset-buyer';
import { SupportedProvider } from 'ethereum-types';
import * as _ from 'lodash';
import { Network, OrderSource } from '../types';
export const assetBuyerFactory = {
getAssetBuyer: (supportedProvider: SupportedProvider, orderSource: OrderSource, network: Network): AssetBuyer => {
const assetBuyerOptions: Partial<AssetBuyerOpts> = {
networkId: network,
};
const assetBuyer = _.isString(orderSource)
? AssetBuyer.getAssetBuyerForStandardRelayerAPIUrl(supportedProvider, orderSource, assetBuyerOptions)
: AssetBuyer.getAssetBuyerForProvidedOrders(supportedProvider, orderSource, assetBuyerOptions);
return assetBuyer;
},
};

View File

@ -0,0 +1,23 @@
import { SwapQuoteConsumer, SwapQuoter, SwapQuoterOpts } from '@0x/asset-swapper';
import { SupportedProvider } from 'ethereum-types';
import * as _ from 'lodash';
import { Network, OrderSource } from '../types';
export const assetSwapperFactory = {
getSwapQuoter: (supportedProvider: SupportedProvider, orderSource: OrderSource, network: Network): SwapQuoter => {
const swapQuoterOpts: Partial<SwapQuoterOpts> = {
chainId: network,
};
const swapQuoter = _.isString(orderSource)
? SwapQuoter.getSwapQuoterForStandardRelayerAPIUrl(supportedProvider, orderSource, swapQuoterOpts)
: SwapQuoter.getSwapQuoterForProvidedOrders(supportedProvider, orderSource, swapQuoterOpts);
return swapQuoter;
},
getSwapQuoteConsumer: (supportedProvider: SupportedProvider, network: Network): SwapQuoteConsumer => {
const swapQuoteConsumerOptions: Partial<SwapQuoterOpts> = {
chainId: network,
};
return new SwapQuoteConsumer(supportedProvider, swapQuoteConsumerOptions);
},
};

View File

@ -1,68 +0,0 @@
import { AssetBuyer, BuyQuote } from '@0x/asset-buyer';
import { AssetProxyId } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import * as _ from 'lodash';
import { Dispatch } from 'redux';
import { oc } from 'ts-optchain';
import { ERC20_BUY_QUOTE_SLIPPAGE_PERCENTAGE, ERC721_BUY_QUOTE_SLIPPAGE_PERCENTAGE } from '../constants';
import { Action, actions } from '../redux/actions';
import { AffiliateInfo, Asset, QuoteFetchOrigin } from '../types';
import { analytics } from '../util/analytics';
import { assetUtils } from '../util/asset';
import { errorFlasher } from '../util/error_flasher';
import { errorReporter } from '../util/error_reporter';
export const buyQuoteUpdater = {
updateBuyQuoteAsync: async (
assetBuyer: AssetBuyer,
dispatch: Dispatch<Action>,
asset: Asset,
assetUnitAmount: BigNumber,
fetchOrigin: QuoteFetchOrigin,
options: {
setPending: boolean;
dispatchErrors: boolean;
affiliateInfo?: AffiliateInfo;
},
): Promise<void> => {
// get a new buy quote.
const baseUnitValue =
asset.metaData.assetProxyId === AssetProxyId.ERC20
? Web3Wrapper.toBaseUnitAmount(assetUnitAmount, asset.metaData.decimals)
: assetUnitAmount;
if (options.setPending) {
// mark quote as pending
dispatch(actions.setQuoteRequestStatePending());
}
const feePercentage = oc(options.affiliateInfo).feePercentage();
let newBuyQuote: BuyQuote | undefined;
const slippagePercentage =
asset.metaData.assetProxyId === AssetProxyId.ERC20
? ERC20_BUY_QUOTE_SLIPPAGE_PERCENTAGE
: ERC721_BUY_QUOTE_SLIPPAGE_PERCENTAGE;
try {
newBuyQuote = await assetBuyer.getBuyQuoteAsync(asset.assetData, baseUnitValue, {
feePercentage,
slippagePercentage,
});
} catch (error) {
const errorMessage = assetUtils.assetBuyerErrorMessage(asset, error);
errorReporter.report(error);
analytics.trackQuoteError(error.message ? error.message : 'other', baseUnitValue, fetchOrigin);
if (options.dispatchErrors) {
dispatch(actions.setQuoteRequestStateFailure());
errorFlasher.flashNewErrorMessage(dispatch, errorMessage || 'Error fetching price, please try again');
}
return;
}
// We have a successful new buy quote
errorFlasher.clearError(dispatch);
// invalidate the last buy quote.
dispatch(actions.updateLatestBuyQuote(newBuyQuote));
analytics.trackQuoteFetched(newBuyQuote, fetchOrigin);
},
};

View File

@ -15,10 +15,10 @@ export const generateAccountHeartbeater = (options: HeartbeatFactoryOptions): He
}, shouldPerformImmediatelyOnStart);
};
export const generateBuyQuoteHeartbeater = (options: HeartbeatFactoryOptions): Heartbeater => {
export const generateSwapQuoteHeartbeater = (options: HeartbeatFactoryOptions): Heartbeater => {
const { store, shouldPerformImmediatelyOnStart } = options;
return new Heartbeater(async () => {
await asyncData.fetchCurrentBuyQuoteAndDispatchToStore(
await asyncData.fetchCurrentSwapQuoteAndDispatchToStore(
store.getState(),
store.dispatch,
QuoteFetchOrigin.Heartbeat,

View File

@ -7,7 +7,7 @@ import { LOADING_ACCOUNT, NO_ACCOUNT } from '../constants';
import { Maybe, Network, OrderSource, ProviderState } from '../types';
import { envUtil } from '../util/env';
import { assetBuyerFactory } from './asset_buyer_factory';
import { assetSwapperFactory } from './asset_swapper_factory';
import { providerFactory } from './provider_factory';
export const providerStateFactory = {
@ -48,7 +48,8 @@ export const providerStateFactory = {
displayName: walletDisplayName || envUtil.getProviderDisplayName(provider),
provider,
web3Wrapper: new Web3Wrapper(provider),
assetBuyer: assetBuyerFactory.getAssetBuyer(provider, orderSource, network),
swapQuoter: assetSwapperFactory.getSwapQuoter(provider, orderSource, network),
swapQuoteConsumer: assetSwapperFactory.getSwapQuoteConsumer(provider, network),
account: LOADING_ACCOUNT,
};
return providerState;
@ -65,7 +66,8 @@ export const providerStateFactory = {
displayName: walletDisplayName || envUtil.getProviderDisplayName(injectedProviderIfExists),
provider: injectedProviderIfExists,
web3Wrapper: new Web3Wrapper(injectedProviderIfExists),
assetBuyer: assetBuyerFactory.getAssetBuyer(injectedProviderIfExists, orderSource, network),
swapQuoter: assetSwapperFactory.getSwapQuoter(injectedProviderIfExists, orderSource, network),
swapQuoteConsumer: assetSwapperFactory.getSwapQuoteConsumer(injectedProviderIfExists, network),
account: LOADING_ACCOUNT,
};
return providerState;
@ -84,7 +86,8 @@ export const providerStateFactory = {
displayName: walletDisplayName || envUtil.getProviderDisplayName(provider),
provider,
web3Wrapper: new Web3Wrapper(provider),
assetBuyer: assetBuyerFactory.getAssetBuyer(provider, orderSource, network),
swapQuoter: assetSwapperFactory.getSwapQuoter(provider, orderSource, network),
swapQuoteConsumer: assetSwapperFactory.getSwapQuoteConsumer(provider, network),
account: NO_ACCOUNT,
};
return providerState;

View File

@ -0,0 +1,73 @@
import { MarketBuySwapQuote, SwapQuoter } from '@0x/asset-swapper';
import { AssetProxyId } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { Web3Wrapper } from '@0x/web3-wrapper';
import { Dispatch } from 'redux';
import { ERC20_SWAP_QUOTE_SLIPPAGE_PERCENTAGE, ERC721_SWAP_QUOTE_SLIPPAGE_PERCENTAGE } from '../constants';
import { Action, actions } from '../redux/actions';
import { Asset, QuoteFetchOrigin } from '../types';
import { analytics } from './analytics';
import { assetUtils } from './asset';
import { errorFlasher } from './error_flasher';
import { errorReporter } from './error_reporter';
import { gasPriceEstimator } from './gas_price_estimator';
export const swapQuoteUpdater = {
updateSwapQuoteAsync: async (
swapQuoter: SwapQuoter,
dispatch: Dispatch<Action>,
asset: Asset,
assetUnitAmount: BigNumber,
fetchOrigin: QuoteFetchOrigin,
options: {
setPending: boolean;
dispatchErrors: boolean;
},
): Promise<void> => {
// get a new swap quote.
const baseUnitValue =
asset.metaData.assetProxyId === AssetProxyId.ERC20
? Web3Wrapper.toBaseUnitAmount(assetUnitAmount, asset.metaData.decimals)
: assetUnitAmount;
if (options.setPending) {
// mark quote as pending
dispatch(actions.setQuoteRequestStatePending());
}
const wethAssetData = await swapQuoter.getEtherTokenAssetDataOrThrowAsync();
let newSwapQuote: MarketBuySwapQuote | undefined;
const slippagePercentage =
asset.metaData.assetProxyId === AssetProxyId.ERC20
? ERC20_SWAP_QUOTE_SLIPPAGE_PERCENTAGE
: ERC721_SWAP_QUOTE_SLIPPAGE_PERCENTAGE;
try {
const gasInfo = await gasPriceEstimator.getGasInfoAsync();
newSwapQuote = await swapQuoter.getMarketBuySwapQuoteForAssetDataAsync(
asset.assetData,
wethAssetData,
baseUnitValue,
{
slippagePercentage,
gasPrice: gasInfo.gasPriceInWei,
},
);
} catch (error) {
const errorMessage = assetUtils.swapQuoterErrorMessage(asset, error);
errorReporter.report(error);
analytics.trackQuoteError(error.message ? error.message : 'other', baseUnitValue, fetchOrigin);
if (options.dispatchErrors) {
dispatch(actions.setQuoteRequestStateFailure());
errorFlasher.flashNewErrorMessage(dispatch, errorMessage || 'Error fetching price, please try again');
}
return;
}
// We have a successful new swap quote
errorFlasher.clearError(dispatch);
// invalidate the last swap quote.
dispatch(actions.updateLatestSwapQuote(newSwapQuote));
analytics.trackQuoteFetched(newSwapQuote, fetchOrigin);
},
};

View File

@ -1,4 +1,4 @@
import { AssetBuyerError, BigNumber, InsufficientAssetLiquidityError } from '@0x/asset-buyer';
import { BigNumber, InsufficientAssetLiquidityError, SwapQuoterError } from '@0x/asset-swapper';
import { AssetProxyId, ObjectMap } from '@0x/types';
import { Web3Wrapper } from '@0x/web3-wrapper';
@ -59,8 +59,8 @@ describe('assetDataUtil', () => {
});
describe('assetBuyerErrorMessage', () => {
it('should return message for generic InsufficientAssetLiquidity error', () => {
const insufficientAssetError = new Error(AssetBuyerError.InsufficientAssetLiquidity);
expect(assetUtils.assetBuyerErrorMessage(ZRX_ASSET, insufficientAssetError)).toEqual(
const insufficientAssetError = new Error(SwapQuoterError.InsufficientAssetLiquidity);
expect(assetUtils.swapQuoterErrorMessage(ZRX_ASSET, insufficientAssetError)).toEqual(
'Not enough ZRX available',
);
});
@ -68,45 +68,39 @@ describe('assetDataUtil', () => {
it('should return custom message for token w/ 18 decimals', () => {
const amountAvailable = Web3Wrapper.toBaseUnitAmount(new BigNumber(20.059), 18);
expect(
assetUtils.assetBuyerErrorMessage(ZRX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)),
assetUtils.swapQuoterErrorMessage(ZRX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)),
).toEqual('There are only 20.05 ZRX available to buy');
});
it('should return custom message for token w/ 18 decimals and small amount available', () => {
const amountAvailable = Web3Wrapper.toBaseUnitAmount(new BigNumber(0.01), 18);
expect(
assetUtils.assetBuyerErrorMessage(ZRX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)),
assetUtils.swapQuoterErrorMessage(ZRX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)),
).toEqual('There are only 0.01 ZRX available to buy');
});
it('should return custom message for token w/ 8 decimals', () => {
const amountAvailable = Web3Wrapper.toBaseUnitAmount(new BigNumber(3), 8);
expect(
assetUtils.assetBuyerErrorMessage(WAX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)),
assetUtils.swapQuoterErrorMessage(WAX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)),
).toEqual('There are only 3 WAX available to buy');
});
it('should return generic message when amount available rounds to zero', () => {
const amountAvailable = Web3Wrapper.toBaseUnitAmount(new BigNumber(0.002), 18);
expect(
assetUtils.assetBuyerErrorMessage(ZRX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)),
assetUtils.swapQuoterErrorMessage(ZRX_ASSET, new InsufficientAssetLiquidityError(amountAvailable)),
).toEqual('Not enough ZRX available');
});
});
it('should return message for InsufficientZrxLiquidity', () => {
const insufficientZrxError = new Error(AssetBuyerError.InsufficientZrxLiquidity);
expect(assetUtils.assetBuyerErrorMessage(ZRX_ASSET, insufficientZrxError)).toEqual(
'Not enough ZRX available',
);
});
it('should message for StandardRelayerApiError', () => {
const standardRelayerError = new Error(AssetBuyerError.StandardRelayerApiError);
expect(assetUtils.assetBuyerErrorMessage(ZRX_ASSET, standardRelayerError)).toEqual(
const standardRelayerError = new Error(SwapQuoterError.StandardRelayerApiError);
expect(assetUtils.swapQuoterErrorMessage(ZRX_ASSET, standardRelayerError)).toEqual(
'ZRX is currently unavailable',
);
});
it('should return error for AssetUnavailable error', () => {
const assetUnavailableError = new Error(
`${AssetBuyerError.AssetUnavailable}: For assetData ${ZRX_ASSET_DATA}`,
`${SwapQuoterError.AssetUnavailable}: For assetData ${ZRX_ASSET_DATA}`,
);
expect(assetUtils.assetBuyerErrorMessage(ZRX_ASSET, assetUnavailableError)).toEqual(
expect(assetUtils.swapQuoterErrorMessage(ZRX_ASSET, assetUnavailableError)).toEqual(
'ZRX is currently unavailable',
);
});

View File

@ -38,6 +38,7 @@ export const docGenConfigs: DocGenConfigs = {
'SubscriptionErrors',
'TypedDataError',
'SwapQuoterError',
'SwapQuoteConsumerError',
'SwapQuoteGetOutputOpts',
'SwapQuoteExecutionOpts',
'ForwarderError',

View File

@ -47,6 +47,10 @@
"@types/lodash": "4.14.104",
"@types/mocha": "^5.2.7",
"@types/web3-provider-engine": "^14.0.0",
"@0x/types": "^2.5.0-beta.2",
"@0x/typescript-typings": "^4.4.0-beta.2",
"ethereum-types": "^2.2.0-beta.2",
"@types/node": "*",
"chai": "^4.0.1",
"make-promises-safe": "^1.1.0",
"mocha": "^6.2.0",
@ -63,15 +67,10 @@
"@0x/assert": "^2.2.0-beta.2",
"@0x/contract-addresses": "^3.3.0-beta.4",
"@0x/contract-artifacts": "^2.3.0-beta.3",
"@0x/contracts-dev-utils": "^0.1.0-beta.3",
"@0x/json-schemas": "^4.1.0-beta.2",
"@0x/types": "^2.5.0-beta.2",
"@0x/typescript-typings": "^4.4.0-beta.2",
"@0x/utils": "^4.6.0-beta.2",
"@0x/web3-wrapper": "^6.1.0-beta.2",
"@types/node": "*",
"bn.js": "^4.11.8",
"ethereum-types": "^2.2.0-beta.2",
"ethereumjs-abi": "0.6.5",
"ethereumjs-util": "^5.1.1",
"ethers": "~4.0.4",

View File

@ -1,4 +1,4 @@
import { BigNumber } from '@0x/utils';
import { BigNumber, NULL_ADDRESS, NULL_BYTES } from '@0x/utils';
import { MethodAbi } from 'ethereum-types';
const ERC20_METHOD_ABI: MethodAbi = {
@ -84,8 +84,9 @@ const STATIC_CALL_METHOD_ABI: MethodAbi = {
};
export const constants = {
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
NULL_BYTES: '0x',
NULL_ADDRESS,
FAKED_PROVIDER: { isEIP1193: true },
NULL_BYTES,
NULL_ERC20_ASSET_DATA: '0xf47261b00000000000000000000000000000000000000000000000000000000000000000',
// tslint:disable-next-line:custom-no-magic-numbers
UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1),

View File

@ -1,5 +1,5 @@
import { DevUtilsContract } from '@0x/abi-gen-wrappers';
import { assert } from '@0x/assert';
import { DevUtilsContract } from '@0x/contracts-dev-utils';
import { schemas } from '@0x/json-schemas';
import {
EIP712DomainWithDefaultSchema,
@ -112,9 +112,7 @@ export const eip712Utils = {
version: constants.COORDINATOR_DOMAIN_VERSION,
verifyingContract,
};
const transactionHash = await new DevUtilsContract('0x0000000000000000000000000000000000000000', {
isEIP1193: true,
} as any)
const transactionHash = await new DevUtilsContract(constants.NULL_ADDRESS, constants.FAKED_PROVIDER as any)
.getTransactionHash(
transaction,
new BigNumber(transaction.domain.chainId),

View File

@ -5,6 +5,7 @@ export { marketUtils } from './market_utils';
export { rateUtils } from './rate_utils';
export { sortingUtils } from './sorting_utils';
export { orderCalculationUtils } from './order_calculation_utils';
export { orderHashUtils } from './order_hash_utils';
export { eip712Utils } from './eip712_utils';

View File

@ -1,13 +1,13 @@
import { DevUtilsContract } from '@0x/contracts-dev-utils';
import { Order, SignedOrder } from '@0x/types';
import { BigNumber, providerUtils } from '@0x/utils';
import { SupportedProvider } from 'ethereum-types';
import * as _ from 'lodash';
import { constants } from './constants';
import { orderHashUtils } from './order_hash_utils';
import { generatePseudoRandomSalt } from './salt';
import { signatureUtils } from './signature_utils';
import { CreateOrderOpts } from './types';
export const orderFactory = {
createOrderFromPartial(partialOrder: Partial<Order>): Order {
const chainId: number = getChainIdFromPartial(partialOrder);
@ -77,20 +77,16 @@ export const orderFactory = {
await providerUtils.getChainIdAsync(supportedProvider),
createOrderOpts,
);
const orderHash = await new DevUtilsContract('0x0000000000000000000000000000000000000000', {
isEIP1193: true,
} as any)
.getOrderHash(order, new BigNumber(order.chainId), order.exchangeAddress)
.callAsync();
const orderHash = await orderHashUtils.getOrderHashAsync(order);
const signature = await signatureUtils.ecSignHashAsync(supportedProvider, orderHash, makerAddress);
const signedOrder: SignedOrder = _.assign(order, { signature });
const signedOrder: SignedOrder = { ...order, signature };
return signedOrder;
},
};
function getChainIdFromPartial(partialOrder: Partial<Order> | Partial<SignedOrder>): number {
const chainId = partialOrder.chainId;
if (!_.isNumber(chainId)) {
if (chainId === undefined || !Number.isInteger(chainId)) {
throw new Error('chainId must be valid');
}
return chainId;

View File

@ -0,0 +1,16 @@
import { DevUtilsContract } from '@0x/abi-gen-wrappers';
import { Order } from '@0x/types';
import { BigNumber } from '@0x/utils';
import { constants } from './constants';
const devUtilsContract = new DevUtilsContract(constants.NULL_ADDRESS, constants.FAKED_PROVIDER as any);
export const orderHashUtils = {
getOrderHashAsync: async (order: Order): Promise<string> => {
const orderHash = await devUtilsContract
.getOrderHash(order, new BigNumber(order.chainId), order.exchangeAddress)
.callAsync();
return orderHash;
},
};

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