Integrate one-time dump and API for nonfungible.com (#1603)
* Add script for pulling NFT trade data from nonfungible.com * corrections for current state of API * change data model to match data source * change primary key * pull data from initial dump first, then API * pull all supported NFT's, not just cryptokitties * disable problematic data sources * rename function to satisfy linter * Rename table to nonfungible_dot_com_trades * rename parser module to nonfungible_dot_com from non_fungible_dot_com, for consistency * correct mistaken reference to Bloxy * rename NonfungibleDotComTrade to ...TradeResponse * `NftTrade` -> `NonfungibleDotComTrade` * rename files to match prior object renaming * use fetchAsync instead of axios * improve fetchAsync error message: include URL * avoid non-null contraints in API trades too, not just for trades from the one-time dump * disable mythereum publisher
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import { MigrationInterface, QueryRunner, Table } from 'typeorm';
|
||||
|
||||
const nftTrades = new Table({
|
||||
name: 'raw.nonfungible_dot_com_trades',
|
||||
columns: [
|
||||
{ name: 'publisher', type: 'varchar', isPrimary: true },
|
||||
{ name: 'transaction_hash', type: 'varchar', isPrimary: true },
|
||||
{ name: 'asset_id', type: 'varchar', isPrimary: true },
|
||||
{ name: 'block_number', type: 'bigint', isPrimary: true },
|
||||
{ name: 'log_index', type: 'integer', isPrimary: true },
|
||||
|
||||
{ name: 'block_timestamp', type: 'bigint' },
|
||||
{ name: 'asset_descriptor', type: 'varchar' },
|
||||
{ name: 'market_address', type: 'varchar(42)' },
|
||||
{ name: 'total_price', type: 'numeric' },
|
||||
{ name: 'usd_price', type: 'numeric' },
|
||||
{ name: 'buyer_address', type: 'varchar(42)' },
|
||||
{ name: 'seller_address', type: 'varchar(42)' },
|
||||
{ name: 'meta', type: 'jsonb' },
|
||||
],
|
||||
});
|
||||
|
||||
export class CreateNftTrades1543540108767 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.createTable(nftTrades);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.dropTable(nftTrades);
|
||||
}
|
||||
}
|
||||
220
packages/pipeline/src/data_sources/nonfungible_dot_com/index.ts
Normal file
220
packages/pipeline/src/data_sources/nonfungible_dot_com/index.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
import { stringify } from 'querystring';
|
||||
|
||||
import { logUtils } from '@0x/utils';
|
||||
|
||||
import { fetchSuccessfullyOrThrowAsync } from '../../utils';
|
||||
|
||||
// URL to use for getting nft trades from nonfungible.com.
|
||||
export const NONFUNGIBLE_DOT_COM_URL = 'https://nonfungible.com/api/v1';
|
||||
// Number of trades to get at once. This is a hard limit enforced by the API.
|
||||
const MAX_TRADES_PER_QUERY = 100;
|
||||
|
||||
// Note(albrow): For now this will have to be manually updated by checking
|
||||
// https://nonfungible.com/
|
||||
export const knownPublishers = [
|
||||
'axieinfinity',
|
||||
// 'cryptokitties', // disabled until we get updated initial dump that isn't truncated
|
||||
'cryptopunks',
|
||||
'cryptovoxels',
|
||||
'decentraland',
|
||||
'decentraland_estate',
|
||||
'etherbots',
|
||||
'etheremon',
|
||||
'ethtown',
|
||||
// 'knownorigin', // disabled because of null characters in data being rejected by postgres
|
||||
// 'mythereum', // worked at one time, but now seems dead
|
||||
'superrare',
|
||||
];
|
||||
|
||||
export interface NonfungibleDotComHistoryResponse {
|
||||
data: NonfungibleDotComTradeResponse[];
|
||||
}
|
||||
|
||||
export interface NonfungibleDotComTradeResponse {
|
||||
_id: string;
|
||||
transactionHash: string;
|
||||
blockNumber: number;
|
||||
logIndex: number;
|
||||
blockTimestamp: string;
|
||||
assetId: string;
|
||||
assetDescriptor: string;
|
||||
nftAddress: string;
|
||||
marketAddress: string;
|
||||
tokenTicker: string;
|
||||
totalDecimalPrice: number;
|
||||
totalPrice: string;
|
||||
usdPrice: number;
|
||||
currencyTransfer: object;
|
||||
buyer: string;
|
||||
seller: string;
|
||||
meta: object;
|
||||
image: string;
|
||||
composedOf: string;
|
||||
asset_link: string;
|
||||
seller_address_link: string;
|
||||
buyer_address_link: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and returns all trades for the given publisher, starting at the given block number.
|
||||
* Automatically handles pagination.
|
||||
* @param publisher A valid "publisher" for the nonfungible.com API. (e.g. "cryptokitties")
|
||||
* @param blockNumberStart The block number to start querying from.
|
||||
*/
|
||||
export async function getTradesAsync(
|
||||
publisher: string,
|
||||
blockNumberStart: number,
|
||||
): Promise<NonfungibleDotComTradeResponse[]> {
|
||||
const allTrades: NonfungibleDotComTradeResponse[] = [];
|
||||
|
||||
/**
|
||||
* due to high data volumes and rate limiting, we procured an initial data
|
||||
* dump from nonfungible.com. If the requested starting block number is
|
||||
* contained in that initial dump, then pull relevant trades from there
|
||||
* first. Later (below) we'll get the more recent trades from the API itself.
|
||||
*/
|
||||
|
||||
if (blockNumberStart < highestBlockNumbersInIntialDump[publisher]) {
|
||||
logUtils.log('getting trades from one-time dump');
|
||||
// caller needs trades that are in the initial data dump, so get them
|
||||
// from there, then later go to the API for the rest.
|
||||
const initialDumpResponse: NonfungibleDotComHistoryResponse = await fetchSuccessfullyOrThrowAsync(
|
||||
getInitialDumpUrl(publisher),
|
||||
);
|
||||
const initialDumpTrades = initialDumpResponse.data;
|
||||
for (const initialDumpTrade of initialDumpTrades) {
|
||||
if (!shouldProcessTrade(initialDumpTrade, allTrades)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ensureNonNull(initialDumpTrade);
|
||||
|
||||
allTrades.push(initialDumpTrade);
|
||||
}
|
||||
logUtils.log(`got ${allTrades.length} from one-time dump`);
|
||||
}
|
||||
|
||||
const fullUrl = getFullUrlForPublisher(publisher);
|
||||
|
||||
/**
|
||||
* API returns trades in reverse chronological order, so highest block
|
||||
* numbers first. The `start` query parameter indicates how far back in
|
||||
* time (in number of trades) the results should start. Here we iterate
|
||||
* over both start parameter values and block numbers simultaneously.
|
||||
* Start parameter values count up from zero. Block numbers count down
|
||||
* until reaching the highest block number in the initial dump.
|
||||
*/
|
||||
|
||||
const blockNumberStop = Math.max(highestBlockNumbersInIntialDump[publisher] + 1, blockNumberStart);
|
||||
for (
|
||||
let startParam = 0, blockNumber = Number.MAX_SAFE_INTEGER;
|
||||
blockNumber > blockNumberStop;
|
||||
startParam += MAX_TRADES_PER_QUERY
|
||||
) {
|
||||
const response = await _getTradesWithOffsetAsync(fullUrl, publisher, startParam);
|
||||
const tradesFromApi = response.data;
|
||||
logUtils.log(
|
||||
`got ${
|
||||
tradesFromApi.length
|
||||
} trades from API. blockNumber=${blockNumber}. blockNumberStop=${blockNumberStop}`,
|
||||
);
|
||||
for (const tradeFromApi of tradesFromApi) {
|
||||
if (tradeFromApi.blockNumber <= blockNumberStop) {
|
||||
blockNumber = blockNumberStop;
|
||||
break;
|
||||
}
|
||||
if (!shouldProcessTrade(tradeFromApi, allTrades)) {
|
||||
continue;
|
||||
}
|
||||
ensureNonNull(tradeFromApi);
|
||||
allTrades.push(tradeFromApi);
|
||||
blockNumber = tradeFromApi.blockNumber;
|
||||
}
|
||||
}
|
||||
|
||||
return allTrades;
|
||||
}
|
||||
|
||||
function shouldProcessTrade(
|
||||
trade: NonfungibleDotComTradeResponse,
|
||||
existingTrades: NonfungibleDotComTradeResponse[],
|
||||
): boolean {
|
||||
// check to see if this trade is already in existingTrades
|
||||
const existingTradeIndex = existingTrades.findIndex(
|
||||
// HACK! making assumptions about composition of primary key
|
||||
e =>
|
||||
e.transactionHash === trade.transactionHash &&
|
||||
e.logIndex === trade.logIndex &&
|
||||
e.blockNumber === trade.blockNumber,
|
||||
);
|
||||
if (existingTradeIndex !== -1) {
|
||||
logUtils.log("we've already captured this trade. deciding whether to use the existing record or this one.");
|
||||
if (trade.blockNumber > existingTrades[existingTradeIndex].blockNumber) {
|
||||
logUtils.log('throwing out existing trade');
|
||||
existingTrades.splice(existingTradeIndex, 1);
|
||||
} else {
|
||||
logUtils.log('letting existing trade stand, and skipping processing of this trade');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const highestBlockNumbersInIntialDump: { [publisher: string]: number } = {
|
||||
axieinfinity: 7065913,
|
||||
cryptokitties: 4658171,
|
||||
cryptopunks: 7058897,
|
||||
cryptovoxels: 7060783,
|
||||
decentraland_estate: 7065181,
|
||||
decentraland: 6938962,
|
||||
etherbots: 5204980,
|
||||
etheremon: 7065370,
|
||||
ethtown: 7064126,
|
||||
knownorigin: 7065160,
|
||||
mythereum: 7065311,
|
||||
superrare: 7065955,
|
||||
};
|
||||
|
||||
async function _getTradesWithOffsetAsync(
|
||||
url: string,
|
||||
publisher: string,
|
||||
offset: number,
|
||||
): Promise<NonfungibleDotComHistoryResponse> {
|
||||
const resp: NonfungibleDotComHistoryResponse = await fetchSuccessfullyOrThrowAsync(
|
||||
`${url}?${stringify({
|
||||
publisher,
|
||||
start: offset,
|
||||
length: MAX_TRADES_PER_QUERY,
|
||||
})}`,
|
||||
);
|
||||
return resp;
|
||||
}
|
||||
|
||||
function getFullUrlForPublisher(publisher: string): string {
|
||||
return `${NONFUNGIBLE_DOT_COM_URL}/market/${publisher}/history`;
|
||||
}
|
||||
|
||||
function getInitialDumpUrl(publisher: string): string {
|
||||
return `https://nonfungible-dot-com-one-time-data-dump.s3.amazonaws.com/sales_summary_${publisher}.json`;
|
||||
}
|
||||
|
||||
function ensureNonNull(trade: NonfungibleDotComTradeResponse): void {
|
||||
// these fields need to be set in order to avoid non-null
|
||||
// constraint exceptions upon database insertion.
|
||||
if (trade.logIndex === undefined) {
|
||||
// for cryptopunks
|
||||
trade.logIndex = 0;
|
||||
}
|
||||
if (trade.assetDescriptor === undefined) {
|
||||
// for cryptopunks
|
||||
trade.assetDescriptor = '';
|
||||
}
|
||||
if (trade.meta === undefined) {
|
||||
// for cryptopunks
|
||||
trade.meta = {};
|
||||
}
|
||||
if (trade.marketAddress === null) {
|
||||
// for decentraland_estate
|
||||
trade.marketAddress = '';
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ 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 { NonfungibleDotComTrade } from './nonfungible_dot_com_trade';
|
||||
export { OHLCVExternal } from './ohlcv_external';
|
||||
export { Relayer } from './relayer';
|
||||
export { SraOrder } from './sra_order';
|
||||
|
||||
35
packages/pipeline/src/entities/nonfungible_dot_com_trade.ts
Normal file
35
packages/pipeline/src/entities/nonfungible_dot_com_trade.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
||||
|
||||
import { bigNumberTransformer, numberToBigIntTransformer } from '../utils';
|
||||
|
||||
@Entity({ name: 'nonfungible_dot_com_trades', schema: 'raw' })
|
||||
export class NonfungibleDotComTrade {
|
||||
@PrimaryColumn({ name: 'transaction_hash' })
|
||||
public transactionHash!: string;
|
||||
@PrimaryColumn({ name: 'publisher' })
|
||||
public publisher!: string;
|
||||
@PrimaryColumn({ name: 'block_number', type: 'bigint', transformer: numberToBigIntTransformer })
|
||||
public blockNumber!: number;
|
||||
@PrimaryColumn({ name: 'log_index' })
|
||||
public logIndex!: number;
|
||||
@PrimaryColumn({ name: 'asset_id' })
|
||||
public assetId!: string;
|
||||
|
||||
@Column({ name: 'block_timestamp', type: 'bigint', transformer: numberToBigIntTransformer })
|
||||
public blockTimestamp!: number;
|
||||
@Column({ name: 'asset_descriptor' })
|
||||
public assetDescriptor!: string;
|
||||
@Column({ name: 'market_address' })
|
||||
public marketAddress!: string;
|
||||
@Column({ name: 'total_price', type: 'numeric', transformer: bigNumberTransformer })
|
||||
public totalPrice!: BigNumber;
|
||||
@Column({ name: 'usd_price', type: 'numeric', transformer: bigNumberTransformer })
|
||||
public usdPrice!: BigNumber;
|
||||
@Column({ name: 'buyer_address' })
|
||||
public buyerAddress!: string;
|
||||
@Column({ name: 'seller_address' })
|
||||
public sellerAddress!: string;
|
||||
@Column({ type: 'jsonb' })
|
||||
public meta!: object;
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
ExchangeCancelEvent,
|
||||
ExchangeCancelUpToEvent,
|
||||
ExchangeFillEvent,
|
||||
NonfungibleDotComTrade,
|
||||
OHLCVExternal,
|
||||
Relayer,
|
||||
SraOrder,
|
||||
@@ -33,6 +34,7 @@ const entities = [
|
||||
ExchangeCancelUpToEvent,
|
||||
ExchangeFillEvent,
|
||||
ERC20ApprovalEvent,
|
||||
NonfungibleDotComTrade,
|
||||
OHLCVExternal,
|
||||
Relayer,
|
||||
SraOrder,
|
||||
|
||||
42
packages/pipeline/src/parsers/nonfungible_dot_com/index.ts
Normal file
42
packages/pipeline/src/parsers/nonfungible_dot_com/index.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as R from 'ramda';
|
||||
|
||||
import { NonfungibleDotComTradeResponse } from '../../data_sources/nonfungible_dot_com';
|
||||
import { NonfungibleDotComTrade } from '../../entities';
|
||||
|
||||
/**
|
||||
* Parses a raw trades from the nonfungible.com API and returns an array of
|
||||
* NonfungibleDotComTrade entities.
|
||||
* @param rawTrades A raw order response from an SRA endpoint.
|
||||
*/
|
||||
export function parseNonFungibleDotComTrades(
|
||||
rawTrades: NonfungibleDotComTradeResponse[],
|
||||
publisher: string,
|
||||
): NonfungibleDotComTrade[] {
|
||||
return R.map(_parseNonFungibleDotComTrade.bind(null, publisher), rawTrades);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a single trade from nonfungible.com into an NonfungibleDotComTrade entity.
|
||||
* @param rawTrade A single trade from the response from the nonfungible.com API.
|
||||
*/
|
||||
export function _parseNonFungibleDotComTrade(
|
||||
publisher: string,
|
||||
rawTrade: NonfungibleDotComTradeResponse,
|
||||
): NonfungibleDotComTrade {
|
||||
const nonfungibleDotComTrade = new NonfungibleDotComTrade();
|
||||
nonfungibleDotComTrade.assetDescriptor = rawTrade.assetDescriptor;
|
||||
nonfungibleDotComTrade.assetId = rawTrade.assetId;
|
||||
nonfungibleDotComTrade.blockNumber = rawTrade.blockNumber;
|
||||
nonfungibleDotComTrade.blockTimestamp = new Date(rawTrade.blockTimestamp).getTime();
|
||||
nonfungibleDotComTrade.buyerAddress = rawTrade.buyer;
|
||||
nonfungibleDotComTrade.logIndex = rawTrade.logIndex;
|
||||
nonfungibleDotComTrade.marketAddress = rawTrade.marketAddress;
|
||||
nonfungibleDotComTrade.meta = rawTrade.meta;
|
||||
nonfungibleDotComTrade.sellerAddress = rawTrade.seller;
|
||||
nonfungibleDotComTrade.totalPrice = new BigNumber(rawTrade.totalPrice);
|
||||
nonfungibleDotComTrade.transactionHash = rawTrade.transactionHash;
|
||||
nonfungibleDotComTrade.usdPrice = new BigNumber(rawTrade.usdPrice);
|
||||
nonfungibleDotComTrade.publisher = publisher;
|
||||
return nonfungibleDotComTrade;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// tslint:disable:no-console
|
||||
import 'reflect-metadata';
|
||||
import { Connection, ConnectionOptions, createConnection } from 'typeorm';
|
||||
|
||||
import { getTradesAsync, knownPublishers } from '../data_sources/nonfungible_dot_com';
|
||||
import { NonfungibleDotComTrade } from '../entities';
|
||||
import * as ormConfig from '../ormconfig';
|
||||
import { parseNonFungibleDotComTrades } from '../parsers/nonfungible_dot_com';
|
||||
import { handleError } from '../utils';
|
||||
|
||||
// Number of trades to save at once.
|
||||
const BATCH_SAVE_SIZE = 1000;
|
||||
|
||||
let connection: Connection;
|
||||
|
||||
(async () => {
|
||||
connection = await createConnection(ormConfig as ConnectionOptions);
|
||||
await getAndSaveTradesAsync();
|
||||
process.exit(0);
|
||||
})().catch(handleError);
|
||||
|
||||
async function getAndSaveTradesAsync(): Promise<void> {
|
||||
const tradesRepository = connection.getRepository(NonfungibleDotComTrade);
|
||||
|
||||
for (const publisher of knownPublishers) {
|
||||
console.log(`Getting latest trades for NFT ${publisher}...`);
|
||||
const tradeWithHighestBlockNumber = await tradesRepository
|
||||
.createQueryBuilder('nonfungible_dot_com_trades')
|
||||
.where('nonfungible_dot_com_trades.publisher = :publisher', { publisher })
|
||||
.orderBy({ 'nonfungible_dot_com_trades.block_number': 'DESC' })
|
||||
.getOne();
|
||||
const highestExistingBlockNumber =
|
||||
tradeWithHighestBlockNumber === undefined ? 0 : tradeWithHighestBlockNumber.blockNumber;
|
||||
console.log(`Highest block number in existing trades: ${highestExistingBlockNumber}`);
|
||||
const rawTrades = await getTradesAsync(publisher, highestExistingBlockNumber);
|
||||
console.log(`Parsing ${rawTrades.length} trades...`);
|
||||
const trades = parseNonFungibleDotComTrades(rawTrades, publisher);
|
||||
console.log(`Saving ${rawTrades.length} trades...`);
|
||||
await tradesRepository.save(trades, { chunk: Math.ceil(trades.length / BATCH_SAVE_SIZE) });
|
||||
}
|
||||
const newTotalTrades = await tradesRepository.count();
|
||||
console.log(`Done saving trades. There are now ${newTotalTrades} total NFT trades.`);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { BigNumber, fetchAsync } from '@0x/utils';
|
||||
export * from './transformers';
|
||||
export * from './constants';
|
||||
|
||||
@@ -51,3 +51,16 @@ export function handleError(e: any): void {
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does fetchAsync(), and checks the status code, throwing if it doesn't indicate success.
|
||||
*/
|
||||
export async function fetchSuccessfullyOrThrowAsync(url: string): Promise<any> {
|
||||
const response = await fetchAsync(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch URL ${url}. Unsuccessful HTTP status code (${response.status}): ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
48
packages/pipeline/test/entities/nft_trades_test.ts
Normal file
48
packages/pipeline/test/entities/nft_trades_test.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import 'mocha';
|
||||
import 'reflect-metadata';
|
||||
|
||||
import { NonfungibleDotComTrade } from '../../src/entities';
|
||||
import { createDbConnectionOnceAsync } from '../db_setup';
|
||||
import { chaiSetup } from '../utils/chai_setup';
|
||||
|
||||
import { testSaveAndFindEntityAsync } from './util';
|
||||
|
||||
chaiSetup.configure();
|
||||
|
||||
const baseTrade: NonfungibleDotComTrade = {
|
||||
assetDescriptor: 'Kitty #1002',
|
||||
assetId: '1002',
|
||||
blockNumber: 4608542,
|
||||
blockTimestamp: 1543544083704,
|
||||
buyerAddress: '0x316c55d1895a085c4b39a98ecb563f509301aaf7',
|
||||
logIndex: 28,
|
||||
marketAddress: '0xb1690C08E213a35Ed9bAb7B318DE14420FB57d8C',
|
||||
meta: {
|
||||
cattribute_body: 'munchkin',
|
||||
cattribute_coloreyes: 'mintgreen',
|
||||
cattribute_colorprimary: 'orangesoda',
|
||||
cattribute_colorsecondary: 'coffee',
|
||||
cattribute_colortertiary: 'kittencream',
|
||||
cattribute_eyes: 'thicccbrowz',
|
||||
cattribute_mouth: 'soserious',
|
||||
cattribute_pattern: 'totesbasic',
|
||||
generation: '0',
|
||||
is_exclusive: false,
|
||||
is_fancy: false,
|
||||
},
|
||||
sellerAddress: '0xba52c75764d6f594735dc735be7f1830cdf58ddf',
|
||||
totalPrice: new BigNumber('9751388888888889'),
|
||||
transactionHash: '0x468168419be7e442d5ff32d264fab24087b744bc2e37fdbac7024e1e74f4c6c8',
|
||||
usdPrice: new BigNumber('3.71957'),
|
||||
publisher: 'cryptokitties',
|
||||
};
|
||||
|
||||
// tslint:disable:custom-no-magic-numbers
|
||||
describe('NonfungibleDotComTrade entity', () => {
|
||||
it('save/find', async () => {
|
||||
const connection = await createDbConnectionOnceAsync();
|
||||
const tradesRepository = connection.getRepository(NonfungibleDotComTrade);
|
||||
await testSaveAndFindEntityAsync(tradesRepository, baseTrade);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
// tslint:disable:custom-no-magic-numbers
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import * as chai from 'chai';
|
||||
import 'mocha';
|
||||
|
||||
import { NonfungibleDotComTradeResponse } from '../../../src/data_sources/nonfungible_dot_com';
|
||||
import { NonfungibleDotComTrade } from '../../../src/entities';
|
||||
import { _parseNonFungibleDotComTrade } from '../../../src/parsers/nonfungible_dot_com';
|
||||
import { chaiSetup } from '../../utils/chai_setup';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
const input: NonfungibleDotComTradeResponse = {
|
||||
_id: '5b4cd04244abdb5ac3a8063f',
|
||||
assetDescriptor: 'Kitty #1002',
|
||||
assetId: '1002',
|
||||
blockNumber: 4608542,
|
||||
blockTimestamp: '2017-11-23T18:50:19.000Z',
|
||||
buyer: '0x316c55d1895a085c4b39a98ecb563f509301aaf7',
|
||||
logIndex: 28,
|
||||
nftAddress: '0xb1690C08E213a35Ed9bAb7B318DE14420FB57d8C',
|
||||
marketAddress: '0xb1690C08E213a35Ed9bAb7B318DE14420FB57d8C',
|
||||
tokenTicker: 'eth',
|
||||
meta: {
|
||||
cattribute_body: 'munchkin',
|
||||
cattribute_coloreyes: 'mintgreen',
|
||||
cattribute_colorprimary: 'orangesoda',
|
||||
cattribute_colorsecondary: 'coffee',
|
||||
cattribute_colortertiary: 'kittencream',
|
||||
cattribute_eyes: 'thicccbrowz',
|
||||
cattribute_mouth: 'soserious',
|
||||
cattribute_pattern: 'totesbasic',
|
||||
generation: '0',
|
||||
is_exclusive: false,
|
||||
is_fancy: false,
|
||||
},
|
||||
seller: '0xba52c75764d6f594735dc735be7f1830cdf58ddf',
|
||||
totalDecimalPrice: 0.00975138888888889,
|
||||
totalPrice: '9751388888888889',
|
||||
transactionHash: '0x468168419be7e442d5ff32d264fab24087b744bc2e37fdbac7024e1e74f4c6c8',
|
||||
usdPrice: 3.71957,
|
||||
currencyTransfer: {},
|
||||
image: '',
|
||||
composedOf: '',
|
||||
asset_link: '',
|
||||
seller_address_link: '',
|
||||
buyer_address_link: '',
|
||||
};
|
||||
|
||||
const expected: NonfungibleDotComTrade = {
|
||||
assetDescriptor: 'Kitty #1002',
|
||||
assetId: '1002',
|
||||
blockNumber: 4608542,
|
||||
blockTimestamp: 1511463019000,
|
||||
buyerAddress: '0x316c55d1895a085c4b39a98ecb563f509301aaf7',
|
||||
logIndex: 28,
|
||||
marketAddress: '0xb1690C08E213a35Ed9bAb7B318DE14420FB57d8C',
|
||||
meta: {
|
||||
cattribute_body: 'munchkin',
|
||||
cattribute_coloreyes: 'mintgreen',
|
||||
cattribute_colorprimary: 'orangesoda',
|
||||
cattribute_colorsecondary: 'coffee',
|
||||
cattribute_colortertiary: 'kittencream',
|
||||
cattribute_eyes: 'thicccbrowz',
|
||||
cattribute_mouth: 'soserious',
|
||||
cattribute_pattern: 'totesbasic',
|
||||
generation: '0',
|
||||
is_exclusive: false,
|
||||
is_fancy: false,
|
||||
},
|
||||
sellerAddress: '0xba52c75764d6f594735dc735be7f1830cdf58ddf',
|
||||
totalPrice: new BigNumber('9751388888888889'),
|
||||
transactionHash: '0x468168419be7e442d5ff32d264fab24087b744bc2e37fdbac7024e1e74f4c6c8',
|
||||
usdPrice: new BigNumber('3.71957'),
|
||||
publisher: 'cryptokitties',
|
||||
};
|
||||
|
||||
describe('nonfungible.com', () => {
|
||||
describe('_parseNonFungibleDotComTrade', () => {
|
||||
it(`converts NonfungibleDotComTradeResponse to NonfungibleDotComTrade entity`, () => {
|
||||
const actual = _parseNonFungibleDotComTrade(expected.publisher, input);
|
||||
expect(actual).deep.equal(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user