Merge branch 'development' into feature/contracts-monorepo-7
This commit is contained in:
commit
e72742f1f7
@ -83,6 +83,20 @@ jobs:
|
||||
keys:
|
||||
- repo-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run: yarn test:generate_docs:circleci
|
||||
test-pipeline:
|
||||
docker:
|
||||
- image: circleci/node:9
|
||||
- image: postgres:11-alpine
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- repo-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- run: ZEROEX_DATA_PIPELINE_TEST_DB_URL='postgresql://postgres@localhost/postgres' yarn wsrun test:circleci @0x/pipeline
|
||||
- save_cache:
|
||||
key: coverage-pipeline-{{ .Environment.CIRCLE_SHA1 }}
|
||||
paths:
|
||||
- ~/repo/packages/pipeline/coverage/lcov.info
|
||||
test-rest:
|
||||
docker:
|
||||
- image: circleci/node:9
|
||||
@ -342,6 +356,9 @@ workflows:
|
||||
- test-contracts-geth:
|
||||
requires:
|
||||
- build
|
||||
- test-pipeline:
|
||||
requires:
|
||||
- build
|
||||
- test-rest:
|
||||
requires:
|
||||
- build
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -11,6 +11,10 @@ pids
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# SQLite database files
|
||||
*.db
|
||||
*.sqlite
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
|
@ -77,8 +77,9 @@ Visit our [developer portal](https://0xproject.com/docs/order-utils) for a compr
|
||||
#### Private Packages
|
||||
|
||||
| Package | Description |
|
||||
| -------------------------------------------------- | ---------------------------------------------------------------- |
|
||||
| -------------------------------------------------- | -------------------------------------------------------------------------------- |
|
||||
| [`@0x/contracts`](/contracts/core) | 0x protocol solidity smart contracts & tests |
|
||||
| [`@0x/instant`](/packages/instant) | A free and flexible way to offer simple crypto purchasing in any app or website. |
|
||||
| [`@0x/testnet-faucets`](/packages/testnet-faucets) | A faucet micro-service that dispenses test ERC20 tokens or Ether |
|
||||
| [`@0x/website`](/packages/website) | 0x website |
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
## Contracts interfaces
|
||||
## Contract interfaces
|
||||
|
||||
Smart contract interfaces of the 0x protocol.
|
||||
|
||||
|
@ -135,7 +135,7 @@ describe('MixinSignatureValidator', () => {
|
||||
|
||||
it('should revert when signature type is unsupported', async () => {
|
||||
const unsupportedSignatureType = SignatureType.NSignatureTypes;
|
||||
const unsupportedSignatureHex = '0x' + Buffer.from([unsupportedSignatureType]).toString('hex');
|
||||
const unsupportedSignatureHex = `0x${Buffer.from([unsupportedSignatureType]).toString('hex')}`;
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
return expectContractCallFailedAsync(
|
||||
signatureValidator.publicIsValidSignature.callAsync(
|
||||
@ -148,7 +148,7 @@ describe('MixinSignatureValidator', () => {
|
||||
});
|
||||
|
||||
it('should revert when SignatureType=Illegal', async () => {
|
||||
const unsupportedSignatureHex = '0x' + Buffer.from([SignatureType.Illegal]).toString('hex');
|
||||
const unsupportedSignatureHex = `0x${Buffer.from([SignatureType.Illegal]).toString('hex')}`;
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
return expectContractCallFailedAsync(
|
||||
signatureValidator.publicIsValidSignature.callAsync(
|
||||
@ -161,7 +161,7 @@ describe('MixinSignatureValidator', () => {
|
||||
});
|
||||
|
||||
it('should return false when SignatureType=Invalid and signature has a length of zero', async () => {
|
||||
const signatureHex = '0x' + Buffer.from([SignatureType.Invalid]).toString('hex');
|
||||
const signatureHex = `0x${Buffer.from([SignatureType.Invalid]).toString('hex')}`;
|
||||
const orderHashHex = orderHashUtils.getOrderHashHex(signedOrder);
|
||||
const isValidSignature = await signatureValidator.publicIsValidSignature.callAsync(
|
||||
orderHashHex,
|
||||
|
@ -229,7 +229,7 @@ export class ExchangeWrapper {
|
||||
return orderEpoch;
|
||||
}
|
||||
public async getOrderInfoAsync(signedOrder: SignedOrder): Promise<OrderInfo> {
|
||||
const orderInfo = (await this._exchange.getOrderInfo.callAsync(signedOrder)) as OrderInfo;
|
||||
const orderInfo = await this._exchange.getOrderInfo.callAsync(signedOrder);
|
||||
return orderInfo;
|
||||
}
|
||||
public async getOrdersInfoAsync(signedOrders: SignedOrder[]): Promise<OrderInfo[]> {
|
||||
|
11
contracts/test-utils/CHANGELOG.json
Normal file
11
contracts/test-utils/CHANGELOG.json
Normal file
@ -0,0 +1,11 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1544482891,
|
||||
"version": "1.0.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
10
contracts/test-utils/CHANGELOG.md
Normal file
10
contracts/test-utils/CHANGELOG.md
Normal file
@ -0,0 +1,10 @@
|
||||
<!--
|
||||
changelogUtils.file is auto-generated using the monorepo-scripts package. Don't edit directly.
|
||||
Edit the package's CHANGELOG.json file only.
|
||||
-->
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v1.0.1 - _December 10, 2018_
|
||||
|
||||
* Dependencies updated
|
@ -137,7 +137,7 @@ export async function expectTransactionFailedWithoutReasonAsync(p: sendTransacti
|
||||
// directly.
|
||||
txReceiptStatus = result.status;
|
||||
} else {
|
||||
throw new Error('Unexpected result type: ' + typeof result);
|
||||
throw new Error(`Unexpected result type: ${typeof result}`);
|
||||
}
|
||||
expect(_.toString(txReceiptStatus)).to.equal(
|
||||
'0',
|
||||
|
@ -25,7 +25,7 @@ const blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
||||
|
||||
// BUG: Ideally we would use Buffer.from(memory).toString('hex')
|
||||
// https://github.com/Microsoft/TypeScript/issues/23155
|
||||
const toHex = (buf: Uint8Array): string => buf.reduce((a, v) => a + ('00' + v.toString(16)).slice(-2), '0x');
|
||||
const toHex = (buf: Uint8Array): string => buf.reduce((a, v) => a + `00${v.toString(16)}`.slice(-2), '0x');
|
||||
|
||||
const fromHex = (str: string): Uint8Array => Uint8Array.from(Buffer.from(str.slice(2), 'hex'));
|
||||
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1544482891,
|
||||
"version": "2.0.7",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1543401373,
|
||||
"version": "2.0.6",
|
||||
|
@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v2.0.7 - _December 10, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.0.6 - _November 28, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1544482891,
|
||||
"version": "2.0.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"changes": [
|
||||
|
@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v2.0.1 - _December 10, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.0.0 - _November 28, 2018_
|
||||
|
||||
* Update Exchange artifact to receive ZRX asset data as a constructor argument (#1309)
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1544482891,
|
||||
"version": "1.0.18",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1542821676,
|
||||
"version": "1.0.17",
|
||||
|
@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v1.0.18 - _December 10, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.0.17 - _November 21, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1544482891,
|
||||
"version": "1.0.19",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1542821676,
|
||||
"version": "1.0.18",
|
||||
|
@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v1.0.19 - _December 10, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.0.18 - _November 21, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
@ -5,7 +5,8 @@
|
||||
{
|
||||
"note": "Update SRA order provider to include Dai"
|
||||
}
|
||||
]
|
||||
],
|
||||
"timestamp": 1544482891
|
||||
},
|
||||
{
|
||||
"timestamp": 1543401373,
|
||||
|
@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v3.0.3 - _December 10, 2018_
|
||||
|
||||
* Update SRA order provider to include Dai
|
||||
|
||||
## v3.0.2 - _November 28, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1544482891,
|
||||
"version": "3.0.9",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1543401373,
|
||||
"version": "3.0.8",
|
||||
|
@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v3.0.9 - _December 10, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.0.8 - _November 28, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1544482891,
|
||||
"version": "3.0.9",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1543401373,
|
||||
"version": "3.0.8",
|
||||
|
@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v3.0.9 - _December 10, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.0.8 - _November 28, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1544482891,
|
||||
"version": "4.1.2",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1543401373,
|
||||
"version": "4.1.1",
|
||||
|
@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v4.1.2 - _December 10, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v4.1.1 - _November 28, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1544482891,
|
||||
"version": "1.0.20",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1543401373,
|
||||
"version": "1.0.19",
|
||||
|
@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v1.0.20 - _December 10, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.0.19 - _November 28, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1544482891,
|
||||
"version": "1.1.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "1.1.2",
|
||||
"changes": [
|
||||
|
@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v1.1.3 - _December 10, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.1.2 - _November 9, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1544482891,
|
||||
"version": "1.0.15",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1543401373,
|
||||
"version": "1.0.14",
|
||||
|
@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v1.0.15 - _December 10, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v1.0.14 - _November 28, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
BIN
packages/instant/.DS_Store
vendored
BIN
packages/instant/.DS_Store
vendored
Binary file not shown.
@ -1,5 +1,9 @@
|
||||
## @0x/instant
|
||||
|
||||
## Integration
|
||||
|
||||
Looking to integrate 0x Instant into your web application or site? Check out the dedicated [instant documentation](https://0xproject.com/wiki#Get-Started-With-Instant) to get started. The documentation covers instant and related topics in depth. For a more "drag and drop" experience, check out our [configurator tool](https://0xproject.com/instant#configure). For on demand developer support, join our [Discord](https://discordapp.com/invite/d3FTX3M).
|
||||
|
||||
## Installation
|
||||
|
||||
The package is available as a UMD module named `zeroExInstant` at https://instant.0xproject.com/instant.js.
|
||||
|
@ -10,7 +10,7 @@
|
||||
"scripts": {
|
||||
"build": "webpack --mode production",
|
||||
"build:ci": "yarn build",
|
||||
"dev": "webpack-dev-server --mode development",
|
||||
"dev": "dotenv webpack-dev-server -- --mode development",
|
||||
"lint": "tslint --format stylish --project .",
|
||||
"test": "jest",
|
||||
"test:coverage": "jest --coverage",
|
||||
@ -24,10 +24,7 @@
|
||||
},
|
||||
"config": {
|
||||
"postpublish": {
|
||||
"assets": [
|
||||
"packages/instant/umd/instant.js",
|
||||
"packages/instant/umd/instant.js.map"
|
||||
]
|
||||
"assets": ["packages/instant/umd/instant.js", "packages/instant/umd/instant.js.map"]
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
|
@ -15,6 +15,10 @@ input {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
input::-webkit-input-placeholder {
|
||||
color: #b4b4b4 !important;
|
||||
}
|
||||
|
||||
div {
|
||||
padding: 3px;
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import { analytics } from '../util/analytics';
|
||||
import { assetUtils } from '../util/asset';
|
||||
|
||||
import { SearchInput } from './search_input';
|
||||
|
||||
import { Circle } from './ui/circle';
|
||||
import { Container } from './ui/container';
|
||||
import { Flex } from './ui/flex';
|
||||
@ -123,10 +122,20 @@ interface TokenSelectorRowIconProps {
|
||||
token: ERC20Asset;
|
||||
}
|
||||
|
||||
const getTokenIcon = (symbol: string): React.StatelessComponent | undefined => {
|
||||
try {
|
||||
return require(`../assets/icons/${symbol}.svg`) as React.StatelessComponent;
|
||||
} catch (e) {
|
||||
// Can't find icon
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const TokenSelectorRowIcon: React.StatelessComponent<TokenSelectorRowIconProps> = props => {
|
||||
const { token } = props;
|
||||
const iconUrlIfExists = token.metaData.iconUrl;
|
||||
const TokenIcon = require(`../assets/icons/${token.metaData.symbol}.svg`);
|
||||
|
||||
const TokenIcon = getTokenIcon(token.metaData.symbol);
|
||||
const displaySymbol = assetUtils.bestNameForAsset(token);
|
||||
if (!_.isUndefined(iconUrlIfExists)) {
|
||||
return <img src={iconUrlIfExists} />;
|
||||
|
@ -61,6 +61,12 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> {
|
||||
}
|
||||
|
||||
private _renderAmountsSection(): React.ReactNode {
|
||||
if (
|
||||
_.isUndefined(this.props.totalEthBaseUnitAmount) &&
|
||||
this.props.quoteRequestState !== AsyncProcessState.Pending
|
||||
) {
|
||||
return null;
|
||||
} else {
|
||||
return (
|
||||
<Container>
|
||||
<Container marginBottom="5px">{this._renderPlaceholderOrAmount(this._renderEthAmount)}</Container>
|
||||
@ -68,6 +74,7 @@ export class InstantHeading extends React.Component<InstantHeadingProps, {}> {
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _renderIcon(): React.ReactNode {
|
||||
const processState = this.props.buyOrderState.processState;
|
||||
|
@ -29,8 +29,8 @@ export const Input =
|
||||
outline: none;
|
||||
border: none;
|
||||
&::placeholder {
|
||||
color: ${props => props.theme[props.fontColor || 'white']};
|
||||
opacity: 0.5;
|
||||
color: ${props => props.theme[props.fontColor || 'white']} !important;
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -1,6 +1,4 @@
|
||||
import { ObjectMap } from '@0x/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { Provider } from 'ethereum-types';
|
||||
import * as _ from 'lodash';
|
||||
import * as React from 'react';
|
||||
import { Provider as ReduxProvider } from 'react-redux';
|
||||
@ -11,7 +9,7 @@ import { asyncData } from '../redux/async_data';
|
||||
import { DEFAULT_STATE, DefaultState, State } from '../redux/reducer';
|
||||
import { store, Store } from '../redux/store';
|
||||
import { fonts } from '../style/fonts';
|
||||
import { AccountState, AffiliateInfo, AssetMetaData, Network, OrderSource, QuoteFetchOrigin } from '../types';
|
||||
import { AccountState, Network, QuoteFetchOrigin, ZeroExInstantBaseConfig } from '../types';
|
||||
import { analytics, disableAnalytics } from '../util/analytics';
|
||||
import { assetUtils } from '../util/asset';
|
||||
import { errorFlasher } from '../util/error_flasher';
|
||||
@ -21,24 +19,7 @@ import { Heartbeater } from '../util/heartbeater';
|
||||
import { generateAccountHeartbeater, generateBuyQuoteHeartbeater } from '../util/heartbeater_factory';
|
||||
import { providerStateFactory } from '../util/provider_state_factory';
|
||||
|
||||
export type ZeroExInstantProviderProps = ZeroExInstantProviderRequiredProps &
|
||||
Partial<ZeroExInstantProviderOptionalProps>;
|
||||
|
||||
export interface ZeroExInstantProviderRequiredProps {
|
||||
orderSource: OrderSource;
|
||||
}
|
||||
|
||||
export interface ZeroExInstantProviderOptionalProps {
|
||||
provider: Provider;
|
||||
walletDisplayName: string;
|
||||
availableAssetDatas: string[];
|
||||
defaultAssetBuyAmount: number;
|
||||
defaultSelectedAssetData: string;
|
||||
additionalAssetMetaDataMap: ObjectMap<AssetMetaData>;
|
||||
networkId: Network;
|
||||
affiliateInfo: AffiliateInfo;
|
||||
shouldDisableAnalyticsTracking: boolean;
|
||||
}
|
||||
export type ZeroExInstantProviderProps = ZeroExInstantBaseConfig;
|
||||
|
||||
export class ZeroExInstantProvider extends React.Component<ZeroExInstantProviderProps> {
|
||||
private readonly _store: Store;
|
||||
@ -57,10 +38,12 @@ export class ZeroExInstantProvider extends React.Component<ZeroExInstantProvider
|
||||
props.orderSource,
|
||||
networkId,
|
||||
props.provider,
|
||||
props.walletDisplayName,
|
||||
);
|
||||
// merge the additional additionalAssetMetaDataMap with our default map
|
||||
const completeAssetMetaDataMap = {
|
||||
...props.additionalAssetMetaDataMap,
|
||||
// Make sure the passed in assetDatas are lower case
|
||||
..._.mapKeys(props.additionalAssetMetaDataMap || {}, (value, key) => key.toLowerCase()),
|
||||
...defaultState.assetMetaDataMap,
|
||||
};
|
||||
// construct the final state
|
||||
|
@ -15,6 +15,7 @@ export const GWEI_IN_WEI = new BigNumber(1000000000);
|
||||
export const ONE_SECOND_MS = 1000;
|
||||
export const ONE_MINUTE_MS = ONE_SECOND_MS * 60;
|
||||
export const GIT_SHA = process.env.GIT_SHA;
|
||||
export const NODE_ENV = process.env.NODE_ENV;
|
||||
export const NPM_PACKAGE_VERSION = process.env.NPM_PACKAGE_VERSION;
|
||||
export const ACCOUNT_UPDATE_INTERVAL_TIME_MS = ONE_SECOND_MS * 5;
|
||||
export const BUY_QUOTE_UPDATE_INTERVAL_TIME_MS = ONE_SECOND_MS * 15;
|
||||
@ -28,14 +29,12 @@ export const HEAP_ENABLED = process.env.HEAP_ENABLED;
|
||||
export const COINBASE_API_BASE_URL = 'https://api.coinbase.com/v2';
|
||||
export const PROGRESS_STALL_AT_WIDTH = '95%';
|
||||
export const PROGRESS_FINISH_ANIMATION_TIME_MS = 200;
|
||||
export const HOST_DOMAINS = [
|
||||
export const HOST_DOMAINS_EXTERNAL = [
|
||||
'0x-instant-staging.s3-website-us-east-1.amazonaws.com',
|
||||
'0x-instant-dogfood.s3-website-us-east-1.amazonaws.com',
|
||||
'localhost',
|
||||
'127.0.0.1',
|
||||
'0.0.0.0',
|
||||
'instant.0xproject.com',
|
||||
];
|
||||
export const HOST_DOMAINS_LOCAL = ['localhost', '127.0.0.1', '0.0.0.0'];
|
||||
export const ROLLBAR_CLIENT_TOKEN = process.env.ROLLBAR_CLIENT_TOKEN;
|
||||
export const ROLLBAR_ENABLED = process.env.ROLLBAR_ENABLED;
|
||||
export const INSTANT_DISCHARGE_TARGET = process.env.INSTANT_DISCHARGE_TARGET as
|
||||
|
@ -58,7 +58,7 @@ const mergeProps = (
|
||||
...ownProps,
|
||||
network: connectedState.network,
|
||||
account: connectedState.providerState.account,
|
||||
walletDisplayName: connectedState.walletDisplayName || connectedState.providerState.name,
|
||||
walletDisplayName: connectedState.providerState.displayName,
|
||||
onUnlockWalletClick: () => connectedDispatch.unlockWalletAndDispatchToStore(connectedState.providerState),
|
||||
onInstallWalletClick: () => {
|
||||
const isMobile = envUtil.isMobileOperatingSystem();
|
||||
|
@ -102,6 +102,7 @@ export interface AffiliateInfo {
|
||||
|
||||
export interface ProviderState {
|
||||
name: string;
|
||||
displayName: string;
|
||||
provider: Provider;
|
||||
assetBuyer: AssetBuyer;
|
||||
web3Wrapper: Web3Wrapper;
|
||||
@ -177,3 +178,21 @@ export enum ProviderType {
|
||||
Cipher = 'CIPHER',
|
||||
Fallback = 'FALLBACK',
|
||||
}
|
||||
|
||||
export interface ZeroExInstantRequiredBaseConfig {
|
||||
orderSource: OrderSource;
|
||||
}
|
||||
|
||||
export interface ZeroExInstantOptionalBaseConfig {
|
||||
provider: Provider;
|
||||
walletDisplayName: string;
|
||||
availableAssetDatas: string[];
|
||||
defaultAssetBuyAmount: number;
|
||||
defaultSelectedAssetData: string;
|
||||
additionalAssetMetaDataMap: ObjectMap<AssetMetaData>;
|
||||
networkId: Network;
|
||||
affiliateInfo: AffiliateInfo;
|
||||
shouldDisableAnalyticsTracking: boolean;
|
||||
}
|
||||
|
||||
export type ZeroExInstantBaseConfig = ZeroExInstantRequiredBaseConfig & Partial<ZeroExInstantOptionalBaseConfig>;
|
||||
|
@ -2,7 +2,7 @@ import { BuyQuote } from '@0x/asset-buyer';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { GIT_SHA, HEAP_ENABLED, INSTANT_DISCHARGE_TARGET, NPM_PACKAGE_VERSION } from '../constants';
|
||||
import { GIT_SHA, HEAP_ENABLED, INSTANT_DISCHARGE_TARGET, NODE_ENV, NPM_PACKAGE_VERSION } from '../constants';
|
||||
import {
|
||||
AffiliateInfo,
|
||||
Asset,
|
||||
@ -106,6 +106,7 @@ export interface AnalyticsEventOptions {
|
||||
ethAddress?: string;
|
||||
networkId?: number;
|
||||
providerName?: string;
|
||||
providerDisplayName?: string;
|
||||
gitSha?: string;
|
||||
npmVersion?: string;
|
||||
instantEnvironment?: string;
|
||||
@ -149,6 +150,7 @@ export const analytics = {
|
||||
embeddedUrl: window.location.href,
|
||||
networkId: network,
|
||||
providerName: providerState.name,
|
||||
providerDisplayName: providerState.displayName,
|
||||
gitSha: GIT_SHA,
|
||||
npmVersion: NPM_PACKAGE_VERSION,
|
||||
orderSource: orderSourceName,
|
||||
@ -156,7 +158,7 @@ export const analytics = {
|
||||
affiliateFeePercent,
|
||||
selectedAssetName: selectedAsset ? selectedAsset.metaData.name : 'none',
|
||||
selectedAssetData: selectedAsset ? selectedAsset.assetData : 'none',
|
||||
instantEnvironment: INSTANT_DISCHARGE_TARGET || `Local ${process.env.NODE_ENV}`,
|
||||
instantEnvironment: INSTANT_DISCHARGE_TARGET || `Local ${NODE_ENV}`,
|
||||
};
|
||||
return eventOptions;
|
||||
},
|
||||
|
@ -26,7 +26,7 @@ export const assetUtils = {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
assetData,
|
||||
assetData: assetData.toLowerCase(),
|
||||
metaData,
|
||||
};
|
||||
},
|
||||
@ -36,7 +36,7 @@ export const assetUtils = {
|
||||
network: Network,
|
||||
): Asset => {
|
||||
return {
|
||||
assetData,
|
||||
assetData: assetData.toLowerCase(),
|
||||
metaData: assetUtils.getMetaDataOrThrow(assetData, assetMetaDataMap, network),
|
||||
};
|
||||
},
|
||||
|
@ -38,14 +38,11 @@ export const buyQuoteUpdater = {
|
||||
} catch (error) {
|
||||
const errorMessage = assetUtils.assetBuyerErrorMessage(asset, error);
|
||||
|
||||
if (_.isUndefined(errorMessage)) {
|
||||
// This is an unknown error, report it to rollbar
|
||||
errorReporter.report(error);
|
||||
}
|
||||
analytics.trackQuoteError(error.message ? error.message : 'other', baseUnitValue, fetchOrigin);
|
||||
|
||||
if (options.dispatchErrors) {
|
||||
dispatch(actions.setQuoteRequestStateFailure());
|
||||
analytics.trackQuoteError(error.message ? error.message : 'other', baseUnitValue, fetchOrigin);
|
||||
errorFlasher.flashNewErrorMessage(dispatch, errorMessage || 'Error fetching price, please try again');
|
||||
}
|
||||
return;
|
||||
|
@ -62,4 +62,11 @@ export const envUtil = {
|
||||
}
|
||||
return PROVIDER_TYPE_TO_NAME[providerTypeIfExists];
|
||||
},
|
||||
getProviderDisplayName(provider: Provider): string {
|
||||
const providerTypeIfExists = envUtil.getProviderType(provider);
|
||||
if (_.isUndefined(providerTypeIfExists)) {
|
||||
return 'Wallet';
|
||||
}
|
||||
return PROVIDER_TYPE_TO_NAME[providerTypeIfExists];
|
||||
},
|
||||
};
|
||||
|
@ -1,17 +1,34 @@
|
||||
import { logUtils } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { GIT_SHA, HOST_DOMAINS, INSTANT_DISCHARGE_TARGET, ROLLBAR_CLIENT_TOKEN, ROLLBAR_ENABLED } from '../constants';
|
||||
import {
|
||||
GIT_SHA,
|
||||
HOST_DOMAINS_EXTERNAL,
|
||||
HOST_DOMAINS_LOCAL,
|
||||
INSTANT_DISCHARGE_TARGET,
|
||||
NODE_ENV,
|
||||
ROLLBAR_CLIENT_TOKEN,
|
||||
ROLLBAR_ENABLED,
|
||||
} from '../constants';
|
||||
|
||||
// Import version of Rollbar designed for embedded components
|
||||
// See https://docs.rollbar.com/docs/using-rollbarjs-inside-an-embedded-component
|
||||
// tslint:disable-next-line:no-var-requires
|
||||
const Rollbar = require('rollbar/dist/rollbar.noconflict.umd');
|
||||
|
||||
const getRollbarHostDomains = (): string[] => {
|
||||
if (NODE_ENV === 'development') {
|
||||
return HOST_DOMAINS_EXTERNAL.concat(HOST_DOMAINS_LOCAL);
|
||||
} else {
|
||||
return HOST_DOMAINS_EXTERNAL;
|
||||
}
|
||||
};
|
||||
|
||||
let rollbar: any;
|
||||
// Configures rollbar and sets up error catching
|
||||
export const setupRollbar = (): any => {
|
||||
if (_.isUndefined(rollbar) && ROLLBAR_CLIENT_TOKEN && ROLLBAR_ENABLED) {
|
||||
const hostDomains = getRollbarHostDomains();
|
||||
rollbar = new Rollbar({
|
||||
accessToken: ROLLBAR_CLIENT_TOKEN,
|
||||
captureUncaught: true,
|
||||
@ -20,7 +37,7 @@ export const setupRollbar = (): any => {
|
||||
itemsPerMinute: 10,
|
||||
maxItems: 500,
|
||||
payload: {
|
||||
environment: INSTANT_DISCHARGE_TARGET || `Local ${process.env.NODE_ENV}`,
|
||||
environment: INSTANT_DISCHARGE_TARGET || `Local ${NODE_ENV}`,
|
||||
client: {
|
||||
javascript: {
|
||||
source_map_enabled: true,
|
||||
@ -29,7 +46,7 @@ export const setupRollbar = (): any => {
|
||||
},
|
||||
},
|
||||
},
|
||||
hostWhiteList: HOST_DOMAINS,
|
||||
hostWhiteList: hostDomains,
|
||||
uncaughtErrorLevel: 'error',
|
||||
ignoredMessages: [
|
||||
// Errors from the third-party scripts
|
||||
|
@ -10,27 +10,40 @@ import { assetBuyerFactory } from './asset_buyer_factory';
|
||||
import { providerFactory } from './provider_factory';
|
||||
|
||||
export const providerStateFactory = {
|
||||
getInitialProviderState: (orderSource: OrderSource, network: Network, provider?: Provider): ProviderState => {
|
||||
getInitialProviderState: (
|
||||
orderSource: OrderSource,
|
||||
network: Network,
|
||||
provider?: Provider,
|
||||
walletDisplayName?: string,
|
||||
): ProviderState => {
|
||||
if (!_.isUndefined(provider)) {
|
||||
return providerStateFactory.getInitialProviderStateFromProvider(orderSource, network, provider);
|
||||
return providerStateFactory.getInitialProviderStateFromProvider(
|
||||
orderSource,
|
||||
network,
|
||||
provider,
|
||||
walletDisplayName,
|
||||
);
|
||||
}
|
||||
const providerStateFromWindowIfExits = providerStateFactory.getInitialProviderStateFromWindowIfExists(
|
||||
orderSource,
|
||||
network,
|
||||
walletDisplayName,
|
||||
);
|
||||
if (providerStateFromWindowIfExits) {
|
||||
return providerStateFromWindowIfExits;
|
||||
} else {
|
||||
return providerStateFactory.getInitialProviderStateFallback(orderSource, network);
|
||||
return providerStateFactory.getInitialProviderStateFallback(orderSource, network, walletDisplayName);
|
||||
}
|
||||
},
|
||||
getInitialProviderStateFromProvider: (
|
||||
orderSource: OrderSource,
|
||||
network: Network,
|
||||
provider: Provider,
|
||||
walletDisplayName?: string,
|
||||
): ProviderState => {
|
||||
const providerState: ProviderState = {
|
||||
name: envUtil.getProviderName(provider),
|
||||
displayName: walletDisplayName || envUtil.getProviderDisplayName(provider),
|
||||
provider,
|
||||
web3Wrapper: new Web3Wrapper(provider),
|
||||
assetBuyer: assetBuyerFactory.getAssetBuyer(provider, orderSource, network),
|
||||
@ -38,11 +51,16 @@ export const providerStateFactory = {
|
||||
};
|
||||
return providerState;
|
||||
},
|
||||
getInitialProviderStateFromWindowIfExists: (orderSource: OrderSource, network: Network): Maybe<ProviderState> => {
|
||||
getInitialProviderStateFromWindowIfExists: (
|
||||
orderSource: OrderSource,
|
||||
network: Network,
|
||||
walletDisplayName?: string,
|
||||
): Maybe<ProviderState> => {
|
||||
const injectedProviderIfExists = providerFactory.getInjectedProviderIfExists();
|
||||
if (!_.isUndefined(injectedProviderIfExists)) {
|
||||
const providerState: ProviderState = {
|
||||
name: envUtil.getProviderName(injectedProviderIfExists),
|
||||
displayName: walletDisplayName || envUtil.getProviderDisplayName(injectedProviderIfExists),
|
||||
provider: injectedProviderIfExists,
|
||||
web3Wrapper: new Web3Wrapper(injectedProviderIfExists),
|
||||
assetBuyer: assetBuyerFactory.getAssetBuyer(injectedProviderIfExists, orderSource, network),
|
||||
@ -53,10 +71,15 @@ export const providerStateFactory = {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
getInitialProviderStateFallback: (orderSource: OrderSource, network: Network): ProviderState => {
|
||||
getInitialProviderStateFallback: (
|
||||
orderSource: OrderSource,
|
||||
network: Network,
|
||||
walletDisplayName?: string,
|
||||
): ProviderState => {
|
||||
const provider = providerFactory.getFallbackNoSigningProvider(network);
|
||||
const providerState: ProviderState = {
|
||||
name: 'Fallback',
|
||||
displayName: walletDisplayName || envUtil.getProviderDisplayName(provider),
|
||||
provider,
|
||||
web3Wrapper: new Web3Wrapper(provider),
|
||||
assetBuyer: assetBuyerFactory.getAssetBuyer(provider, orderSource, network),
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1544482891,
|
||||
"version": "2.1.3",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1542821676,
|
||||
"version": "2.1.2",
|
||||
|
@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v2.1.3 - _December 10, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.1.2 - _November 21, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1544482891,
|
||||
"version": "2.2.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "2.2.0",
|
||||
"changes": [
|
||||
|
@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v2.2.1 - _December 10, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.2.0 - _November 28, 2018_
|
||||
|
||||
* Add CLI `0x-migrate` for running the 0x migrations in a language-agnostic way (#1324)
|
||||
|
@ -98,7 +98,7 @@ async function testInstallPackageAsync(
|
||||
const lastChangelogVersion = JSON.parse(fs.readFileSync(changelogPath).toString())[0].version;
|
||||
const packageName = installablePackage.packageJson.name;
|
||||
utils.log(`Testing ${packageName}@${lastChangelogVersion}`);
|
||||
const packageDirName = path.join(...(packageName + '-test').split('/'));
|
||||
const packageDirName = path.join(...`${packageName}-test`.split('/'));
|
||||
// NOTE(fabio): The `testDirectory` needs to be somewhere **outside** the monorepo root directory.
|
||||
// Otherwise, it will have access to the hoisted `node_modules` directory and the Typescript missing
|
||||
// type errors will not be caught.
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1544482891,
|
||||
"version": "3.0.5",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1543401373,
|
||||
"version": "3.0.4",
|
||||
|
@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v3.0.5 - _December 10, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v3.0.4 - _November 28, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
@ -70,7 +70,7 @@ describe('Order hashing', () => {
|
||||
});
|
||||
it('returns true if order hash is correct', () => {
|
||||
const orderHashLength = 65;
|
||||
const isValid = orderHashUtils.isValidOrderHash('0x' + Array(orderHashLength).join('0'));
|
||||
const isValid = orderHashUtils.isValidOrderHash(`0x${Array(orderHashLength).join('0')}`);
|
||||
expect(isValid).to.be.true();
|
||||
});
|
||||
});
|
||||
|
@ -1,4 +1,13 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1544482891,
|
||||
"version": "2.2.7",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Dependencies updated"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"timestamp": 1543401373,
|
||||
"version": "2.2.6",
|
||||
|
@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v2.2.7 - _December 10, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
||||
## v2.2.6 - _November 28, 2018_
|
||||
|
||||
* Dependencies updated
|
||||
|
7
packages/pipeline/.npmignore
Normal file
7
packages/pipeline/.npmignore
Normal file
@ -0,0 +1,7 @@
|
||||
.*
|
||||
yarn-error.log
|
||||
/scripts/
|
||||
/generated_docs/
|
||||
/src/
|
||||
tsconfig.json
|
||||
/lib/monorepo_scripts/
|
166
packages/pipeline/README.md
Normal file
166
packages/pipeline/README.md
Normal file
@ -0,0 +1,166 @@
|
||||
## @0xproject/pipeline
|
||||
|
||||
This repository contains scripts used for scraping data from the Ethereum blockchain into SQL tables for analysis by the 0x team.
|
||||
|
||||
## Contributing
|
||||
|
||||
We strongly recommend that the community help us make improvements and determine the future direction of the protocol. 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:
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
yarn build
|
||||
```
|
||||
|
||||
### Clean
|
||||
|
||||
```bash
|
||||
yarn clean
|
||||
```
|
||||
|
||||
### Lint
|
||||
|
||||
```bash
|
||||
yarn lint
|
||||
```
|
||||
|
||||
### Migrations
|
||||
|
||||
Create a new migration: `yarn migrate:create --name MigrationNameInCamelCase`
|
||||
Run migrations: `yarn migrate:run`
|
||||
Revert the most recent migration (CAUTION: may result in data loss!): `yarn migrate:revert`
|
||||
|
||||
## Testing
|
||||
|
||||
There are several test scripts in **package.json**. You can run all the tests
|
||||
with `yarn test:all` or run certain tests seprately by following the
|
||||
instructions below. Some tests may not work out of the box on certain platforms
|
||||
or operating systems (see the "Database tests" section below).
|
||||
|
||||
### Unit tests
|
||||
|
||||
The unit tests can be run with `yarn test`. These tests don't depend on any
|
||||
services or databases and will run in any environment that can run Node.
|
||||
|
||||
### Database tests
|
||||
|
||||
Database integration tests can be run with `yarn test:db`. These tests will
|
||||
attempt to automatically spin up a Postgres database via Docker. If this doesn't
|
||||
work you have two other options:
|
||||
|
||||
1. Set the `DOCKER_SOCKET` environment variable to a valid socket path to use
|
||||
for communicating with Docker.
|
||||
2. Start Postgres manually and set the `ZEROEX_DATA_PIPELINE_TEST_DB_URL`
|
||||
environment variable. If this is set, the tests will use your existing
|
||||
Postgres database instead of trying to create one with Docker.
|
||||
|
||||
## Running locally
|
||||
|
||||
`pipeline` requires access to a PostgreSQL database. The easiest way to start
|
||||
Postgres is via Docker. Depending on your platform, you may need to prepend
|
||||
`sudo` to the following command:
|
||||
|
||||
```
|
||||
docker run --rm -d -p 5432:5432 --name pipeline_postgres postgres:11-alpine
|
||||
```
|
||||
|
||||
This will start a Postgres server with the default username and database name
|
||||
(`postgres` and `postgres`). You should set the environment variable as follows:
|
||||
|
||||
```
|
||||
export ZEROEX_DATA_PIPELINE_DB_URL=postgresql://postgres@localhost/postgres
|
||||
```
|
||||
|
||||
First thing you will need to do is run the migrations:
|
||||
|
||||
```
|
||||
yarn migrate:run
|
||||
```
|
||||
|
||||
Now you can run scripts locally:
|
||||
|
||||
```
|
||||
node packages/pipeline/lib/src/scripts/pull_radar_relay_orders.js
|
||||
```
|
||||
|
||||
To stop the Postgres server (you may need to add `sudo`):
|
||||
|
||||
```
|
||||
docker stop pipeline_postgres
|
||||
```
|
||||
|
||||
This will remove all data from the database.
|
||||
|
||||
If you prefer, you can also install Postgres with e.g.,
|
||||
[Homebrew](https://wiki.postgresql.org/wiki/Homebrew) or
|
||||
[Postgress.app](https://postgresapp.com/). Keep in mind that you will need to
|
||||
set the`ZEROEX_DATA_PIPELINE_DB_URL` environment variable to a valid
|
||||
[PostgreSQL connection url](https://stackoverflow.com/questions/3582552/postgresql-connection-url)
|
||||
|
||||
## Directory structure
|
||||
|
||||
```
|
||||
.
|
||||
├── lib: Code generated by the TypeScript compiler. Don't edit this directly.
|
||||
├── migrations: Code for creating and updating database schemas.
|
||||
├── node_modules:
|
||||
├── src: All TypeScript source code.
|
||||
│ ├── data_sources: Code responsible for getting raw data, typically from a third-party source.
|
||||
│ ├── entities: TypeORM entities which closely mirror our database schemas. Some other ORMs call these "models".
|
||||
│ ├── parsers: Code for converting raw data into entities.
|
||||
│ ├── scripts: Executable scripts which put all the pieces together.
|
||||
│ └── utils: Various utils used across packages/files.
|
||||
├── test: All tests go here and are organized in the same way as the folder/file that they test.
|
||||
```
|
||||
|
||||
## Adding new data to the pipeline
|
||||
|
||||
1. Create an entity in the _entities_ directory. Entities directly mirror our
|
||||
database schemas. We follow the practice of having "dumb" entities, so
|
||||
entity classes should typically not have any methods.
|
||||
2. Create a migration using the `yarn migrate:create` command. Create/update
|
||||
tables as needed. Remember to fill in both the `up` and `down` methods. Try
|
||||
to avoid data loss as much as possible in your migrations.
|
||||
3. Add basic tests for your entity and migrations to the **test/entities/**
|
||||
directory.
|
||||
4. Create a class or function in the **data_sources/** directory for getting
|
||||
raw data. This code should abstract away pagination and rate-limiting as
|
||||
much as possible.
|
||||
5. Create a class or function in the **parsers/** directory for converting the
|
||||
raw data into an entity. Also add tests in the **tests/** directory to test
|
||||
the parser.
|
||||
6. Create an executable script in the **scripts/** directory for putting
|
||||
everything together. Your script can accept environment variables for things
|
||||
like API keys. It should pull the data, parse it, and save it to the
|
||||
database. Scripts should be idempotent and atomic (when possible). What this
|
||||
means is that your script may be responsible for determining _which_ data
|
||||
needs to be updated. For example, you may need to query the database to find
|
||||
the most recent block number that we have already pulled, then pull new data
|
||||
starting from that block number.
|
||||
7. Run the migrations and then run your new script locally and verify it works
|
||||
as expected.
|
||||
|
||||
#### Additional guidelines and tips:
|
||||
|
||||
* Table names should be plural and separated by underscores (e.g.,
|
||||
`exchange_fill_events`).
|
||||
* Any table which contains data which comes directly from a third-party source
|
||||
should be namespaced in the `raw` PostgreSQL schema.
|
||||
* Column names in the database should be separated by underscores (e.g.,
|
||||
`maker_asset_type`).
|
||||
* Field names in entity classes (like any other fields in TypeScript) should
|
||||
be camel-cased (e.g., `makerAssetType`).
|
||||
* All timestamps should be stored as milliseconds since the Unix Epoch.
|
||||
* Use the `BigNumber` type for TypeScript code which deals with 256-bit
|
||||
numbers from smart contracts or for any case where we are dealing with large
|
||||
floating point numbers.
|
||||
* [TypeORM documentation](http://typeorm.io/#/) is pretty robust and can be a
|
||||
helpful resource.
|
0
packages/pipeline/coverage/.gitkeep
Normal file
0
packages/pipeline/coverage/.gitkeep
Normal file
187
packages/pipeline/migrations/1542070840010-InitialSchema.ts
Normal file
187
packages/pipeline/migrations/1542070840010-InitialSchema.ts
Normal file
@ -0,0 +1,187 @@
|
||||
import { MigrationInterface, QueryRunner, Table } from 'typeorm';
|
||||
|
||||
const blocks = new Table({
|
||||
name: 'raw.blocks',
|
||||
columns: [
|
||||
{ name: 'number', type: 'bigint', isPrimary: true },
|
||||
{ name: 'hash', type: 'varchar', isPrimary: true },
|
||||
{ name: 'timestamp', type: 'bigint' },
|
||||
],
|
||||
});
|
||||
|
||||
const exchange_cancel_events = new Table({
|
||||
name: 'raw.exchange_cancel_events',
|
||||
columns: [
|
||||
{ name: 'contract_address', type: 'char(42)', isPrimary: true },
|
||||
{ name: 'log_index', type: 'integer', isPrimary: true },
|
||||
{ name: 'block_number', type: 'bigint', isPrimary: true },
|
||||
|
||||
{ name: 'raw_data', type: 'varchar' },
|
||||
|
||||
{ name: 'transaction_hash', type: 'varchar' },
|
||||
{ name: 'maker_address', type: 'char(42)' },
|
||||
{ name: 'taker_address', type: 'char(42)' },
|
||||
{ name: 'fee_recipient_address', type: 'char(42)' },
|
||||
{ name: 'sender_address', type: 'char(42)' },
|
||||
{ name: 'order_hash', type: 'varchar' },
|
||||
|
||||
{ name: 'raw_maker_asset_data', type: 'varchar' },
|
||||
{ name: 'maker_asset_type', type: 'varchar' },
|
||||
{ name: 'maker_asset_proxy_id', type: 'varchar' },
|
||||
{ name: 'maker_token_address', type: 'char(42)' },
|
||||
{ name: 'maker_token_id', type: 'varchar', isNullable: true },
|
||||
{ name: 'raw_taker_asset_data', type: 'varchar' },
|
||||
{ name: 'taker_asset_type', type: 'varchar' },
|
||||
{ name: 'taker_asset_proxy_id', type: 'varchar' },
|
||||
{ name: 'taker_token_address', type: 'char(42)' },
|
||||
{ name: 'taker_token_id', type: 'varchar', isNullable: true },
|
||||
],
|
||||
});
|
||||
|
||||
const exchange_cancel_up_to_events = new Table({
|
||||
name: 'raw.exchange_cancel_up_to_events',
|
||||
columns: [
|
||||
{ name: 'contract_address', type: 'char(42)', isPrimary: true },
|
||||
{ name: 'log_index', type: 'integer', isPrimary: true },
|
||||
{ name: 'block_number', type: 'bigint', isPrimary: true },
|
||||
|
||||
{ name: 'raw_data', type: 'varchar' },
|
||||
|
||||
{ name: 'transaction_hash', type: 'varchar' },
|
||||
{ name: 'maker_address', type: 'char(42)' },
|
||||
{ name: 'sender_address', type: 'char(42)' },
|
||||
{ name: 'order_epoch', type: 'varchar' },
|
||||
],
|
||||
});
|
||||
|
||||
const exchange_fill_events = new Table({
|
||||
name: 'raw.exchange_fill_events',
|
||||
columns: [
|
||||
{ name: 'contract_address', type: 'char(42)', isPrimary: true },
|
||||
{ name: 'log_index', type: 'integer', isPrimary: true },
|
||||
{ name: 'block_number', type: 'bigint', isPrimary: true },
|
||||
|
||||
{ name: 'raw_data', type: 'varchar' },
|
||||
|
||||
{ name: 'transaction_hash', type: 'varchar' },
|
||||
{ name: 'maker_address', type: 'char(42)' },
|
||||
{ name: 'taker_address', type: 'char(42)' },
|
||||
{ name: 'fee_recipient_address', type: 'char(42)' },
|
||||
{ name: 'sender_address', type: 'char(42)' },
|
||||
{ name: 'maker_asset_filled_amount', type: 'varchar' },
|
||||
{ name: 'taker_asset_filled_amount', type: 'varchar' },
|
||||
{ name: 'maker_fee_paid', type: 'varchar' },
|
||||
{ name: 'taker_fee_paid', type: 'varchar' },
|
||||
{ name: 'order_hash', type: 'varchar' },
|
||||
|
||||
{ name: 'raw_maker_asset_data', type: 'varchar' },
|
||||
{ name: 'maker_asset_type', type: 'varchar' },
|
||||
{ name: 'maker_asset_proxy_id', type: 'varchar' },
|
||||
{ name: 'maker_token_address', type: 'char(42)' },
|
||||
{ name: 'maker_token_id', type: 'varchar', isNullable: true },
|
||||
{ name: 'raw_taker_asset_data', type: 'varchar' },
|
||||
{ name: 'taker_asset_type', type: 'varchar' },
|
||||
{ name: 'taker_asset_proxy_id', type: 'varchar' },
|
||||
{ name: 'taker_token_address', type: 'char(42)' },
|
||||
{ name: 'taker_token_id', type: 'varchar', isNullable: true },
|
||||
],
|
||||
});
|
||||
|
||||
const relayers = new Table({
|
||||
name: 'raw.relayers',
|
||||
columns: [
|
||||
{ name: 'uuid', type: 'varchar', isPrimary: true },
|
||||
{ name: 'name', type: 'varchar' },
|
||||
{ name: 'sra_http_endpoint', type: 'varchar', isNullable: true },
|
||||
{ name: 'sra_ws_endpoint', type: 'varchar', isNullable: true },
|
||||
{ name: 'app_url', type: 'varchar', isNullable: true },
|
||||
{ name: 'fee_recipient_addresses', type: 'char(42)', isArray: true },
|
||||
{ name: 'taker_addresses', type: 'char(42)', isArray: true },
|
||||
],
|
||||
});
|
||||
|
||||
const sra_orders = new Table({
|
||||
name: 'raw.sra_orders',
|
||||
columns: [
|
||||
{ name: 'exchange_address', type: 'char(42)', isPrimary: true },
|
||||
{ name: 'order_hash_hex', type: 'varchar', isPrimary: true },
|
||||
|
||||
{ name: 'source_url', type: 'varchar' },
|
||||
{ name: 'last_updated_timestamp', type: 'bigint' },
|
||||
{ name: 'first_seen_timestamp', type: 'bigint' },
|
||||
|
||||
{ name: 'maker_address', type: 'char(42)' },
|
||||
{ name: 'taker_address', type: 'char(42)' },
|
||||
{ name: 'fee_recipient_address', type: 'char(42)' },
|
||||
{ name: 'sender_address', type: 'char(42)' },
|
||||
{ name: 'maker_asset_filled_amount', type: 'varchar' },
|
||||
{ name: 'taker_asset_filled_amount', type: 'varchar' },
|
||||
{ name: 'maker_fee', type: 'varchar' },
|
||||
{ name: 'taker_fee', type: 'varchar' },
|
||||
{ name: 'expiration_time_seconds', type: 'int' },
|
||||
{ name: 'salt', type: 'varchar' },
|
||||
{ name: 'signature', type: 'varchar' },
|
||||
|
||||
{ name: 'raw_maker_asset_data', type: 'varchar' },
|
||||
{ name: 'maker_asset_type', type: 'varchar' },
|
||||
{ name: 'maker_asset_proxy_id', type: 'varchar' },
|
||||
{ name: 'maker_token_address', type: 'char(42)' },
|
||||
{ name: 'maker_token_id', type: 'varchar', isNullable: true },
|
||||
{ name: 'raw_taker_asset_data', type: 'varchar' },
|
||||
{ name: 'taker_asset_type', type: 'varchar' },
|
||||
{ name: 'taker_asset_proxy_id', type: 'varchar' },
|
||||
{ name: 'taker_token_address', type: 'char(42)' },
|
||||
{ name: 'taker_token_id', type: 'varchar', isNullable: true },
|
||||
|
||||
{ name: 'metadata_json', type: 'varchar' },
|
||||
],
|
||||
});
|
||||
|
||||
const token_on_chain_metadata = new Table({
|
||||
name: 'raw.token_on_chain_metadata',
|
||||
columns: [
|
||||
{ name: 'address', type: 'char(42)', isPrimary: true },
|
||||
{ name: 'decimals', type: 'integer' },
|
||||
{ name: 'symbol', type: 'varchar' },
|
||||
{ name: 'name', type: 'varchar' },
|
||||
],
|
||||
});
|
||||
|
||||
const transactions = new Table({
|
||||
name: 'raw.transactions',
|
||||
columns: [
|
||||
{ name: 'block_number', type: 'bigint', isPrimary: true },
|
||||
{ name: 'block_hash', type: 'varchar', isPrimary: true },
|
||||
{ name: 'transaction_hash', type: 'varchar', isPrimary: true },
|
||||
{ name: 'gas_used', type: 'bigint' },
|
||||
{ name: 'gas_price', type: 'bigint' },
|
||||
],
|
||||
});
|
||||
|
||||
export class InitialSchema1542070840010 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.createSchema('raw');
|
||||
|
||||
await queryRunner.createTable(blocks);
|
||||
await queryRunner.createTable(exchange_cancel_events);
|
||||
await queryRunner.createTable(exchange_cancel_up_to_events);
|
||||
await queryRunner.createTable(exchange_fill_events);
|
||||
await queryRunner.createTable(relayers);
|
||||
await queryRunner.createTable(sra_orders);
|
||||
await queryRunner.createTable(token_on_chain_metadata);
|
||||
await queryRunner.createTable(transactions);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.dropTable(blocks.name);
|
||||
await queryRunner.dropTable(exchange_cancel_events.name);
|
||||
await queryRunner.dropTable(exchange_cancel_up_to_events.name);
|
||||
await queryRunner.dropTable(exchange_fill_events.name);
|
||||
await queryRunner.dropTable(relayers.name);
|
||||
await queryRunner.dropTable(sra_orders.name);
|
||||
await queryRunner.dropTable(token_on_chain_metadata.name);
|
||||
await queryRunner.dropTable(transactions.name);
|
||||
|
||||
await queryRunner.dropSchema('raw');
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
import { MigrationInterface, QueryRunner, Table } from 'typeorm';
|
||||
|
||||
export class NewSraOrderTimestampFormat1542147915364 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE raw.sra_orders
|
||||
DROP CONSTRAINT "PK_09bfb9980715329563bd53d667e",
|
||||
ADD PRIMARY KEY (order_hash_hex, exchange_address, source_url);
|
||||
`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE raw.sra_orders_observed_timestamps (
|
||||
order_hash_hex varchar NOT NULL,
|
||||
exchange_address varchar NOT NULL,
|
||||
source_url varchar NOT NULL,
|
||||
observed_timestamp bigint NOT NULL,
|
||||
FOREIGN KEY
|
||||
(order_hash_hex, exchange_address, source_url)
|
||||
REFERENCES raw.sra_orders (order_hash_hex, exchange_address, source_url),
|
||||
PRIMARY KEY (order_hash_hex, exchange_address, source_url, observed_timestamp)
|
||||
);`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE raw.sra_orders
|
||||
DROP COLUMN last_updated_timestamp,
|
||||
DROP COLUMN first_seen_timestamp;`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.dropTable('raw.sra_orders_observed_timestamps');
|
||||
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE raw.sra_orders
|
||||
ADD COLUMN last_updated_timestamp bigint NOT NULL DEFAULT 0,
|
||||
ADD COLUMN first_seen_timestamp bigint NOT NULL DEFAULT 0;`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE raw.sra_orders
|
||||
DROP CONSTRAINT sra_orders_pkey,
|
||||
ADD CONSTRAINT "PK_09bfb9980715329563bd53d667e" PRIMARY KEY ("exchange_address", "order_hash_hex");
|
||||
`,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class RenameSraOrdersFilledAmounts1542152278484 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.renameColumn('raw.sra_orders', 'maker_asset_filled_amount', 'maker_asset_amount');
|
||||
await queryRunner.renameColumn('raw.sra_orders', 'taker_asset_filled_amount', 'taker_asset_amount');
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.renameColumn('raw.sra_orders', 'maker_asset_amount', 'maker_asset_filled_amount');
|
||||
await queryRunner.renameColumn('raw.sra_orders', 'taker_asset_amount', 'taker_asset_filled_amount');
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class ConvertBigNumberToNumeric1542234704666 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE raw.exchange_fill_events
|
||||
ALTER COLUMN maker_asset_filled_amount TYPE numeric USING maker_asset_filled_amount::numeric,
|
||||
ALTER COLUMN taker_asset_filled_amount TYPE numeric USING taker_asset_filled_amount::numeric,
|
||||
ALTER COLUMN maker_fee_paid TYPE numeric USING maker_fee_paid::numeric,
|
||||
ALTER COLUMN taker_fee_paid TYPE numeric USING taker_fee_paid::numeric;`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE raw.exchange_cancel_up_to_events
|
||||
ALTER COLUMN order_epoch TYPE numeric USING order_epoch::numeric;`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE raw.sra_orders
|
||||
ALTER COLUMN maker_asset_amount TYPE numeric USING maker_asset_amount::numeric,
|
||||
ALTER COLUMN taker_asset_amount TYPE numeric USING taker_asset_amount::numeric,
|
||||
ALTER COLUMN maker_fee TYPE numeric USING maker_fee::numeric,
|
||||
ALTER COLUMN taker_fee TYPE numeric USING taker_fee::numeric,
|
||||
ALTER COLUMN expiration_time_seconds TYPE numeric USING expiration_time_seconds::numeric,
|
||||
ALTER COLUMN salt TYPE numeric USING salt::numeric;`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE raw.sra_orders
|
||||
ALTER COLUMN maker_asset_amount TYPE varchar USING maker_asset_amount::varchar,
|
||||
ALTER COLUMN taker_asset_amount TYPE varchar USING taker_asset_amount::varchar,
|
||||
ALTER COLUMN maker_fee TYPE varchar USING maker_fee::varchar,
|
||||
ALTER COLUMN taker_fee TYPE varchar USING taker_fee::varchar,
|
||||
ALTER COLUMN expiration_time_seconds TYPE varchar USING expiration_time_seconds::varchar,
|
||||
ALTER COLUMN salt TYPE varchar USING salt::varchar;`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE raw.exchange_cancel_up_to_events
|
||||
ALTER COLUMN order_epoch TYPE varchar USING order_epoch::varchar;`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE raw.exchange_fill_events
|
||||
ALTER COLUMN maker_asset_filled_amount TYPE varchar USING maker_asset_filled_amount::varchar,
|
||||
ALTER COLUMN taker_asset_filled_amount TYPE varchar USING taker_asset_filled_amount::varchar,
|
||||
ALTER COLUMN maker_fee_paid TYPE varchar USING maker_fee_paid::varchar,
|
||||
ALTER COLUMN taker_fee_paid TYPE varchar USING taker_fee_paid::varchar;`,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
|
||||
|
||||
export class AddHomepageUrlToRelayers1542249766882 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.addColumn(
|
||||
'raw.relayers',
|
||||
new TableColumn({ name: 'homepage_url', type: 'varchar', default: `'unknown'` }),
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.dropColumn('raw.relayers', 'homepage_url');
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class MakeTakerAddressNullable1542401122477 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE raw.exchange_cancel_events
|
||||
ALTER COLUMN taker_address DROP NOT NULL;`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE raw.exchange_cancel_events
|
||||
ALTER COLUMN taker_address SET NOT NULL;`,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class NewMetadataAndOHLCVTables1542655823221 implements MigrationInterface {
|
||||
// tslint:disable-next-line
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE raw.token_metadata (
|
||||
address VARCHAR NOT NULL,
|
||||
authority VARCHAR NOT NULL,
|
||||
decimals INT NULL,
|
||||
symbol VARCHAR NULL,
|
||||
name VARCHAR NULL,
|
||||
|
||||
PRIMARY KEY (address, authority)
|
||||
);
|
||||
`);
|
||||
|
||||
await queryRunner.dropTable('raw.token_on_chain_metadata');
|
||||
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE raw.ohlcv_external (
|
||||
exchange VARCHAR NOT NULL,
|
||||
from_symbol VARCHAR NOT NULL,
|
||||
to_symbol VARCHAR NOT NULL,
|
||||
start_time BIGINT NOT NULL,
|
||||
end_time BIGINT NOT NULL,
|
||||
|
||||
open DOUBLE PRECISION NOT NULL,
|
||||
close DOUBLE PRECISION NOT NULL,
|
||||
low DOUBLE PRECISION NOT NULL,
|
||||
high DOUBLE PRECISION NOT NULL,
|
||||
volume_from DOUBLE PRECISION NOT NULL,
|
||||
volume_to DOUBLE PRECISION NOT NULL,
|
||||
|
||||
source VARCHAR NOT NULL,
|
||||
observed_timestamp BIGINT NOT NULL,
|
||||
|
||||
PRIMARY KEY (exchange, from_symbol, to_symbol, start_time, end_time, source, observed_timestamp)
|
||||
);
|
||||
`);
|
||||
}
|
||||
|
||||
// tslint:disable-next-line
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE raw.token_on_chain_metadata (
|
||||
address VARCHAR NOT NULL,
|
||||
decimals INT NULL,
|
||||
symbol VARCHAR NULL,
|
||||
name VARCHAR NULL,
|
||||
|
||||
PRIMARY KEY (address)
|
||||
);
|
||||
`);
|
||||
|
||||
await queryRunner.dropTable('raw.token_metadata');
|
||||
|
||||
await queryRunner.dropTable('raw.ohlcv_external');
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
import { MigrationInterface, QueryRunner, Table } from 'typeorm';
|
||||
|
||||
const tokenOrderbookSnapshots = new Table({
|
||||
name: 'raw.token_orderbook_snapshots',
|
||||
columns: [
|
||||
{ name: 'observed_timestamp', type: 'bigint', isPrimary: true },
|
||||
{ name: 'source', type: 'varchar', isPrimary: true },
|
||||
{ name: 'order_type', type: 'order_t' },
|
||||
{ name: 'price', type: 'numeric', isPrimary: true },
|
||||
|
||||
{ name: 'base_asset_symbol', type: 'varchar', isPrimary: true },
|
||||
{ name: 'base_asset_address', type: 'char(42)' },
|
||||
{ name: 'base_volume', type: 'numeric' },
|
||||
|
||||
{ name: 'quote_asset_symbol', type: 'varchar', isPrimary: true },
|
||||
{ name: 'quote_asset_address', type: 'char(42)' },
|
||||
{ name: 'quote_volume', type: 'numeric' },
|
||||
],
|
||||
});
|
||||
|
||||
export class TokenOrderbookSnapshots1543434472116 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(`CREATE TYPE order_t AS enum('bid', 'ask');`);
|
||||
await queryRunner.createTable(tokenOrderbookSnapshots);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.dropTable(tokenOrderbookSnapshots.name);
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import { MigrationInterface, QueryRunner, Table } from 'typeorm';
|
||||
|
||||
const dexTrades = new Table({
|
||||
name: 'raw.dex_trades',
|
||||
columns: [
|
||||
{ name: 'source_url', type: 'varchar', isPrimary: true },
|
||||
{ name: 'tx_hash', type: 'varchar', isPrimary: true },
|
||||
|
||||
{ name: 'tx_timestamp', type: 'bigint' },
|
||||
{ name: 'tx_date', type: 'varchar' },
|
||||
{ name: 'tx_sender', type: 'varchar(42)' },
|
||||
{ name: 'smart_contract_id', type: 'bigint' },
|
||||
{ name: 'smart_contract_address', type: 'varchar(42)' },
|
||||
{ name: 'contract_type', type: 'varchar' },
|
||||
{ name: 'maker', type: 'varchar(42)' },
|
||||
{ name: 'taker', type: 'varchar(42)' },
|
||||
{ name: 'amount_buy', type: 'numeric' },
|
||||
{ name: 'maker_fee_amount', type: 'numeric' },
|
||||
{ name: 'buy_currency_id', type: 'bigint' },
|
||||
{ name: 'buy_symbol', type: 'varchar' },
|
||||
{ name: 'amount_sell', type: 'numeric' },
|
||||
{ name: 'taker_fee_amount', type: 'numeric' },
|
||||
{ name: 'sell_currency_id', type: 'bigint' },
|
||||
{ name: 'sell_symbol', type: 'varchar' },
|
||||
{ name: 'maker_annotation', type: 'varchar' },
|
||||
{ name: 'taker_annotation', type: 'varchar' },
|
||||
{ name: 'protocol', type: 'varchar' },
|
||||
{ name: 'buy_address', type: 'varchar(42)', isNullable: true },
|
||||
{ name: 'sell_address', type: 'varchar(42)', isNullable: true },
|
||||
],
|
||||
});
|
||||
|
||||
export class CreateDexTrades1543446690436 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.createTable(dexTrades);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.dropTable(dexTrades);
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class ConvertTokenMetadataDecimalsToBigNumber1543980079179 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE raw.token_metadata
|
||||
ALTER COLUMN decimals TYPE numeric USING decimals::numeric;`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE raw.token_metadata
|
||||
ALTER COLUMN decimals TYPE numeric USING decimals::integer;`,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class ConvertTransactionGasPriceToBigNumber1543983324954 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE raw.transactions
|
||||
ALTER COLUMN gas_price TYPE numeric USING gas_price::numeric,
|
||||
ALTER COLUMN gas_used TYPE numeric USING gas_used::numeric;`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE raw.transactions
|
||||
ALTER COLUMN gas_price TYPE numeric USING gas_price::bigint,
|
||||
ALTER COLUMN gas_used TYPE numeric USING gas_used::bigint;`,
|
||||
);
|
||||
}
|
||||
}
|
65
packages/pipeline/package.json
Normal file
65
packages/pipeline/package.json
Normal file
@ -0,0 +1,65 @@
|
||||
{
|
||||
"name": "@0x/pipeline",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "Data pipeline for offline analysis",
|
||||
"scripts": {
|
||||
"build": "yarn tsc -b",
|
||||
"build:ci": "yarn build",
|
||||
"test": "yarn run_mocha",
|
||||
"rebuild_and_test": "run-s build test:all",
|
||||
"test:db": "yarn run_mocha:db",
|
||||
"test:all": "run-s test test:db",
|
||||
"test:circleci": "yarn test:coverage",
|
||||
"run_mocha": "mocha --require source-map-support/register --require make-promises-safe 'lib/test/!(entities)/**/*_test.js' --bail --exit",
|
||||
"run_mocha:db": "mocha --require source-map-support/register --require make-promises-safe lib/test/db_global_hooks.js 'lib/test/entities/*_test.js' --bail --exit --timeout 60000",
|
||||
"test:coverage": "nyc npm run test:all --all && yarn coverage:report:lcov",
|
||||
"coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info",
|
||||
"clean": "shx rm -rf lib",
|
||||
"lint": "tslint --project . --format stylish --exclude ./migrations/**/*",
|
||||
"migrate:run": "yarn typeorm migration:run --config ./lib/src/ormconfig",
|
||||
"migrate:revert": "yarn typeorm migration:revert --config ./lib/src/ormconfig",
|
||||
"migrate:create": "yarn typeorm migration:create --config ./lib/src/ormconfig --dir migrations"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/0xProject/0x-monorepo"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@0x/tslint-config": "^1.0.9",
|
||||
"@types/axios": "^0.14.0",
|
||||
"@types/ramda": "^0.25.38",
|
||||
"chai": "^4.1.2",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"chai-bignumber": "^2.0.2",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"mocha": "^5.2.0",
|
||||
"tslint": "5.11.0",
|
||||
"typescript": "3.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/connect": "^3.0.2",
|
||||
"@0x/contract-artifacts": "^1.0.1",
|
||||
"@0x/contract-wrappers": "^3.0.0",
|
||||
"@0x/dev-utils": "^1.0.13",
|
||||
"@0x/order-utils": "^2.0.0",
|
||||
"@0x/subproviders": "^2.1.0",
|
||||
"@0x/types": "^1.2.0",
|
||||
"@0x/utils": "^2.0.3",
|
||||
"@0x/web3-wrapper": "^3.1.0",
|
||||
"@types/dockerode": "^2.5.9",
|
||||
"@types/p-limit": "^2.0.0",
|
||||
"async-parallel": "^1.2.3",
|
||||
"axios": "^0.18.0",
|
||||
"bottleneck": "^2.13.2",
|
||||
"dockerode": "^2.5.7",
|
||||
"ethereum-types": "^1.0.6",
|
||||
"pg": "^7.5.0",
|
||||
"prettier": "^1.15.3",
|
||||
"ramda": "^0.25.0",
|
||||
"reflect-metadata": "^0.1.12",
|
||||
"sqlite3": "^4.0.2",
|
||||
"typeorm": "^0.2.7"
|
||||
}
|
||||
}
|
133
packages/pipeline/src/data_sources/bloxy/index.ts
Normal file
133
packages/pipeline/src/data_sources/bloxy/index.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import axios from 'axios';
|
||||
import * as R from 'ramda';
|
||||
|
||||
// URL to use for getting dex trades from Bloxy.
|
||||
export const BLOXY_DEX_TRADES_URL = 'https://bloxy.info/api/dex/trades';
|
||||
// Number of trades to get at once. Must be less than or equal to MAX_OFFSET.
|
||||
const TRADES_PER_QUERY = 10000;
|
||||
// Maximum offset supported by the Bloxy API.
|
||||
const MAX_OFFSET = 100000;
|
||||
// Buffer to subtract from offset. This means we will request some trades twice
|
||||
// but we have less chance on missing out on any data.
|
||||
const OFFSET_BUFFER = 1000;
|
||||
// Maximum number of days supported by the Bloxy API.
|
||||
const MAX_DAYS = 30;
|
||||
// Buffer used for comparing the last seen timestamp to the last returned
|
||||
// timestamp. Increasing this reduces chances of data loss but also creates more
|
||||
// redundancy and can impact performance.
|
||||
// tslint:disable-next-line:custom-no-magic-numbers
|
||||
const LAST_SEEN_TIMESTAMP_BUFFER_MS = 1000 * 60 * 30; // 30 minutes
|
||||
|
||||
// tslint:disable-next-line:custom-no-magic-numbers
|
||||
const millisecondsPerDay = 1000 * 60 * 60 * 24; // ms/d = ms/s * s/m * m/h * h/d
|
||||
|
||||
export interface BloxyTrade {
|
||||
tx_hash: string;
|
||||
tx_time: string;
|
||||
tx_date: string;
|
||||
tx_sender: string;
|
||||
smart_contract_id: number;
|
||||
smart_contract_address: string;
|
||||
contract_type: string;
|
||||
maker: string;
|
||||
taker: string;
|
||||
amountBuy: number;
|
||||
makerFee: number;
|
||||
buyCurrencyId: number;
|
||||
buySymbol: string;
|
||||
amountSell: number;
|
||||
takerFee: number;
|
||||
sellCurrencyId: number;
|
||||
sellSymbol: string;
|
||||
maker_annotation: string;
|
||||
taker_annotation: string;
|
||||
protocol: string;
|
||||
buyAddress: string | null;
|
||||
sellAddress: string | null;
|
||||
}
|
||||
|
||||
interface BloxyError {
|
||||
error: string;
|
||||
}
|
||||
|
||||
type BloxyResponse<T> = T | BloxyError;
|
||||
type BloxyTradeResponse = BloxyResponse<BloxyTrade[]>;
|
||||
|
||||
function isError<T>(response: BloxyResponse<T>): response is BloxyError {
|
||||
return (response as BloxyError).error !== undefined;
|
||||
}
|
||||
|
||||
export class BloxySource {
|
||||
private readonly _apiKey: string;
|
||||
|
||||
constructor(apiKey: string) {
|
||||
this._apiKey = apiKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all latest trades between the lastSeenTimestamp (minus some buffer)
|
||||
* and the current time. Note that because the Bloxy API has some hard
|
||||
* limits it might not always be possible to get *all* the trades in the
|
||||
* desired time range.
|
||||
* @param lastSeenTimestamp The latest timestamp for trades that have
|
||||
* already been seen.
|
||||
*/
|
||||
public async getDexTradesAsync(lastSeenTimestamp: number): Promise<BloxyTrade[]> {
|
||||
let allTrades: BloxyTrade[] = [];
|
||||
|
||||
// Clamp numberOfDays so that it is always between 1 and MAX_DAYS (inclusive)
|
||||
const numberOfDays = R.clamp(1, MAX_DAYS, getDaysSinceTimestamp(lastSeenTimestamp));
|
||||
|
||||
// Keep getting trades until we hit one of the following conditions:
|
||||
//
|
||||
// 1. Offset hits MAX_OFFSET (we can't go back any further).
|
||||
// 2. There are no more trades in the response.
|
||||
// 3. We see a tx_time equal to or earlier than lastSeenTimestamp (plus
|
||||
// some buffer).
|
||||
//
|
||||
for (let offset = 0; offset <= MAX_OFFSET; offset += TRADES_PER_QUERY - OFFSET_BUFFER) {
|
||||
const trades = await this._getTradesWithOffsetAsync(numberOfDays, offset);
|
||||
if (trades.length === 0) {
|
||||
// There are no more trades left for the days we are querying.
|
||||
// This means we are done.
|
||||
return filterDuplicateTrades(allTrades);
|
||||
}
|
||||
const sortedTrades = R.reverse(R.sortBy(trade => trade.tx_time, trades));
|
||||
allTrades = allTrades.concat(sortedTrades);
|
||||
|
||||
// Check if lastReturnedTimestamp < lastSeenTimestamp
|
||||
const lastReturnedTimestamp = new Date(sortedTrades[0].tx_time).getTime();
|
||||
if (lastReturnedTimestamp < lastSeenTimestamp - LAST_SEEN_TIMESTAMP_BUFFER_MS) {
|
||||
// We are at the point where we have already seen trades for the
|
||||
// timestamp range that is being returned. We're done.
|
||||
return filterDuplicateTrades(allTrades);
|
||||
}
|
||||
}
|
||||
return filterDuplicateTrades(allTrades);
|
||||
}
|
||||
|
||||
private async _getTradesWithOffsetAsync(numberOfDays: number, offset: number): Promise<BloxyTrade[]> {
|
||||
const resp = await axios.get<BloxyTradeResponse>(BLOXY_DEX_TRADES_URL, {
|
||||
params: {
|
||||
key: this._apiKey,
|
||||
days: numberOfDays,
|
||||
limit: TRADES_PER_QUERY,
|
||||
offset,
|
||||
},
|
||||
});
|
||||
if (isError(resp.data)) {
|
||||
throw new Error(`Error in Bloxy API response: ${resp.data.error}`);
|
||||
}
|
||||
return resp.data;
|
||||
}
|
||||
}
|
||||
|
||||
// Computes the number of days between the given timestamp and the current
|
||||
// timestamp (rounded up).
|
||||
function getDaysSinceTimestamp(timestamp: number): number {
|
||||
const msSinceTimestamp = Date.now() - timestamp;
|
||||
const daysSinceTimestamp = msSinceTimestamp / millisecondsPerDay;
|
||||
return Math.ceil(daysSinceTimestamp);
|
||||
}
|
||||
|
||||
const filterDuplicateTrades = R.uniqBy((trade: BloxyTrade) => trade.tx_hash);
|
@ -0,0 +1,85 @@
|
||||
import {
|
||||
ContractWrappers,
|
||||
ExchangeCancelEventArgs,
|
||||
ExchangeCancelUpToEventArgs,
|
||||
ExchangeEventArgs,
|
||||
ExchangeEvents,
|
||||
ExchangeFillEventArgs,
|
||||
ExchangeWrapper,
|
||||
} from '@0x/contract-wrappers';
|
||||
import { Web3ProviderEngine } from '@0x/subproviders';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import { LogWithDecodedArgs } from 'ethereum-types';
|
||||
|
||||
import { EXCHANGE_START_BLOCK } from '../../utils';
|
||||
|
||||
const BLOCK_FINALITY_THRESHOLD = 10; // When to consider blocks as final. Used to compute default toBlock.
|
||||
const NUM_BLOCKS_PER_QUERY = 20000; // Number of blocks to query for events at a time.
|
||||
|
||||
export class ExchangeEventsSource {
|
||||
private readonly _exchangeWrapper: ExchangeWrapper;
|
||||
private readonly _web3Wrapper: Web3Wrapper;
|
||||
constructor(provider: Web3ProviderEngine, networkId: number) {
|
||||
this._web3Wrapper = new Web3Wrapper(provider);
|
||||
const contractWrappers = new ContractWrappers(provider, { networkId });
|
||||
this._exchangeWrapper = contractWrappers.exchange;
|
||||
}
|
||||
|
||||
public async getFillEventsAsync(
|
||||
fromBlock?: number,
|
||||
toBlock?: number,
|
||||
): Promise<Array<LogWithDecodedArgs<ExchangeFillEventArgs>>> {
|
||||
return this._getEventsAsync<ExchangeFillEventArgs>(ExchangeEvents.Fill, fromBlock, toBlock);
|
||||
}
|
||||
|
||||
public async getCancelEventsAsync(
|
||||
fromBlock?: number,
|
||||
toBlock?: number,
|
||||
): Promise<Array<LogWithDecodedArgs<ExchangeCancelEventArgs>>> {
|
||||
return this._getEventsAsync<ExchangeCancelEventArgs>(ExchangeEvents.Cancel, fromBlock, toBlock);
|
||||
}
|
||||
|
||||
public async getCancelUpToEventsAsync(
|
||||
fromBlock?: number,
|
||||
toBlock?: number,
|
||||
): Promise<Array<LogWithDecodedArgs<ExchangeCancelUpToEventArgs>>> {
|
||||
return this._getEventsAsync<ExchangeCancelUpToEventArgs>(ExchangeEvents.CancelUpTo, fromBlock, toBlock);
|
||||
}
|
||||
|
||||
private async _getEventsAsync<ArgsType extends ExchangeEventArgs>(
|
||||
eventName: ExchangeEvents,
|
||||
fromBlock: number = EXCHANGE_START_BLOCK,
|
||||
toBlock?: number,
|
||||
): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
|
||||
const calculatedToBlock =
|
||||
toBlock === undefined
|
||||
? (await this._web3Wrapper.getBlockNumberAsync()) - BLOCK_FINALITY_THRESHOLD
|
||||
: toBlock;
|
||||
let events: Array<LogWithDecodedArgs<ArgsType>> = [];
|
||||
for (let currFromBlock = fromBlock; currFromBlock <= calculatedToBlock; currFromBlock += NUM_BLOCKS_PER_QUERY) {
|
||||
events = events.concat(
|
||||
await this._getEventsForRangeAsync<ArgsType>(
|
||||
eventName,
|
||||
currFromBlock,
|
||||
Math.min(currFromBlock + NUM_BLOCKS_PER_QUERY - 1, calculatedToBlock),
|
||||
),
|
||||
);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
private async _getEventsForRangeAsync<ArgsType extends ExchangeEventArgs>(
|
||||
eventName: ExchangeEvents,
|
||||
fromBlock: number,
|
||||
toBlock: number,
|
||||
): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
|
||||
return this._exchangeWrapper.getLogsAsync<ArgsType>(
|
||||
eventName,
|
||||
{
|
||||
fromBlock,
|
||||
toBlock,
|
||||
},
|
||||
{},
|
||||
);
|
||||
}
|
||||
}
|
78
packages/pipeline/src/data_sources/ddex/index.ts
Normal file
78
packages/pipeline/src/data_sources/ddex/index.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { fetchAsync, logUtils } from '@0x/utils';
|
||||
|
||||
const DDEX_BASE_URL = 'https://api.ddex.io/v2';
|
||||
const ACTIVE_MARKETS_URL = `${DDEX_BASE_URL}/markets`;
|
||||
const NO_AGGREGATION_LEVEL = 3; // See https://docs.ddex.io/#get-orderbook
|
||||
const ORDERBOOK_ENDPOINT = `/orderbook?level=${NO_AGGREGATION_LEVEL}`;
|
||||
export const DDEX_SOURCE = 'ddex';
|
||||
|
||||
export interface DdexActiveMarketsResponse {
|
||||
status: number;
|
||||
desc: string;
|
||||
data: {
|
||||
markets: DdexMarket[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface DdexMarket {
|
||||
id: string;
|
||||
quoteToken: string;
|
||||
quoteTokenDecimals: number;
|
||||
quoteTokenAddress: string;
|
||||
baseToken: string;
|
||||
baseTokenDecimals: number;
|
||||
baseTokenAddress: string;
|
||||
minOrderSize: string;
|
||||
maxOrderSize: string;
|
||||
pricePrecision: number;
|
||||
priceDecimals: number;
|
||||
amountDecimals: number;
|
||||
}
|
||||
|
||||
export interface DdexOrderbookResponse {
|
||||
status: number;
|
||||
desc: string;
|
||||
data: {
|
||||
orderBook: DdexOrderbook;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DdexOrderbook {
|
||||
marketId: string;
|
||||
bids: DdexOrder[];
|
||||
asks: DdexOrder[];
|
||||
}
|
||||
|
||||
export interface DdexOrder {
|
||||
price: string;
|
||||
amount: string;
|
||||
orderId: string;
|
||||
}
|
||||
|
||||
// tslint:disable:prefer-function-over-method
|
||||
// ^ Keep consistency with other sources and help logical organization
|
||||
export class DdexSource {
|
||||
/**
|
||||
* Call Ddex API to find out which markets they are maintaining orderbooks for.
|
||||
*/
|
||||
public async getActiveMarketsAsync(): Promise<DdexMarket[]> {
|
||||
logUtils.log('Getting all active DDEX markets');
|
||||
const resp = await fetchAsync(ACTIVE_MARKETS_URL);
|
||||
const respJson: DdexActiveMarketsResponse = await resp.json();
|
||||
const markets = respJson.data.markets;
|
||||
logUtils.log(`Got ${markets.length} markets.`);
|
||||
return markets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve orderbook from Ddex API for a given market.
|
||||
* @param marketId String identifying the market we want data for. Eg. 'REP/AUG'
|
||||
*/
|
||||
public async getMarketOrderbookAsync(marketId: string): Promise<DdexOrderbook> {
|
||||
logUtils.log(`${marketId}: Retrieving orderbook.`);
|
||||
const marketOrderbookUrl = `${ACTIVE_MARKETS_URL}/${marketId}${ORDERBOOK_ENDPOINT}`;
|
||||
const resp = await fetchAsync(marketOrderbookUrl);
|
||||
const respJson: DdexOrderbookResponse = await resp.json();
|
||||
return respJson.data.orderBook;
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
// tslint:disable:no-duplicate-imports
|
||||
import { fetchAsync } from '@0x/utils';
|
||||
import Bottleneck from 'bottleneck';
|
||||
import { stringify } from 'querystring';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { TradingPair } from '../../utils/get_ohlcv_trading_pairs';
|
||||
|
||||
export interface CryptoCompareOHLCVResponse {
|
||||
Data: CryptoCompareOHLCVRecord[];
|
||||
Response: string;
|
||||
Message: string;
|
||||
Type: number;
|
||||
}
|
||||
|
||||
export interface CryptoCompareOHLCVRecord {
|
||||
time: number; // in seconds, not milliseconds
|
||||
close: number;
|
||||
high: number;
|
||||
low: number;
|
||||
open: number;
|
||||
volumefrom: number;
|
||||
volumeto: number;
|
||||
}
|
||||
|
||||
export interface CryptoCompareOHLCVParams {
|
||||
fsym: string;
|
||||
tsym: string;
|
||||
e?: string;
|
||||
aggregate?: string;
|
||||
aggregatePredictableTimePeriods?: boolean;
|
||||
limit?: number;
|
||||
toTs?: number;
|
||||
}
|
||||
|
||||
const ONE_HOUR = 60 * 60 * 1000; // tslint:disable-line:custom-no-magic-numbers
|
||||
const ONE_SECOND = 1000;
|
||||
const ONE_HOUR_AGO = new Date().getTime() - ONE_HOUR;
|
||||
const HTTP_OK_STATUS = 200;
|
||||
const CRYPTO_COMPARE_VALID_EMPTY_RESPONSE_TYPE = 96;
|
||||
const MAX_PAGE_SIZE = 2000;
|
||||
|
||||
export class CryptoCompareOHLCVSource {
|
||||
public readonly intervalBetweenRecords = ONE_HOUR;
|
||||
public readonly defaultExchange = 'CCCAGG';
|
||||
public readonly interval = this.intervalBetweenRecords * MAX_PAGE_SIZE; // the hourly API returns data for one interval at a time
|
||||
private readonly _url: string = 'https://min-api.cryptocompare.com/data/histohour?';
|
||||
|
||||
// rate-limit for all API calls through this class instance
|
||||
private readonly _limiter: Bottleneck;
|
||||
constructor(maxReqsPerSecond: number) {
|
||||
this._limiter = new Bottleneck({
|
||||
minTime: ONE_SECOND / maxReqsPerSecond,
|
||||
reservoir: 30,
|
||||
reservoirRefreshAmount: 30,
|
||||
reservoirRefreshInterval: ONE_SECOND,
|
||||
});
|
||||
}
|
||||
|
||||
// gets OHLCV records starting from pair.latest
|
||||
public async getHourlyOHLCVAsync(pair: TradingPair): Promise<CryptoCompareOHLCVRecord[]> {
|
||||
const params = {
|
||||
e: this.defaultExchange,
|
||||
fsym: pair.fromSymbol,
|
||||
tsym: pair.toSymbol,
|
||||
limit: MAX_PAGE_SIZE,
|
||||
toTs: Math.floor((pair.latestSavedTime + this.interval) / ONE_SECOND), // CryptoCompare uses timestamp in seconds. not ms
|
||||
};
|
||||
const url = this._url + stringify(params);
|
||||
const response = await this._limiter.schedule(() => fetchAsync(url));
|
||||
if (response.status !== HTTP_OK_STATUS) {
|
||||
throw new Error(`HTTP error while scraping Crypto Compare: [${response}]`);
|
||||
}
|
||||
const json: CryptoCompareOHLCVResponse = await response.json();
|
||||
if (
|
||||
(json.Response === 'Error' || json.Data.length === 0) &&
|
||||
json.Type !== CRYPTO_COMPARE_VALID_EMPTY_RESPONSE_TYPE
|
||||
) {
|
||||
throw new Error(JSON.stringify(json));
|
||||
}
|
||||
return json.Data.filter(rec => {
|
||||
return (
|
||||
// Crypto Compare takes ~30 mins to finalise records
|
||||
rec.time * ONE_SECOND < ONE_HOUR_AGO && rec.time * ONE_SECOND > pair.latestSavedTime && hasData(rec)
|
||||
);
|
||||
});
|
||||
}
|
||||
public generateBackfillIntervals(pair: TradingPair): TradingPair[] {
|
||||
const now = new Date().getTime();
|
||||
const f = (p: TradingPair): false | [TradingPair, TradingPair] => {
|
||||
if (p.latestSavedTime > now) {
|
||||
return false;
|
||||
} else {
|
||||
return [p, R.merge(p, { latestSavedTime: p.latestSavedTime + this.interval })];
|
||||
}
|
||||
};
|
||||
return R.unfold(f, pair);
|
||||
}
|
||||
}
|
||||
|
||||
function hasData(record: CryptoCompareOHLCVRecord): boolean {
|
||||
return (
|
||||
record.close !== 0 ||
|
||||
record.open !== 0 ||
|
||||
record.high !== 0 ||
|
||||
record.low !== 0 ||
|
||||
record.volumefrom !== 0 ||
|
||||
record.volumeto !== 0
|
||||
);
|
||||
}
|
92
packages/pipeline/src/data_sources/paradex/index.ts
Normal file
92
packages/pipeline/src/data_sources/paradex/index.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { fetchAsync, logUtils } from '@0x/utils';
|
||||
|
||||
const PARADEX_BASE_URL = 'https://api.paradex.io/consumer/v0';
|
||||
const ACTIVE_MARKETS_URL = `${PARADEX_BASE_URL}/markets`;
|
||||
const ORDERBOOK_ENDPOINT = `${PARADEX_BASE_URL}/orderbook`;
|
||||
const TOKEN_INFO_ENDPOINT = `${PARADEX_BASE_URL}/tokens`;
|
||||
export const PARADEX_SOURCE = 'paradex';
|
||||
|
||||
export type ParadexActiveMarketsResponse = ParadexMarket[];
|
||||
|
||||
export interface ParadexMarket {
|
||||
id: string;
|
||||
symbol: string;
|
||||
baseToken: string;
|
||||
quoteToken: string;
|
||||
minOrderSize: string;
|
||||
maxOrderSize: string;
|
||||
priceMaxDecimals: number;
|
||||
amountMaxDecimals: number;
|
||||
// These are not native to the Paradex API response. We tag them on later
|
||||
// by calling the token endpoint and joining on symbol.
|
||||
baseTokenAddress?: string;
|
||||
quoteTokenAddress?: string;
|
||||
}
|
||||
|
||||
export interface ParadexOrderbookResponse {
|
||||
marketId: number;
|
||||
marketSymbol: string;
|
||||
bids: ParadexOrder[];
|
||||
asks: ParadexOrder[];
|
||||
}
|
||||
|
||||
export interface ParadexOrder {
|
||||
amount: string;
|
||||
price: string;
|
||||
}
|
||||
|
||||
export type ParadexTokenInfoResponse = ParadexTokenInfo[];
|
||||
|
||||
export interface ParadexTokenInfo {
|
||||
name: string;
|
||||
symbol: string;
|
||||
address: string;
|
||||
}
|
||||
|
||||
export class ParadexSource {
|
||||
private readonly _apiKey: string;
|
||||
|
||||
constructor(apiKey: string) {
|
||||
this._apiKey = apiKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call Paradex API to find out which markets they are maintaining orderbooks for.
|
||||
*/
|
||||
public async getActiveMarketsAsync(): Promise<ParadexActiveMarketsResponse> {
|
||||
logUtils.log('Getting all active Paradex markets.');
|
||||
const resp = await fetchAsync(ACTIVE_MARKETS_URL, {
|
||||
headers: { 'API-KEY': this._apiKey },
|
||||
});
|
||||
const markets: ParadexActiveMarketsResponse = await resp.json();
|
||||
logUtils.log(`Got ${markets.length} markets.`);
|
||||
return markets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call Paradex API to find out their token information.
|
||||
*/
|
||||
public async getTokenInfoAsync(): Promise<ParadexTokenInfoResponse> {
|
||||
logUtils.log('Getting token information from Paradex.');
|
||||
const resp = await fetchAsync(TOKEN_INFO_ENDPOINT, {
|
||||
headers: { 'API-KEY': this._apiKey },
|
||||
});
|
||||
const tokens: ParadexTokenInfoResponse = await resp.json();
|
||||
logUtils.log(`Got information for ${tokens.length} tokens.`);
|
||||
return tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve orderbook from Paradex API for a given market.
|
||||
* @param marketSymbol String representing the market we want data for.
|
||||
*/
|
||||
public async getMarketOrderbookAsync(marketSymbol: string): Promise<ParadexOrderbookResponse> {
|
||||
logUtils.log(`${marketSymbol}: Retrieving orderbook.`);
|
||||
const marketOrderbookUrl = `${ORDERBOOK_ENDPOINT}?market=${marketSymbol}`;
|
||||
const resp = await fetchAsync(marketOrderbookUrl, {
|
||||
headers: { 'API-KEY': this._apiKey },
|
||||
});
|
||||
const orderbookResponse: ParadexOrderbookResponse = await resp.json();
|
||||
return orderbookResponse;
|
||||
}
|
||||
}
|
33
packages/pipeline/src/data_sources/relayer-registry/index.ts
Normal file
33
packages/pipeline/src/data_sources/relayer-registry/index.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import axios from 'axios';
|
||||
|
||||
export interface RelayerResponse {
|
||||
name: string;
|
||||
homepage_url: string;
|
||||
app_url: string;
|
||||
header_img: string;
|
||||
logo_img: string;
|
||||
networks: RelayerResponseNetwork[];
|
||||
}
|
||||
|
||||
export interface RelayerResponseNetwork {
|
||||
networkId: number;
|
||||
sra_http_endpoint?: string;
|
||||
sra_ws_endpoint?: string;
|
||||
static_order_fields?: {
|
||||
fee_recipient_addresses?: string[];
|
||||
taker_addresses?: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export class RelayerRegistrySource {
|
||||
private readonly _url: string;
|
||||
|
||||
constructor(url: string) {
|
||||
this._url = url;
|
||||
}
|
||||
|
||||
public async getRelayerInfoAsync(): Promise<Map<string, RelayerResponse>> {
|
||||
const resp = await axios.get<Map<string, RelayerResponse>>(this._url);
|
||||
return resp.data;
|
||||
}
|
||||
}
|
29
packages/pipeline/src/data_sources/trusted_tokens/index.ts
Normal file
29
packages/pipeline/src/data_sources/trusted_tokens/index.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import axios from 'axios';
|
||||
|
||||
export interface ZeroExTrustedTokenMeta {
|
||||
address: string;
|
||||
name: string;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
}
|
||||
|
||||
export interface MetamaskTrustedTokenMeta {
|
||||
address: string;
|
||||
name: string;
|
||||
erc20: boolean;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
}
|
||||
|
||||
export class TrustedTokenSource<T> {
|
||||
private readonly _url: string;
|
||||
|
||||
constructor(url: string) {
|
||||
this._url = url;
|
||||
}
|
||||
|
||||
public async getTrustedTokenMetaAsync(): Promise<T> {
|
||||
const resp = await axios.get<T>(this._url);
|
||||
return resp.data;
|
||||
}
|
||||
}
|
22
packages/pipeline/src/data_sources/web3/index.ts
Normal file
22
packages/pipeline/src/data_sources/web3/index.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { Web3ProviderEngine } from '@0x/subproviders';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import { BlockWithoutTransactionData, Transaction } from 'ethereum-types';
|
||||
|
||||
export class Web3Source {
|
||||
private readonly _web3Wrapper: Web3Wrapper;
|
||||
constructor(provider: Web3ProviderEngine) {
|
||||
this._web3Wrapper = new Web3Wrapper(provider);
|
||||
}
|
||||
|
||||
public async getBlockInfoAsync(blockNumber: number): Promise<BlockWithoutTransactionData> {
|
||||
const block = await this._web3Wrapper.getBlockIfExistsAsync(blockNumber);
|
||||
if (block == null) {
|
||||
return Promise.reject(new Error(`Could not find block for given block number: ${blockNumber}`));
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
public async getTransactionInfoAsync(txHash: string): Promise<Transaction> {
|
||||
return this._web3Wrapper.getTransactionByHashAsync(txHash);
|
||||
}
|
||||
}
|
13
packages/pipeline/src/entities/block.ts
Normal file
13
packages/pipeline/src/entities/block.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
||||
|
||||
import { numberToBigIntTransformer } from '../utils';
|
||||
|
||||
@Entity({ name: 'blocks', schema: 'raw' })
|
||||
export class Block {
|
||||
@PrimaryColumn() public hash!: string;
|
||||
@PrimaryColumn({ transformer: numberToBigIntTransformer })
|
||||
public number!: number;
|
||||
|
||||
@Column({ name: 'timestamp', transformer: numberToBigIntTransformer })
|
||||
public timestamp!: number;
|
||||
}
|
54
packages/pipeline/src/entities/dex_trade.ts
Normal file
54
packages/pipeline/src/entities/dex_trade.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
||||
|
||||
import { bigNumberTransformer, numberToBigIntTransformer } from '../utils';
|
||||
|
||||
@Entity({ name: 'dex_trades', schema: 'raw' })
|
||||
export class DexTrade {
|
||||
@PrimaryColumn({ name: 'source_url' })
|
||||
public sourceUrl!: string;
|
||||
@PrimaryColumn({ name: 'tx_hash' })
|
||||
public txHash!: string;
|
||||
|
||||
@Column({ name: 'tx_timestamp', type: 'bigint', transformer: numberToBigIntTransformer })
|
||||
public txTimestamp!: number;
|
||||
@Column({ name: 'tx_date' })
|
||||
public txDate!: string;
|
||||
@Column({ name: 'tx_sender' })
|
||||
public txSender!: string;
|
||||
@Column({ name: 'smart_contract_id', type: 'bigint', transformer: numberToBigIntTransformer })
|
||||
public smartContractId!: number;
|
||||
@Column({ name: 'smart_contract_address' })
|
||||
public smartContractAddress!: string;
|
||||
@Column({ name: 'contract_type' })
|
||||
public contractType!: string;
|
||||
@Column({ type: 'varchar' })
|
||||
public maker!: string;
|
||||
@Column({ type: 'varchar' })
|
||||
public taker!: string;
|
||||
@Column({ name: 'amount_buy', type: 'numeric', transformer: bigNumberTransformer })
|
||||
public amountBuy!: BigNumber;
|
||||
@Column({ name: 'maker_fee_amount', type: 'numeric', transformer: bigNumberTransformer })
|
||||
public makerFeeAmount!: BigNumber;
|
||||
@Column({ name: 'buy_currency_id', type: 'bigint', transformer: numberToBigIntTransformer })
|
||||
public buyCurrencyId!: number;
|
||||
@Column({ name: 'buy_symbol' })
|
||||
public buySymbol!: string;
|
||||
@Column({ name: 'amount_sell', type: 'numeric', transformer: bigNumberTransformer })
|
||||
public amountSell!: BigNumber;
|
||||
@Column({ name: 'taker_fee_amount', type: 'numeric', transformer: bigNumberTransformer })
|
||||
public takerFeeAmount!: BigNumber;
|
||||
@Column({ name: 'sell_currency_id', type: 'bigint', transformer: numberToBigIntTransformer })
|
||||
public sellCurrencyId!: number;
|
||||
@Column({ name: 'sell_symbol' })
|
||||
public sellSymbol!: string;
|
||||
@Column({ name: 'maker_annotation' })
|
||||
public makerAnnotation!: string;
|
||||
@Column({ name: 'taker_annotation' })
|
||||
public takerAnnotation!: string;
|
||||
@Column() public protocol!: string;
|
||||
@Column({ name: 'buy_address', type: 'varchar', nullable: true })
|
||||
public buyAddress!: string | null;
|
||||
@Column({ name: 'sell_address', type: 'varchar', nullable: true })
|
||||
public sellAddress!: string | null;
|
||||
}
|
51
packages/pipeline/src/entities/exchange_cancel_event.ts
Normal file
51
packages/pipeline/src/entities/exchange_cancel_event.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
||||
|
||||
import { AssetType } from '../types';
|
||||
import { numberToBigIntTransformer } from '../utils';
|
||||
|
||||
@Entity({ name: 'exchange_cancel_events', schema: 'raw' })
|
||||
export class ExchangeCancelEvent {
|
||||
@PrimaryColumn({ name: 'contract_address' })
|
||||
public contractAddress!: string;
|
||||
@PrimaryColumn({ name: 'log_index' })
|
||||
public logIndex!: number;
|
||||
@PrimaryColumn({ name: 'block_number', transformer: numberToBigIntTransformer })
|
||||
public blockNumber!: number;
|
||||
|
||||
@Column({ name: 'raw_data' })
|
||||
public rawData!: string;
|
||||
|
||||
@Column({ name: 'transaction_hash' })
|
||||
public transactionHash!: string;
|
||||
@Column({ name: 'maker_address' })
|
||||
public makerAddress!: string;
|
||||
@Column({ nullable: true, type: String, name: 'taker_address' })
|
||||
public takerAddress!: string;
|
||||
@Column({ name: 'fee_recipient_address' })
|
||||
public feeRecipientAddress!: string;
|
||||
@Column({ name: 'sender_address' })
|
||||
public senderAddress!: string;
|
||||
@Column({ name: 'order_hash' })
|
||||
public orderHash!: string;
|
||||
|
||||
@Column({ name: 'raw_maker_asset_data' })
|
||||
public rawMakerAssetData!: string;
|
||||
@Column({ name: 'maker_asset_type' })
|
||||
public makerAssetType!: AssetType;
|
||||
@Column({ name: 'maker_asset_proxy_id' })
|
||||
public makerAssetProxyId!: string;
|
||||
@Column({ name: 'maker_token_address' })
|
||||
public makerTokenAddress!: string;
|
||||
@Column({ nullable: true, type: String, name: 'maker_token_id' })
|
||||
public makerTokenId!: string | null;
|
||||
@Column({ name: 'raw_taker_asset_data' })
|
||||
public rawTakerAssetData!: string;
|
||||
@Column({ name: 'taker_asset_type' })
|
||||
public takerAssetType!: AssetType;
|
||||
@Column({ name: 'taker_asset_proxy_id' })
|
||||
public takerAssetProxyId!: string;
|
||||
@Column({ name: 'taker_token_address' })
|
||||
public takerTokenAddress!: string;
|
||||
@Column({ nullable: true, type: String, name: 'taker_token_id' })
|
||||
public takerTokenId!: string | null;
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
||||
|
||||
import { bigNumberTransformer, numberToBigIntTransformer } from '../utils';
|
||||
|
||||
@Entity({ name: 'exchange_cancel_up_to_events', schema: 'raw' })
|
||||
export class ExchangeCancelUpToEvent {
|
||||
@PrimaryColumn({ name: 'contract_address' })
|
||||
public contractAddress!: string;
|
||||
@PrimaryColumn({ name: 'log_index' })
|
||||
public logIndex!: number;
|
||||
@PrimaryColumn({ name: 'block_number', transformer: numberToBigIntTransformer })
|
||||
public blockNumber!: number;
|
||||
|
||||
@Column({ name: 'raw_data' })
|
||||
public rawData!: string;
|
||||
|
||||
@Column({ name: 'transaction_hash' })
|
||||
public transactionHash!: string;
|
||||
@Column({ name: 'maker_address' })
|
||||
public makerAddress!: string;
|
||||
@Column({ name: 'sender_address' })
|
||||
public senderAddress!: string;
|
||||
@Column({ name: 'order_epoch', type: 'numeric', transformer: bigNumberTransformer })
|
||||
public orderEpoch!: BigNumber;
|
||||
}
|
60
packages/pipeline/src/entities/exchange_fill_event.ts
Normal file
60
packages/pipeline/src/entities/exchange_fill_event.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
||||
|
||||
import { AssetType } from '../types';
|
||||
import { bigNumberTransformer, numberToBigIntTransformer } from '../utils';
|
||||
|
||||
@Entity({ name: 'exchange_fill_events', schema: 'raw' })
|
||||
export class ExchangeFillEvent {
|
||||
@PrimaryColumn({ name: 'contract_address' })
|
||||
public contractAddress!: string;
|
||||
@PrimaryColumn({ name: 'log_index' })
|
||||
public logIndex!: number;
|
||||
@PrimaryColumn({ name: 'block_number', transformer: numberToBigIntTransformer })
|
||||
public blockNumber!: number;
|
||||
|
||||
@Column({ name: 'raw_data' })
|
||||
public rawData!: string;
|
||||
|
||||
@Column({ name: 'transaction_hash' })
|
||||
public transactionHash!: string;
|
||||
@Column({ name: 'maker_address' })
|
||||
public makerAddress!: string;
|
||||
@Column({ name: 'taker_address' })
|
||||
public takerAddress!: string;
|
||||
@Column({ name: 'fee_recipient_address' })
|
||||
public feeRecipientAddress!: string;
|
||||
@Column({ name: 'sender_address' })
|
||||
public senderAddress!: string;
|
||||
@Column({ name: 'maker_asset_filled_amount', type: 'numeric', transformer: bigNumberTransformer })
|
||||
public makerAssetFilledAmount!: BigNumber;
|
||||
@Column({ name: 'taker_asset_filled_amount', type: 'numeric', transformer: bigNumberTransformer })
|
||||
public takerAssetFilledAmount!: BigNumber;
|
||||
@Column({ name: 'maker_fee_paid', type: 'numeric', transformer: bigNumberTransformer })
|
||||
public makerFeePaid!: BigNumber;
|
||||
@Column({ name: 'taker_fee_paid', type: 'numeric', transformer: bigNumberTransformer })
|
||||
public takerFeePaid!: BigNumber;
|
||||
@Column({ name: 'order_hash' })
|
||||
public orderHash!: string;
|
||||
|
||||
@Column({ name: 'raw_maker_asset_data' })
|
||||
public rawMakerAssetData!: string;
|
||||
@Column({ name: 'maker_asset_type' })
|
||||
public makerAssetType!: AssetType;
|
||||
@Column({ name: 'maker_asset_proxy_id' })
|
||||
public makerAssetProxyId!: string;
|
||||
@Column({ name: 'maker_token_address' })
|
||||
public makerTokenAddress!: string;
|
||||
@Column({ nullable: true, type: String, name: 'maker_token_id' })
|
||||
public makerTokenId!: string | null;
|
||||
@Column({ name: 'raw_taker_asset_data' })
|
||||
public rawTakerAssetData!: string;
|
||||
@Column({ name: 'taker_asset_type' })
|
||||
public takerAssetType!: AssetType;
|
||||
@Column({ name: 'taker_asset_proxy_id' })
|
||||
public takerAssetProxyId!: string;
|
||||
@Column({ name: 'taker_token_address' })
|
||||
public takerTokenAddress!: string;
|
||||
@Column({ nullable: true, type: String, name: 'taker_token_id' })
|
||||
public takerTokenId!: string | null;
|
||||
}
|
18
packages/pipeline/src/entities/index.ts
Normal file
18
packages/pipeline/src/entities/index.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { ExchangeCancelEvent } from './exchange_cancel_event';
|
||||
import { ExchangeCancelUpToEvent } from './exchange_cancel_up_to_event';
|
||||
import { ExchangeFillEvent } from './exchange_fill_event';
|
||||
|
||||
export { Block } from './block';
|
||||
export { DexTrade } from './dex_trade';
|
||||
export { ExchangeCancelEvent } from './exchange_cancel_event';
|
||||
export { ExchangeCancelUpToEvent } from './exchange_cancel_up_to_event';
|
||||
export { ExchangeFillEvent } from './exchange_fill_event';
|
||||
export { OHLCVExternal } from './ohlcv_external';
|
||||
export { Relayer } from './relayer';
|
||||
export { SraOrder } from './sra_order';
|
||||
export { SraOrdersObservedTimeStamp, createObservedTimestampForOrder } from './sra_order_observed_timestamp';
|
||||
export { TokenMetadata } from './token_metadata';
|
||||
export { TokenOrderbookSnapshot } from './token_order';
|
||||
export { Transaction } from './transaction';
|
||||
|
||||
export type ExchangeEvent = ExchangeFillEvent | ExchangeCancelEvent | ExchangeCancelUpToEvent;
|
30
packages/pipeline/src/entities/ohlcv_external.ts
Normal file
30
packages/pipeline/src/entities/ohlcv_external.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
||||
|
||||
import { numberToBigIntTransformer } from '../utils';
|
||||
|
||||
@Entity({ name: 'ohlcv_external', schema: 'raw' })
|
||||
export class OHLCVExternal {
|
||||
@PrimaryColumn() public exchange!: string;
|
||||
|
||||
@PrimaryColumn({ name: 'from_symbol', type: 'varchar' })
|
||||
public fromSymbol!: string;
|
||||
@PrimaryColumn({ name: 'to_symbol', type: 'varchar' })
|
||||
public toSymbol!: string;
|
||||
@PrimaryColumn({ name: 'start_time', transformer: numberToBigIntTransformer })
|
||||
public startTime!: number;
|
||||
@PrimaryColumn({ name: 'end_time', transformer: numberToBigIntTransformer })
|
||||
public endTime!: number;
|
||||
|
||||
@Column() public open!: number;
|
||||
@Column() public close!: number;
|
||||
@Column() public low!: number;
|
||||
@Column() public high!: number;
|
||||
@Column({ name: 'volume_from' })
|
||||
public volumeFrom!: number;
|
||||
@Column({ name: 'volume_to' })
|
||||
public volumeTo!: number;
|
||||
|
||||
@PrimaryColumn() public source!: string;
|
||||
@PrimaryColumn({ name: 'observed_timestamp', transformer: numberToBigIntTransformer })
|
||||
public observedTimestamp!: number;
|
||||
}
|
21
packages/pipeline/src/entities/relayer.ts
Normal file
21
packages/pipeline/src/entities/relayer.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
||||
|
||||
@Entity({ name: 'relayers', schema: 'raw' })
|
||||
export class Relayer {
|
||||
@PrimaryColumn() public uuid!: string;
|
||||
|
||||
@Column() public name!: string;
|
||||
@Column({ name: 'homepage_url', type: 'varchar' })
|
||||
public homepageUrl!: string;
|
||||
@Column({ name: 'sra_http_endpoint', type: 'varchar', nullable: true })
|
||||
public sraHttpEndpoint!: string | null;
|
||||
@Column({ name: 'sra_ws_endpoint', type: 'varchar', nullable: true })
|
||||
public sraWsEndpoint!: string | null;
|
||||
@Column({ name: 'app_url', type: 'varchar', nullable: true })
|
||||
public appUrl!: string | null;
|
||||
|
||||
@Column({ name: 'fee_recipient_addresses', type: 'varchar', array: true })
|
||||
public feeRecipientAddresses!: string[];
|
||||
@Column({ name: 'taker_addresses', type: 'varchar', array: true })
|
||||
public takerAddresses!: string[];
|
||||
}
|
63
packages/pipeline/src/entities/sra_order.ts
Normal file
63
packages/pipeline/src/entities/sra_order.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
||||
|
||||
import { AssetType } from '../types';
|
||||
import { bigNumberTransformer } from '../utils';
|
||||
|
||||
@Entity({ name: 'sra_orders', schema: 'raw' })
|
||||
export class SraOrder {
|
||||
@PrimaryColumn({ name: 'exchange_address' })
|
||||
public exchangeAddress!: string;
|
||||
@PrimaryColumn({ name: 'order_hash_hex' })
|
||||
public orderHashHex!: string;
|
||||
@PrimaryColumn({ name: 'source_url' })
|
||||
public sourceUrl!: string;
|
||||
|
||||
@Column({ name: 'maker_address' })
|
||||
public makerAddress!: string;
|
||||
@Column({ name: 'taker_address' })
|
||||
public takerAddress!: string;
|
||||
@Column({ name: 'fee_recipient_address' })
|
||||
public feeRecipientAddress!: string;
|
||||
@Column({ name: 'sender_address' })
|
||||
public senderAddress!: string;
|
||||
@Column({ name: 'maker_asset_amount', type: 'numeric', transformer: bigNumberTransformer })
|
||||
public makerAssetAmount!: BigNumber;
|
||||
@Column({ name: 'taker_asset_amount', type: 'numeric', transformer: bigNumberTransformer })
|
||||
public takerAssetAmount!: BigNumber;
|
||||
@Column({ name: 'maker_fee', type: 'numeric', transformer: bigNumberTransformer })
|
||||
public makerFee!: BigNumber;
|
||||
@Column({ name: 'taker_fee', type: 'numeric', transformer: bigNumberTransformer })
|
||||
public takerFee!: BigNumber;
|
||||
@Column({ name: 'expiration_time_seconds', type: 'numeric', transformer: bigNumberTransformer })
|
||||
public expirationTimeSeconds!: BigNumber;
|
||||
@Column({ name: 'salt', type: 'numeric', transformer: bigNumberTransformer })
|
||||
public salt!: BigNumber;
|
||||
@Column({ name: 'signature' })
|
||||
public signature!: string;
|
||||
|
||||
@Column({ name: 'raw_maker_asset_data' })
|
||||
public rawMakerAssetData!: string;
|
||||
@Column({ name: 'maker_asset_type' })
|
||||
public makerAssetType!: AssetType;
|
||||
@Column({ name: 'maker_asset_proxy_id' })
|
||||
public makerAssetProxyId!: string;
|
||||
@Column({ name: 'maker_token_address' })
|
||||
public makerTokenAddress!: string;
|
||||
@Column({ nullable: true, type: String, name: 'maker_token_id' })
|
||||
public makerTokenId!: string | null;
|
||||
@Column({ name: 'raw_taker_asset_data' })
|
||||
public rawTakerAssetData!: string;
|
||||
@Column({ name: 'taker_asset_type' })
|
||||
public takerAssetType!: AssetType;
|
||||
@Column({ name: 'taker_asset_proxy_id' })
|
||||
public takerAssetProxyId!: string;
|
||||
@Column({ name: 'taker_token_address' })
|
||||
public takerTokenAddress!: string;
|
||||
@Column({ nullable: true, type: String, name: 'taker_token_id' })
|
||||
public takerTokenId!: string | null;
|
||||
|
||||
// TODO(albrow): Make this optional?
|
||||
@Column({ name: 'metadata_json' })
|
||||
public metadataJson!: string;
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
import { Entity, PrimaryColumn } from 'typeorm';
|
||||
|
||||
import { numberToBigIntTransformer } from '../utils';
|
||||
|
||||
import { SraOrder } from './sra_order';
|
||||
|
||||
@Entity({ name: 'sra_orders_observed_timestamps', schema: 'raw' })
|
||||
export class SraOrdersObservedTimeStamp {
|
||||
@PrimaryColumn({ name: 'exchange_address' })
|
||||
public exchangeAddress!: string;
|
||||
@PrimaryColumn({ name: 'order_hash_hex' })
|
||||
public orderHashHex!: string;
|
||||
@PrimaryColumn({ name: 'source_url' })
|
||||
public sourceUrl!: string;
|
||||
|
||||
@PrimaryColumn({ name: 'observed_timestamp', transformer: numberToBigIntTransformer })
|
||||
public observedTimestamp!: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new SraOrdersObservedTimeStamp for the given order based on the
|
||||
* current time.
|
||||
* @param order The order to generate a timestamp for.
|
||||
*/
|
||||
export function createObservedTimestampForOrder(
|
||||
order: SraOrder,
|
||||
observedTimestamp: number,
|
||||
): SraOrdersObservedTimeStamp {
|
||||
const observed = new SraOrdersObservedTimeStamp();
|
||||
observed.exchangeAddress = order.exchangeAddress;
|
||||
observed.orderHashHex = order.orderHashHex;
|
||||
observed.sourceUrl = order.sourceUrl;
|
||||
observed.observedTimestamp = observedTimestamp;
|
||||
return observed;
|
||||
}
|
22
packages/pipeline/src/entities/token_metadata.ts
Normal file
22
packages/pipeline/src/entities/token_metadata.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
||||
|
||||
import { bigNumberTransformer } from '../utils/transformers';
|
||||
|
||||
@Entity({ name: 'token_metadata', schema: 'raw' })
|
||||
export class TokenMetadata {
|
||||
@PrimaryColumn({ type: 'varchar', nullable: false })
|
||||
public address!: string;
|
||||
|
||||
@PrimaryColumn({ type: 'varchar', nullable: false })
|
||||
public authority!: string;
|
||||
|
||||
@Column({ type: 'numeric', transformer: bigNumberTransformer, nullable: true })
|
||||
public decimals!: BigNumber | null;
|
||||
|
||||
@Column({ type: 'varchar', nullable: true })
|
||||
public symbol!: string | null;
|
||||
|
||||
@Column({ type: 'varchar', nullable: true })
|
||||
public name!: string | null;
|
||||
}
|
29
packages/pipeline/src/entities/token_order.ts
Normal file
29
packages/pipeline/src/entities/token_order.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
||||
|
||||
import { OrderType } from '../types';
|
||||
import { bigNumberTransformer, numberToBigIntTransformer } from '../utils';
|
||||
|
||||
@Entity({ name: 'token_orderbook_snapshots', schema: 'raw' })
|
||||
export class TokenOrderbookSnapshot {
|
||||
@PrimaryColumn({ name: 'observed_timestamp', type: 'bigint', transformer: numberToBigIntTransformer })
|
||||
public observedTimestamp!: number;
|
||||
@PrimaryColumn({ name: 'source' })
|
||||
public source!: string;
|
||||
@Column({ name: 'order_type' })
|
||||
public orderType!: OrderType;
|
||||
@PrimaryColumn({ name: 'price', type: 'numeric', transformer: bigNumberTransformer })
|
||||
public price!: BigNumber;
|
||||
@PrimaryColumn({ name: 'base_asset_symbol' })
|
||||
public baseAssetSymbol!: string;
|
||||
@Column({ name: 'base_asset_address' })
|
||||
public baseAssetAddress!: string;
|
||||
@Column({ name: 'base_volume', type: 'numeric', transformer: bigNumberTransformer })
|
||||
public baseVolume!: BigNumber;
|
||||
@PrimaryColumn({ name: 'quote_asset_symbol' })
|
||||
public quoteAssetSymbol!: string;
|
||||
@Column({ name: 'quote_asset_address' })
|
||||
public quoteAssetAddress!: string;
|
||||
@Column({ name: 'quote_volume', type: 'numeric', transformer: bigNumberTransformer })
|
||||
public quoteVolume!: BigNumber;
|
||||
}
|
19
packages/pipeline/src/entities/transaction.ts
Normal file
19
packages/pipeline/src/entities/transaction.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
||||
|
||||
import { bigNumberTransformer, numberToBigIntTransformer } from '../utils';
|
||||
|
||||
@Entity({ name: 'transactions', schema: 'raw' })
|
||||
export class Transaction {
|
||||
@PrimaryColumn({ name: 'transaction_hash' })
|
||||
public transactionHash!: string;
|
||||
@PrimaryColumn({ name: 'block_hash' })
|
||||
public blockHash!: string;
|
||||
@PrimaryColumn({ name: 'block_number', transformer: numberToBigIntTransformer })
|
||||
public blockNumber!: number;
|
||||
|
||||
@Column({ type: 'numeric', name: 'gas_used', transformer: bigNumberTransformer })
|
||||
public gasUsed!: BigNumber;
|
||||
@Column({ type: 'numeric', name: 'gas_price', transformer: bigNumberTransformer })
|
||||
public gasPrice!: BigNumber;
|
||||
}
|
42
packages/pipeline/src/ormconfig.ts
Normal file
42
packages/pipeline/src/ormconfig.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { ConnectionOptions } from 'typeorm';
|
||||
|
||||
import {
|
||||
Block,
|
||||
DexTrade,
|
||||
ExchangeCancelEvent,
|
||||
ExchangeCancelUpToEvent,
|
||||
ExchangeFillEvent,
|
||||
OHLCVExternal,
|
||||
Relayer,
|
||||
SraOrder,
|
||||
SraOrdersObservedTimeStamp,
|
||||
TokenMetadata,
|
||||
TokenOrderbookSnapshot,
|
||||
Transaction,
|
||||
} from './entities';
|
||||
|
||||
const entities = [
|
||||
Block,
|
||||
DexTrade,
|
||||
ExchangeCancelEvent,
|
||||
ExchangeCancelUpToEvent,
|
||||
ExchangeFillEvent,
|
||||
OHLCVExternal,
|
||||
Relayer,
|
||||
SraOrder,
|
||||
SraOrdersObservedTimeStamp,
|
||||
TokenMetadata,
|
||||
TokenOrderbookSnapshot,
|
||||
Transaction,
|
||||
];
|
||||
|
||||
const config: ConnectionOptions = {
|
||||
type: 'postgres',
|
||||
url: process.env.ZEROEX_DATA_PIPELINE_DB_URL,
|
||||
synchronize: false,
|
||||
logging: ['error'],
|
||||
entities,
|
||||
migrations: ['./lib/migrations/**/*.js'],
|
||||
};
|
||||
|
||||
module.exports = config;
|
53
packages/pipeline/src/parsers/bloxy/index.ts
Normal file
53
packages/pipeline/src/parsers/bloxy/index.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { BLOXY_DEX_TRADES_URL, BloxyTrade } from '../../data_sources/bloxy';
|
||||
import { DexTrade } from '../../entities';
|
||||
|
||||
/**
|
||||
* Parses a raw trades response from the Bloxy Dex API and returns an array of
|
||||
* DexTrade entities.
|
||||
* @param rawTrades A raw order response from an SRA endpoint.
|
||||
*/
|
||||
export function parseBloxyTrades(rawTrades: BloxyTrade[]): DexTrade[] {
|
||||
return R.map(_parseBloxyTrade, rawTrades);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a single Bloxy trade into a DexTrade entity.
|
||||
* @param rawTrade A single trade from the response from the Bloxy API.
|
||||
*/
|
||||
export function _parseBloxyTrade(rawTrade: BloxyTrade): DexTrade {
|
||||
const dexTrade = new DexTrade();
|
||||
dexTrade.sourceUrl = BLOXY_DEX_TRADES_URL;
|
||||
dexTrade.txHash = rawTrade.tx_hash;
|
||||
dexTrade.txTimestamp = new Date(rawTrade.tx_time).getTime();
|
||||
dexTrade.txDate = rawTrade.tx_date;
|
||||
dexTrade.txSender = rawTrade.tx_sender;
|
||||
dexTrade.smartContractId = rawTrade.smart_contract_id;
|
||||
dexTrade.smartContractAddress = rawTrade.smart_contract_address;
|
||||
dexTrade.contractType = rawTrade.contract_type;
|
||||
dexTrade.maker = rawTrade.maker;
|
||||
dexTrade.taker = rawTrade.taker;
|
||||
// TODO(albrow): The Bloxy API returns amounts and fees as a `number` type
|
||||
// but some of their values have too many significant digits to be
|
||||
// represented that way. Ideally they will switch to using strings and then
|
||||
// we can update this code.
|
||||
dexTrade.amountBuy = new BigNumber(rawTrade.amountBuy.toString());
|
||||
dexTrade.makerFeeAmount = new BigNumber(rawTrade.makerFee.toString());
|
||||
dexTrade.buyCurrencyId = rawTrade.buyCurrencyId;
|
||||
dexTrade.buySymbol = filterNullCharacters(rawTrade.buySymbol);
|
||||
dexTrade.amountSell = new BigNumber(rawTrade.amountSell.toString());
|
||||
dexTrade.takerFeeAmount = new BigNumber(rawTrade.takerFee.toString());
|
||||
dexTrade.sellCurrencyId = rawTrade.sellCurrencyId;
|
||||
dexTrade.sellSymbol = filterNullCharacters(rawTrade.sellSymbol);
|
||||
dexTrade.makerAnnotation = rawTrade.maker_annotation;
|
||||
dexTrade.takerAnnotation = rawTrade.taker_annotation;
|
||||
dexTrade.protocol = rawTrade.protocol;
|
||||
dexTrade.buyAddress = rawTrade.buyAddress;
|
||||
dexTrade.sellAddress = rawTrade.sellAddress;
|
||||
return dexTrade;
|
||||
}
|
||||
|
||||
// Works with any form of escaped null character (e.g., '\0' and '\u0000').
|
||||
const filterNullCharacters = R.replace(/\0/g, '');
|
77
packages/pipeline/src/parsers/ddex_orders/index.ts
Normal file
77
packages/pipeline/src/parsers/ddex_orders/index.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { DdexMarket, DdexOrder, DdexOrderbook } from '../../data_sources/ddex';
|
||||
import { TokenOrderbookSnapshot as TokenOrder } from '../../entities';
|
||||
import { OrderType } from '../../types';
|
||||
|
||||
/**
|
||||
* Marque function of this file.
|
||||
* 1) Takes in orders from an orderbook,
|
||||
* other information attached.
|
||||
* @param ddexOrderbook A raw orderbook that we pull from the Ddex API.
|
||||
* @param ddexMarket An object containing market data also directly from the API.
|
||||
* @param observedTimestamp Time at which the orders for the market were pulled.
|
||||
* @param source The exchange where these orders are placed. In this case 'ddex'.
|
||||
*/
|
||||
export function parseDdexOrders(
|
||||
ddexOrderbook: DdexOrderbook,
|
||||
ddexMarket: DdexMarket,
|
||||
observedTimestamp: number,
|
||||
source: string,
|
||||
): TokenOrder[] {
|
||||
const aggregatedBids = aggregateOrders(ddexOrderbook.bids);
|
||||
const aggregatedAsks = aggregateOrders(ddexOrderbook.asks);
|
||||
const parsedBids = aggregatedBids.map(order => parseDdexOrder(ddexMarket, observedTimestamp, 'bid', source, order));
|
||||
const parsedAsks = aggregatedAsks.map(order => parseDdexOrder(ddexMarket, observedTimestamp, 'ask', source, order));
|
||||
return parsedBids.concat(parsedAsks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregates orders by price point for consistency with other exchanges.
|
||||
* Querying the Ddex API at level 3 setting returns a breakdown of
|
||||
* individual orders at each price point. Other exchanges only give total amount
|
||||
* at each price point. Returns an array of <price, amount> tuples.
|
||||
* @param ddexOrders A list of Ddex orders awaiting aggregation.
|
||||
*/
|
||||
export function aggregateOrders(ddexOrders: DdexOrder[]): Array<[string, BigNumber]> {
|
||||
const sumAmount = (acc: BigNumber, order: DdexOrder): BigNumber => acc.plus(order.amount);
|
||||
const aggregatedPricePoints = R.reduceBy(sumAmount, new BigNumber(0), R.prop('price'), ddexOrders);
|
||||
return Object.entries(aggregatedPricePoints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a single aggregated Ddex order in order to form a tokenOrder entity
|
||||
* which can be saved into the database.
|
||||
* @param ddexMarket An object containing information about the market where these
|
||||
* trades have been placed.
|
||||
* @param observedTimestamp The time when the API response returned back to us.
|
||||
* @param orderType 'bid' or 'ask' enum.
|
||||
* @param source Exchange where these orders were placed.
|
||||
* @param ddexOrder A <price, amount> tuple which we will convert to volume-basis.
|
||||
*/
|
||||
export function parseDdexOrder(
|
||||
ddexMarket: DdexMarket,
|
||||
observedTimestamp: number,
|
||||
orderType: OrderType,
|
||||
source: string,
|
||||
ddexOrder: [string, BigNumber],
|
||||
): TokenOrder {
|
||||
const tokenOrder = new TokenOrder();
|
||||
const price = new BigNumber(ddexOrder[0]);
|
||||
const amount = ddexOrder[1];
|
||||
|
||||
tokenOrder.source = source;
|
||||
tokenOrder.observedTimestamp = observedTimestamp;
|
||||
tokenOrder.orderType = orderType;
|
||||
tokenOrder.price = price;
|
||||
|
||||
tokenOrder.baseAssetSymbol = ddexMarket.baseToken;
|
||||
tokenOrder.baseAssetAddress = ddexMarket.baseTokenAddress;
|
||||
tokenOrder.baseVolume = price.times(amount);
|
||||
|
||||
tokenOrder.quoteAssetSymbol = ddexMarket.quoteToken;
|
||||
tokenOrder.quoteAssetAddress = ddexMarket.quoteTokenAddress;
|
||||
tokenOrder.quoteVolume = amount;
|
||||
return tokenOrder;
|
||||
}
|
133
packages/pipeline/src/parsers/events/index.ts
Normal file
133
packages/pipeline/src/parsers/events/index.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import { ExchangeCancelEventArgs, ExchangeCancelUpToEventArgs, ExchangeFillEventArgs } from '@0x/contract-wrappers';
|
||||
import { assetDataUtils } from '@0x/order-utils';
|
||||
import { AssetProxyId, ERC721AssetData } from '@0x/types';
|
||||
import { LogWithDecodedArgs } from 'ethereum-types';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { ExchangeCancelEvent, ExchangeCancelUpToEvent, ExchangeFillEvent } from '../../entities';
|
||||
import { bigNumbertoStringOrNull } from '../../utils';
|
||||
|
||||
/**
|
||||
* Parses raw event logs for a fill event and returns an array of
|
||||
* ExchangeFillEvent entities.
|
||||
* @param eventLogs Raw event logs (e.g. returned from contract-wrappers).
|
||||
*/
|
||||
export const parseExchangeFillEvents: (
|
||||
eventLogs: Array<LogWithDecodedArgs<ExchangeFillEventArgs>>,
|
||||
) => ExchangeFillEvent[] = R.map(_convertToExchangeFillEvent);
|
||||
|
||||
/**
|
||||
* Parses raw event logs for a cancel event and returns an array of
|
||||
* ExchangeCancelEvent entities.
|
||||
* @param eventLogs Raw event logs (e.g. returned from contract-wrappers).
|
||||
*/
|
||||
export const parseExchangeCancelEvents: (
|
||||
eventLogs: Array<LogWithDecodedArgs<ExchangeCancelEventArgs>>,
|
||||
) => ExchangeCancelEvent[] = R.map(_convertToExchangeCancelEvent);
|
||||
|
||||
/**
|
||||
* Parses raw event logs for a CancelUpTo event and returns an array of
|
||||
* ExchangeCancelUpToEvent entities.
|
||||
* @param eventLogs Raw event logs (e.g. returned from contract-wrappers).
|
||||
*/
|
||||
export const parseExchangeCancelUpToEvents: (
|
||||
eventLogs: Array<LogWithDecodedArgs<ExchangeCancelUpToEventArgs>>,
|
||||
) => ExchangeCancelUpToEvent[] = R.map(_convertToExchangeCancelUpToEvent);
|
||||
|
||||
/**
|
||||
* Converts a raw event log for a fill event into an ExchangeFillEvent entity.
|
||||
* @param eventLog Raw event log (e.g. returned from contract-wrappers).
|
||||
*/
|
||||
export function _convertToExchangeFillEvent(eventLog: LogWithDecodedArgs<ExchangeFillEventArgs>): ExchangeFillEvent {
|
||||
const makerAssetData = assetDataUtils.decodeAssetDataOrThrow(eventLog.args.makerAssetData);
|
||||
const makerAssetType = makerAssetData.assetProxyId === AssetProxyId.ERC20 ? 'erc20' : 'erc721';
|
||||
const takerAssetData = assetDataUtils.decodeAssetDataOrThrow(eventLog.args.takerAssetData);
|
||||
const takerAssetType = takerAssetData.assetProxyId === AssetProxyId.ERC20 ? 'erc20' : 'erc721';
|
||||
const exchangeFillEvent = new ExchangeFillEvent();
|
||||
exchangeFillEvent.contractAddress = eventLog.address as string;
|
||||
exchangeFillEvent.blockNumber = eventLog.blockNumber as number;
|
||||
exchangeFillEvent.logIndex = eventLog.logIndex as number;
|
||||
exchangeFillEvent.rawData = eventLog.data as string;
|
||||
exchangeFillEvent.transactionHash = eventLog.transactionHash;
|
||||
exchangeFillEvent.makerAddress = eventLog.args.makerAddress;
|
||||
exchangeFillEvent.takerAddress = eventLog.args.takerAddress;
|
||||
exchangeFillEvent.feeRecipientAddress = eventLog.args.feeRecipientAddress;
|
||||
exchangeFillEvent.senderAddress = eventLog.args.senderAddress;
|
||||
exchangeFillEvent.makerAssetFilledAmount = eventLog.args.makerAssetFilledAmount;
|
||||
exchangeFillEvent.takerAssetFilledAmount = eventLog.args.takerAssetFilledAmount;
|
||||
exchangeFillEvent.makerFeePaid = eventLog.args.makerFeePaid;
|
||||
exchangeFillEvent.takerFeePaid = eventLog.args.takerFeePaid;
|
||||
exchangeFillEvent.orderHash = eventLog.args.orderHash;
|
||||
exchangeFillEvent.rawMakerAssetData = eventLog.args.makerAssetData;
|
||||
exchangeFillEvent.makerAssetType = makerAssetType;
|
||||
exchangeFillEvent.makerAssetProxyId = makerAssetData.assetProxyId;
|
||||
exchangeFillEvent.makerTokenAddress = makerAssetData.tokenAddress;
|
||||
// tslint has a false positive here. Type assertion is required.
|
||||
// tslint:disable-next-line:no-unnecessary-type-assertion
|
||||
exchangeFillEvent.makerTokenId = bigNumbertoStringOrNull((makerAssetData as ERC721AssetData).tokenId);
|
||||
exchangeFillEvent.rawTakerAssetData = eventLog.args.takerAssetData;
|
||||
exchangeFillEvent.takerAssetType = takerAssetType;
|
||||
exchangeFillEvent.takerAssetProxyId = takerAssetData.assetProxyId;
|
||||
exchangeFillEvent.takerTokenAddress = takerAssetData.tokenAddress;
|
||||
// tslint:disable-next-line:no-unnecessary-type-assertion
|
||||
exchangeFillEvent.takerTokenId = bigNumbertoStringOrNull((takerAssetData as ERC721AssetData).tokenId);
|
||||
return exchangeFillEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a raw event log for a cancel event into an ExchangeCancelEvent
|
||||
* entity.
|
||||
* @param eventLog Raw event log (e.g. returned from contract-wrappers).
|
||||
*/
|
||||
export function _convertToExchangeCancelEvent(
|
||||
eventLog: LogWithDecodedArgs<ExchangeCancelEventArgs>,
|
||||
): ExchangeCancelEvent {
|
||||
const makerAssetData = assetDataUtils.decodeAssetDataOrThrow(eventLog.args.makerAssetData);
|
||||
const makerAssetType = makerAssetData.assetProxyId === AssetProxyId.ERC20 ? 'erc20' : 'erc721';
|
||||
const takerAssetData = assetDataUtils.decodeAssetDataOrThrow(eventLog.args.takerAssetData);
|
||||
const takerAssetType = takerAssetData.assetProxyId === AssetProxyId.ERC20 ? 'erc20' : 'erc721';
|
||||
const exchangeCancelEvent = new ExchangeCancelEvent();
|
||||
exchangeCancelEvent.contractAddress = eventLog.address as string;
|
||||
exchangeCancelEvent.blockNumber = eventLog.blockNumber as number;
|
||||
exchangeCancelEvent.logIndex = eventLog.logIndex as number;
|
||||
exchangeCancelEvent.rawData = eventLog.data as string;
|
||||
exchangeCancelEvent.transactionHash = eventLog.transactionHash;
|
||||
exchangeCancelEvent.makerAddress = eventLog.args.makerAddress;
|
||||
exchangeCancelEvent.takerAddress = eventLog.args.takerAddress;
|
||||
exchangeCancelEvent.feeRecipientAddress = eventLog.args.feeRecipientAddress;
|
||||
exchangeCancelEvent.senderAddress = eventLog.args.senderAddress;
|
||||
exchangeCancelEvent.orderHash = eventLog.args.orderHash;
|
||||
exchangeCancelEvent.rawMakerAssetData = eventLog.args.makerAssetData;
|
||||
exchangeCancelEvent.makerAssetType = makerAssetType;
|
||||
exchangeCancelEvent.makerAssetProxyId = makerAssetData.assetProxyId;
|
||||
exchangeCancelEvent.makerTokenAddress = makerAssetData.tokenAddress;
|
||||
// tslint:disable-next-line:no-unnecessary-type-assertion
|
||||
exchangeCancelEvent.makerTokenId = bigNumbertoStringOrNull((makerAssetData as ERC721AssetData).tokenId);
|
||||
exchangeCancelEvent.rawTakerAssetData = eventLog.args.takerAssetData;
|
||||
exchangeCancelEvent.takerAssetType = takerAssetType;
|
||||
exchangeCancelEvent.takerAssetProxyId = takerAssetData.assetProxyId;
|
||||
exchangeCancelEvent.takerTokenAddress = takerAssetData.tokenAddress;
|
||||
// tslint:disable-next-line:no-unnecessary-type-assertion
|
||||
exchangeCancelEvent.takerTokenId = bigNumbertoStringOrNull((takerAssetData as ERC721AssetData).tokenId);
|
||||
return exchangeCancelEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a raw event log for a cancelUpTo event into an
|
||||
* ExchangeCancelUpToEvent entity.
|
||||
* @param eventLog Raw event log (e.g. returned from contract-wrappers).
|
||||
*/
|
||||
export function _convertToExchangeCancelUpToEvent(
|
||||
eventLog: LogWithDecodedArgs<ExchangeCancelUpToEventArgs>,
|
||||
): ExchangeCancelUpToEvent {
|
||||
const exchangeCancelUpToEvent = new ExchangeCancelUpToEvent();
|
||||
exchangeCancelUpToEvent.contractAddress = eventLog.address as string;
|
||||
exchangeCancelUpToEvent.blockNumber = eventLog.blockNumber as number;
|
||||
exchangeCancelUpToEvent.logIndex = eventLog.logIndex as number;
|
||||
exchangeCancelUpToEvent.rawData = eventLog.data as string;
|
||||
exchangeCancelUpToEvent.transactionHash = eventLog.transactionHash;
|
||||
exchangeCancelUpToEvent.makerAddress = eventLog.args.makerAddress;
|
||||
exchangeCancelUpToEvent.senderAddress = eventLog.args.senderAddress;
|
||||
exchangeCancelUpToEvent.orderEpoch = eventLog.args.orderEpoch;
|
||||
return exchangeCancelUpToEvent;
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import { CryptoCompareOHLCVRecord } from '../../data_sources/ohlcv_external/crypto_compare';
|
||||
import { OHLCVExternal } from '../../entities';
|
||||
|
||||
const ONE_SECOND = 1000; // Crypto Compare uses timestamps in seconds instead of milliseconds
|
||||
|
||||
export interface OHLCVMetadata {
|
||||
exchange: string;
|
||||
fromSymbol: string;
|
||||
toSymbol: string;
|
||||
source: string;
|
||||
observedTimestamp: number;
|
||||
interval: number;
|
||||
}
|
||||
/**
|
||||
* Parses OHLCV records from Crypto Compare into an array of OHLCVExternal entities
|
||||
* @param rawRecords an array of OHLCV records from Crypto Compare (not the full response)
|
||||
*/
|
||||
export function parseRecords(rawRecords: CryptoCompareOHLCVRecord[], metadata: OHLCVMetadata): OHLCVExternal[] {
|
||||
return rawRecords.map(rec => {
|
||||
const ohlcvRecord = new OHLCVExternal();
|
||||
ohlcvRecord.exchange = metadata.exchange;
|
||||
ohlcvRecord.fromSymbol = metadata.fromSymbol;
|
||||
ohlcvRecord.toSymbol = metadata.toSymbol;
|
||||
ohlcvRecord.startTime = rec.time * ONE_SECOND - metadata.interval;
|
||||
ohlcvRecord.endTime = rec.time * ONE_SECOND;
|
||||
|
||||
ohlcvRecord.open = rec.open;
|
||||
ohlcvRecord.close = rec.close;
|
||||
ohlcvRecord.low = rec.low;
|
||||
ohlcvRecord.high = rec.high;
|
||||
ohlcvRecord.volumeFrom = rec.volumefrom;
|
||||
ohlcvRecord.volumeTo = rec.volumeto;
|
||||
|
||||
ohlcvRecord.source = metadata.source;
|
||||
ohlcvRecord.observedTimestamp = metadata.observedTimestamp;
|
||||
return ohlcvRecord;
|
||||
});
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user