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 4 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.')
})
})
67 changes: 67 additions & 0 deletions packages/caip/src/adapters/coincap/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
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

Large diffs are not rendered by default.

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