Merge pull request #2336 from 0xProject/feature/upgrade-instant-v3
Upgrade instant v3
This commit is contained in:
commit
4b5f2c36b9
@ -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 }}
|
||||
|
2
.github/autolabeler.yml
vendored
2
.github/autolabeler.yml
vendored
@ -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']
|
||||
|
@ -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
|
||||
|
@ -65,7 +65,6 @@ These packages are all under development. See [/contracts/README.md](/contracts/
|
||||
| [`@0x/abi-gen-wrappers`](/packages/abi-gen-wrappers) | [](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) | [](https://www.npmjs.com/package/@0x/sra-spec) | OpenAPI specification for the Standard Relayer API |
|
||||
| [`@0x/connect`](/packages/connect) | [](https://www.npmjs.com/package/@0x/connect) | An HTTP/WS client for interacting with the Standard Relayer API |
|
||||
| [`@0x/asset-buyer`](/packages/asset-buyer) | [](https://www.npmjs.com/package/@0x/asset-buyer) | Convenience package for discovering and buying assets with Ether |
|
||||
| [`@0x/asset-swapper`](/packages/asset-swapper) | [](https://www.npmjs.com/package/@0x/asset-swapper) | Convenience package for discovering and performing swaps for any ERC20 Assets |
|
||||
|
||||
#### Ethereum tooling
|
||||
|
@ -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 {
|
||||
|
@ -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"
|
||||
|
@ -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": {
|
||||
|
@ -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> {
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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": {
|
||||
|
@ -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
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -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
|
@ -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
|
||||
```
|
@ -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"
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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,
|
||||
};
|
@ -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);
|
||||
}
|
||||
}
|
6
packages/asset-buyer/src/globals.d.ts
vendored
6
packages/asset-buyer/src/globals.d.ts
vendored
@ -1,6 +0,0 @@
|
||||
declare module '*.json' {
|
||||
const json: any;
|
||||
/* tslint:disable */
|
||||
export default json;
|
||||
/* tslint:enable */
|
||||
}
|
@ -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';
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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}`,
|
||||
);
|
||||
},
|
||||
};
|
@ -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];
|
||||
}
|
@ -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,
|
||||
};
|
||||
};
|
@ -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);
|
||||
};
|
@ -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;
|
||||
}
|
@ -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,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
@ -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);
|
||||
},
|
||||
};
|
@ -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;
|
||||
};
|
@ -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();
|
||||
},
|
||||
};
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": ["./src/**/*", "./test/**/*"]
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": ["@0x/tslint-config"]
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"extends": "../../typedoc-tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["./src/**/*", "./test/**/*"]
|
||||
}
|
@ -3366,4 +3366,3 @@ ___
|
||||
## Type aliases
|
||||
|
||||
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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';
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
},
|
||||
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
@ -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';
|
||||
|
@ -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;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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,
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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"
|
||||
}
|
||||
|
1504
packages/contract-artifacts/artifacts/DevUtils.json
generated
1504
packages/contract-artifacts/artifacts/DevUtils.json
generated
File diff suppressed because one or more lines are too long
@ -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",
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
@ -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">
|
||||
|
@ -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}
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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 &&
|
||||
|
@ -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 {
|
||||
|
@ -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€';
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)(
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
|
@ -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,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
});
|
||||
},
|
||||
|
@ -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`;
|
||||
|
@ -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;
|
||||
},
|
||||
};
|
23
packages/instant/src/util/asset_swapper_factory.ts
Normal file
23
packages/instant/src/util/asset_swapper_factory.ts
Normal 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);
|
||||
},
|
||||
};
|
@ -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);
|
||||
},
|
||||
};
|
@ -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,
|
||||
|
@ -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;
|
||||
|
73
packages/instant/src/util/swap_quote_updater.ts
Normal file
73
packages/instant/src/util/swap_quote_updater.ts
Normal 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);
|
||||
},
|
||||
};
|
@ -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',
|
||||
);
|
||||
});
|
||||
|
@ -38,6 +38,7 @@ export const docGenConfigs: DocGenConfigs = {
|
||||
'SubscriptionErrors',
|
||||
'TypedDataError',
|
||||
'SwapQuoterError',
|
||||
'SwapQuoteConsumerError',
|
||||
'SwapQuoteGetOutputOpts',
|
||||
'SwapQuoteExecutionOpts',
|
||||
'ForwarderError',
|
||||
|
@ -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",
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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;
|
||||
|
16
packages/order-utils/src/order_hash_utils.ts
Normal file
16
packages/order-utils/src/order_hash_utils.ts
Normal 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
Loading…
x
Reference in New Issue
Block a user