Skip to content

Commit

Permalink
feat: implement coincap market service (fixes shapeshift#281) (shapes…
Browse files Browse the repository at this point in the history
…hift#286)

* Add coincap

* add coincap adapter

* remove tokenId check

* readd tokenId check

* remove scripts to generate marketcap data

* apply linter fixes
  • Loading branch information
Zerquix18 authored and sdmg15 committed Jan 6, 2022
1 parent 62c2231 commit bacd7e2
Show file tree
Hide file tree
Showing 20 changed files with 642 additions and 9,329 deletions.
3 changes: 2 additions & 1 deletion packages/caip/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"build": "yarn clean && tsc --project tsconfig.build.json",
"clean": "rm -rf dist",
"dev": "tsc --watch",
"generate": "yarn build && node dist/adapters/coingecko/generate.js",
"generate-coingecko": "yarn build && node dist/adapters/coingecko/generate.js",
"generate-coincap": "yarn build && node dist/adapters/coincap/generate.js",
"test": "jest test",
"type-check": "tsc --project ./tsconfig.json --noEmit"
},
Expand Down
10 changes: 10 additions & 0 deletions packages/caip/src/adapters/coincap/generate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { coincapUrl } from './index'
import { fetchData, parseData, writeFiles } from './utils'

const main = async () => {
const data = await fetchData(coincapUrl)
const output = parseData(data)
await writeFiles(output)
}

main()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"bip122:000000000019d6689c085ae165831e93/slip44:0":"bitcoin"}

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions packages/caip/src/adapters/coincap/generated/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import bip122 from './bip122:000000000019d6689c085ae165831e93/adapter.json'
import eip155 from './eip155:1/adapter.json'

export { bip122, eip155 }
56 changes: 56 additions & 0 deletions packages/caip/src/adapters/coincap/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { ChainTypes, ContractTypes, NetworkTypes } from '@shapeshiftoss/types'

import { toCAIP19 } from './../../caip19/caip19'
import { CAIP19ToCoinCap, coincapToCAIP19 } from '.'

describe('coincap adapter', () => {
describe('coincapToCAIP19', () => {
it('can get CAIP19 for bitcoin', () => {
const chain = ChainTypes.Bitcoin
const network = NetworkTypes.MAINNET
const caip19 = toCAIP19({ chain, network })
expect(coincapToCAIP19('bitcoin')).toEqual(caip19)
})

it('can get CAIP19 id for ethereum', () => {
const chain = ChainTypes.Ethereum
const network = NetworkTypes.MAINNET
const caip19 = toCAIP19({ chain, network })
expect(coincapToCAIP19('ethereum')).toEqual(caip19)
})

it('can get CAIP19 id for FOX', () => {
const chain = ChainTypes.Ethereum
const network = NetworkTypes.MAINNET
const contractType = ContractTypes.ERC20
const tokenId = '0xc770eefad204b5180df6a14ee197d99d808ee52d'
const caip19 = toCAIP19({ chain, network, contractType, tokenId })
expect(coincapToCAIP19('fox-token')).toEqual(caip19)
})
})

describe('CAIP19ToCoinCap', () => {
it('can get coincap id for bitcoin CAIP19', () => {
const chain = ChainTypes.Bitcoin
const network = NetworkTypes.MAINNET
const caip19 = toCAIP19({ chain, network })
expect(CAIP19ToCoinCap(caip19)).toEqual('bitcoin')
})

it('can get coincap id for ethereum CAIP19', () => {
const chain = ChainTypes.Ethereum
const network = NetworkTypes.MAINNET
const caip19 = toCAIP19({ chain, network })
expect(CAIP19ToCoinCap(caip19)).toEqual('ethereum')
})

it('can get coincap id for FOX', () => {
const chain = ChainTypes.Ethereum
const network = NetworkTypes.MAINNET
const contractType = ContractTypes.ERC20
const tokenId = '0xc770eefad204b5180df6a14ee197d99d808ee52d'
const caip19 = toCAIP19({ chain, network, contractType, tokenId })
expect(CAIP19ToCoinCap(caip19)).toEqual('fox-token')
})
})
})
31 changes: 31 additions & 0 deletions packages/caip/src/adapters/coincap/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import toLower from 'lodash/toLower'

import * as adapters from './generated'

export const coincapUrl = 'https://api.coincap.io/v2/assets?limit=2000'

const generatedCAIP19ToCoinCapMap = Object.values(adapters).reduce((acc, cur) => ({
...acc,
...cur
})) as Record<string, string>

const invert = <T extends Record<string, string>>(data: T) =>
Object.entries(data).reduce((acc, [k, v]) => ((acc[v] = k), acc), {} as Record<string, string>)

const generatedCoinCapToCAIP19Map: Record<string, string> = invert(generatedCAIP19ToCoinCapMap)

export const coincapToCAIP19 = (id: string): string => {
const generated = generatedCoinCapToCAIP19Map[id]
if (!generated) {
throw new Error(`coincapToCAIP19: no caip19 found for id ${id}`)
}
return generated
}

export const CAIP19ToCoinCap = (caip19: string): string => {
const generated = generatedCAIP19ToCoinCapMap[toLower(caip19)]
if (!generated) {
throw new Error(`CAIP19ToCoinCap: no id found for caip19 ${toLower(caip19)}`)
}
return generated
}
115 changes: 115 additions & 0 deletions packages/caip/src/adapters/coincap/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import realFs from 'fs'

import { makeBtcData, parseData, parseEthData, writeFiles } from './utils'

const eth = {
id: 'ethereum',
rank: '2',
symbol: 'ETH',
name: 'Ethereum',
supply: '118739782.1240000000000000',
maxSupply: null,
marketCapUsd: '461557096820.5397856216327206',
volumeUsd24Hr: '13216473429.9114945699035335',
priceUsd: '3887.1310740534754598',
changePercent24Hr: '1.7301970732523704',
vwap24Hr: '3796.0013297212388563',
explorer: 'https://etherscan.io/'
}

const fox = {
id: 'fox-token',
rank: '396',
symbol: 'FOX',
name: 'FOX Token',
supply: '117022448.6044180000000000',
maxSupply: '1000001337.0000000000000000',
marketCapUsd: '76108238.1995641297877127',
volumeUsd24Hr: '1574012.7599955363585018',
priceUsd: '0.6503729763580659',
changePercent24Hr: '-3.1066427856364231',
vwap24Hr: '0.6546275575306273',
explorer: 'https://etherscan.io/token/0xc770eefad204b5180df6a14ee197d99d808ee52d'
}

const btc = {
id: 'bitcoin',
rank: '1',
symbol: 'BTC',
name: 'Bitcoin',
supply: '18901193.0000000000000000',
maxSupply: '21000000.0000000000000000',
marketCapUsd: '908356345541.2269154394485668',
volumeUsd24Hr: '19001957914.4173604708767279',
priceUsd: '48058.1487920485715076',
changePercent24Hr: '2.0370678507913180',
vwap24Hr: '47473.8260811456834087',
explorer: 'https://blockchain.info/'
}

jest.mock('fs', () => ({
promises: {
writeFile: jest.fn(async () => undefined)
}
}))

describe('parseEthData', () => {
it('can parse eth data', async () => {
const result = parseEthData([eth, fox])
const expected = {
'eip155:1/slip44:60': 'ethereum',
'eip155:1/erc20:0xc770eefad204b5180df6a14ee197d99d808ee52d': 'fox-token'
}
expect(result).toEqual(expected)
})

it('can parse btc data', async () => {
const result = makeBtcData()
const expected = { 'bip122:000000000019d6689c085ae165831e93/slip44:0': 'bitcoin' }
expect(result).toEqual(expected)
})
})

describe('parseData', () => {
it('can parse all data', async () => {
const result = parseData([eth, fox, btc])
const expected = {
'bip122:000000000019d6689c085ae165831e93': {
'bip122:000000000019d6689c085ae165831e93/slip44:0': 'bitcoin'
},
'eip155:1': {
'eip155:1/slip44:60': 'ethereum',
'eip155:1/erc20:0xc770eefad204b5180df6a14ee197d99d808ee52d': 'fox-token'
}
}
expect(result).toEqual(expected)
})
})

describe('writeFiles', () => {
it('can writeFiles', async () => {
const data = {
foo: {
caip19abc: 'bitcorn',
caip19def: 'efferium'
},
bar: {
caip19ghi: 'fox',
caip19jkl: 'shib'
}
}
const fooCaips = JSON.stringify(data.foo)
const barCaips = JSON.stringify(data.bar)
console.info = jest.fn()
await writeFiles(data)
expect(realFs.promises.writeFile).toBeCalledWith(
'./src/adapters/coincap/generated/foo/adapter.json',
fooCaips
)
expect(realFs.promises.writeFile).toBeCalledWith(
'./src/adapters/coincap/generated/bar/adapter.json',
barCaips
)
expect(console.info).toBeCalledWith('Generated CoinCap CAIP19 adapter data.')
})
})
72 changes: 72 additions & 0 deletions packages/caip/src/adapters/coincap/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { ChainTypes, ContractTypes, NetworkTypes } from '@shapeshiftoss/types'
import axios from 'axios'
import fs from 'fs'

import { toCAIP2 } from '../../caip2/caip2'
import { toCAIP19 } from './../../caip19/caip19'

export type CoinCapCoin = {
id: string
rank: string
symbol: string
name: string
supply: string
maxSupply: string | null
marketCapUsd: string
volumeUsd24Hr: string
priceUsd: string
changePercent24Hr: string
vwap24Hr: string
explorer: string | null
}

export const writeFiles = async (data: Record<string, Record<string, string>>) => {
const path = './src/adapters/coincap/generated/'
const file = '/adapter.json'
const writeFile = async ([k, v]: [string, unknown]) =>
await fs.promises.writeFile(`${path}${k}${file}`, JSON.stringify(v))
await Promise.all(Object.entries(data).map(writeFile))
console.info('Generated CoinCap CAIP19 adapter data.')
}

export const fetchData = async (URL: string) =>
(await axios.get<{ data: CoinCapCoin[] }>(URL)).data.data

export const parseEthData = (data: CoinCapCoin[]) => {
const ethCoins = data.filter(
({ id, explorer }) =>
(explorer && explorer.startsWith('https://etherscan.io/token/0x')) || id === 'ethereum'
)

const chain = ChainTypes.Ethereum
const network = NetworkTypes.MAINNET
const contractType = ContractTypes.ERC20

const result = ethCoins.reduce((acc, { id, explorer }) => {
let tokenId
if (id !== 'ethereum' && explorer) {
tokenId = explorer
.replace('https://etherscan.io/token/', '')
.split('#')[0]
.split('?')[0]
}
const caip19 = toCAIP19({ chain, network, ...(tokenId ? { contractType, tokenId } : {}) })
acc[caip19] = id
return acc
}, {} as Record<string, string>)

return result
}

export const makeBtcData = () => {
const chain = ChainTypes.Bitcoin
const network = NetworkTypes.MAINNET
const caip19 = toCAIP19({ chain, network })
return { [caip19]: 'bitcoin' }
}

export const parseData = (d: CoinCapCoin[]) => {
const ethMainnet = toCAIP2({ chain: ChainTypes.Ethereum, network: NetworkTypes.MAINNET })
const btcMainnet = toCAIP2({ chain: ChainTypes.Bitcoin, network: NetworkTypes.MAINNET })
return { [ethMainnet]: parseEthData(d), [btcMainnet]: makeBtcData() }
}
4 changes: 2 additions & 2 deletions packages/caip/src/adapters/coingecko/generate.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { url } from './index'
import { coingeckoUrl } from './index'
import { fetchData, parseData, writeFiles } from './utils'

const main = async () => {
const data = await fetchData(url)
const data = await fetchData(coingeckoUrl)
const output = parseData(data)
await writeFiles(output)
}
Expand Down
2 changes: 1 addition & 1 deletion packages/caip/src/adapters/coingecko/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import toLower from 'lodash/toLower'

import * as adapters from './generated'

export const url = 'https://api.coingecko.com/api/v3/coins/list?include_platform=true'
export const coingeckoUrl = 'https://api.coingecko.com/api/v3/coins/list?include_platform=true'

const generatedCAIP19ToCoingeckoMap = Object.values(adapters).reduce((acc, cur) => ({
...acc,
Expand Down
2 changes: 2 additions & 0 deletions packages/caip/src/adapters/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './coingecko'
export * from './coincap'
2 changes: 1 addition & 1 deletion packages/caip/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as adapters from './adapters/coingecko'
import * as adapters from './adapters'
import * as caip2 from './caip2/caip2'
import * as caip10 from './caip10/caip10'
import * as caip19 from './caip19/caip19'
Expand Down
1 change: 0 additions & 1 deletion packages/market-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"build": "yarn clean && tsc --project tsconfig.build.json",
"clean": "rm -rf dist",
"dev": "tsc --watch",
"generate": "yarn build && node ./dist/coingecko/generateMarketCapData.js",
"test": "jest test",
"type-check": "tsc --project ./tsconfig.json --noEmit"
},
Expand Down
14 changes: 14 additions & 0 deletions packages/market-service/src/coincap/coincap-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export type CoinCapMarketCap = {
id: string
rank: string
symbol: string
name: string
supply: string
maxSupply: string | null
marketCapUsd: string
volumeUsd24Hr: string
priceUsd: string
changePercent24Hr: string
vwap24Hr: string
explorer: string
}
Loading

0 comments on commit bacd7e2

Please sign in to comment.