removed asset-buyer
This commit is contained in:
parent
64d25e6522
commit
dd00f2016f
@ -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/**/*"]
|
||||
}
|
41
yarn.lock
41
yarn.lock
@ -661,23 +661,6 @@
|
||||
lodash "^4.17.11"
|
||||
valid-url "^1.0.9"
|
||||
|
||||
"@0x/asset-buyer@6.1.8":
|
||||
version "6.1.8"
|
||||
resolved "https://registry.yarnpkg.com/@0x/asset-buyer/-/asset-buyer-6.1.8.tgz#71f6abb366e89e62457c256644edb37e12113e94"
|
||||
dependencies:
|
||||
"@0x/assert" "^2.1.0"
|
||||
"@0x/connect" "^5.0.13"
|
||||
"@0x/contract-wrappers" "^9.1.7"
|
||||
"@0x/json-schemas" "^3.0.11"
|
||||
"@0x/order-utils" "^8.2.2"
|
||||
"@0x/subproviders" "^4.1.1"
|
||||
"@0x/types" "^2.4.0"
|
||||
"@0x/typescript-typings" "^4.2.3"
|
||||
"@0x/utils" "^4.4.0"
|
||||
"@0x/web3-wrapper" "^6.0.7"
|
||||
ethereum-types "^2.1.3"
|
||||
lodash "^4.17.11"
|
||||
|
||||
"@0x/base-contract@^5.4.0":
|
||||
version "5.4.0"
|
||||
resolved "https://registry.npmjs.org/@0x/base-contract/-/base-contract-5.4.0.tgz#466ea98af22d7e629a21a7d16211c825664e2a48"
|
||||
@ -697,23 +680,7 @@
|
||||
lodash "^4.17.11"
|
||||
uuid "^3.3.2"
|
||||
|
||||
"@0x/connect@^5.0.13":
|
||||
version "5.0.19"
|
||||
resolved "https://registry.npmjs.org/@0x/connect/-/connect-5.0.19.tgz#569679af661ef84a4c34958388e1be7f1c250a04"
|
||||
dependencies:
|
||||
"@0x/assert" "^2.1.6"
|
||||
"@0x/json-schemas" "^4.0.2"
|
||||
"@0x/order-utils" "^8.4.0"
|
||||
"@0x/types" "^2.4.3"
|
||||
"@0x/typescript-typings" "^4.3.0"
|
||||
"@0x/utils" "^4.5.2"
|
||||
lodash "^4.17.11"
|
||||
query-string "^6.0.0"
|
||||
sinon "^4.0.0"
|
||||
uuid "^3.3.2"
|
||||
websocket "^1.0.26"
|
||||
|
||||
"@0x/contract-addresses@^3.0.2", "@0x/contract-addresses@^3.2.0":
|
||||
"@0x/contract-addresses@^3.0.1", "@0x/contract-addresses@^3.0.2", "@0x/contract-addresses@^3.2.0":
|
||||
version "3.2.0"
|
||||
resolved "https://registry.npmjs.org/@0x/contract-addresses/-/contract-addresses-3.2.0.tgz#606307696d9622764220a34e9d4638b899093eec"
|
||||
dependencies:
|
||||
@ -723,7 +690,7 @@
|
||||
version "2.2.2"
|
||||
resolved "https://registry.npmjs.org/@0x/contract-artifacts/-/contract-artifacts-2.2.2.tgz#e6d771afb58d0b59c19c5364af5a42a3dfd17219"
|
||||
|
||||
"@0x/contract-wrappers@^9.1.7":
|
||||
"@0x/contract-wrappers@^9.1.6":
|
||||
version "9.1.8"
|
||||
resolved "https://registry.yarnpkg.com/@0x/contract-wrappers/-/contract-wrappers-9.1.8.tgz#5923d35af3e4b442a57d02f74e02620b2d5b1356"
|
||||
dependencies:
|
||||
@ -807,7 +774,7 @@
|
||||
uuid "^3.3.2"
|
||||
websocket "^1.0.29"
|
||||
|
||||
"@0x/order-utils@^8.2.2", "@0x/order-utils@^8.2.3", "@0x/order-utils@^8.4.0":
|
||||
"@0x/order-utils@^8.2.1", "@0x/order-utils@^8.2.3", "@0x/order-utils@^8.2.4", "@0x/order-utils@^8.4.0":
|
||||
version "8.4.0"
|
||||
resolved "https://registry.npmjs.org/@0x/order-utils/-/order-utils-8.4.0.tgz#f7fe9c73f9fd82ab05ec3c04951049e904aab46a"
|
||||
dependencies:
|
||||
@ -6744,7 +6711,7 @@ ethereum-common@^0.0.18:
|
||||
version "0.0.18"
|
||||
resolved "https://registry.yarnpkg.com/ethereum-common/-/ethereum-common-0.0.18.tgz#2fdc3576f232903358976eb39da783213ff9523f"
|
||||
|
||||
ethereum-types@^2.1.3, ethereum-types@^2.1.4, ethereum-types@^2.1.6:
|
||||
ethereum-types@^2.1.4, ethereum-types@^2.1.6:
|
||||
version "2.1.6"
|
||||
resolved "https://registry.npmjs.org/ethereum-types/-/ethereum-types-2.1.6.tgz#57d9d515fad86ab987c0f6962c4203be37da8579"
|
||||
dependencies:
|
||||
|
Loading…
x
Reference in New Issue
Block a user