Skip to content
This repository has been archived by the owner on Apr 11, 2023. It is now read-only.

feat: implement coincap market service (fixes #281) #286

Merged
merged 6 commits into from
Dec 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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