Skip to content

Commit

Permalink
feat: override aux list props (#768)
Browse files Browse the repository at this point in the history
* refactor: extract processTokenList into a new file

* feat: add option to override token details

* chore: update lists

* fix: avoid issue where all values use the same object
  • Loading branch information
alfetopito authored Dec 18, 2024
1 parent b33f564 commit d4f53bb
Show file tree
Hide file tree
Showing 9 changed files with 5,918 additions and 5,876 deletions.
3,928 changes: 1,964 additions & 1,964 deletions src/public/CoinGecko.1.json

Large diffs are not rendered by default.

3,692 changes: 1,846 additions & 1,846 deletions src/public/CoinGecko.42161.json

Large diffs are not rendered by default.

3,834 changes: 1,917 additions & 1,917 deletions src/public/CoinGecko.8453.json

Large diffs are not rendered by default.

14 changes: 11 additions & 3 deletions src/public/Uniswap.8453.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
],
"version": {
"major": 0,
"minor": 0,
"patch": 1
"minor": 1,
"patch": 0
},
"tokens": [
{
Expand Down Expand Up @@ -907,6 +907,14 @@
"decimals": 18,
"logoURI": "https://assets.coingecko.com/coins/images/24413/large/STG_LOGO.png?1696523595"
},
{
"chainId": 8453,
"address": "0x820c137fa70c8691f0e44dc420a5e53c168921dc",
"name": "USDS",
"symbol": "USDS",
"decimals": 18,
"logoURI": "https://assets.coingecko.com/coins/images/39926/large/usds.webp?1726666683"
},
{
"chainId": 8453,
"address": "0x9c632e6aaa3ea73f91554f8a3cb2ed2f29605e0c",
Expand All @@ -918,5 +926,5 @@
],
"name": "Uniswap on Base",
"logoURI": "ipfs://QmNa8mQkrNKp1WEEeGjFezDmDeodkWRevGFN8JCV7b4Xir",
"timestamp": "2024-12-17T16:22:23.691Z"
"timestamp": "2024-12-18T10:26:15.733Z"
}
17 changes: 14 additions & 3 deletions src/scripts/auxLists/coingecko.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { SupportedChainId } from '@cowprotocol/cow-sdk'
import { processTokenList } from './processTokenList'
import {
COINGECKO_CHAINS,
type CoingeckoIdsMap,
fetchWithApiKey,
getTokenList,
processTokenList,
Overrides,
OverridesPerChain,
TokenInfo,
TOP_TOKENS_COUNT,
VS_CURRENCY,
Expand Down Expand Up @@ -131,6 +133,7 @@ async function getTokenVolumes(
async function fetchAndProcessCoingeckoTokensForChain(
chainId: SupportedChainId,
coingeckoIdsMap: CoingeckoIdsMap,
overrides: Overrides,
): Promise<void> {
try {
const tokens = await getTokenList(chainId)
Expand All @@ -141,6 +144,7 @@ async function fetchAndProcessCoingeckoTokensForChain(
tokens: topTokens.map(({ token, volume }) => ({ ...token, volume })),
prefix: 'CoinGecko',
logo: COINGECKO_LOGO,
overrides,
logMessage: `Top ${TOP_TOKENS_COUNT} tokens`,
})
} catch (error) {
Expand All @@ -151,10 +155,17 @@ async function fetchAndProcessCoingeckoTokensForChain(
/**
* Main function to fetch and process CoinGecko tokens for all supported chains
*/
export async function fetchAndProcessCoingeckoTokens(coingeckoIdsMap: CoingeckoIdsMap): Promise<void> {
export async function fetchAndProcessCoingeckoTokens(
coingeckoIdsMap: CoingeckoIdsMap,
overrides: OverridesPerChain,
): Promise<void> {
const supportedChains = Object.keys(COINGECKO_CHAINS)
.map(Number)
.filter((chain) => COINGECKO_CHAINS[chain as SupportedChainId])

await Promise.all(supportedChains.map((chain) => fetchAndProcessCoingeckoTokensForChain(chain, coingeckoIdsMap)))
await Promise.all(
supportedChains.map((chain) =>
fetchAndProcessCoingeckoTokensForChain(chain, coingeckoIdsMap, overrides[chain as SupportedChainId]),
),
)
}
10 changes: 7 additions & 3 deletions src/scripts/auxLists/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { mapSupportedNetworks, SupportedChainId } from '@cowprotocol/cow-sdk'
import { fetchAndProcessCoingeckoTokens } from './coingecko'
import { fetchAndProcessUniswapTokens } from './uniswap'
import { getCoingeckoTokenIdsMap } from './utils'
import { getCoingeckoTokenIdsMap, OverridesPerChain } from './utils'

const OVERRIDES: OverridesPerChain = mapSupportedNetworks(() => ({}))
OVERRIDES[SupportedChainId.BASE]['0x18dd5b087bca9920562aff7a0199b96b9230438b'] = { decimals: 8 } // incorrect decimals set on CoinGecko's list

async function main(): Promise<void> {
const COINGECKO_IDS_MAP = await getCoingeckoTokenIdsMap()

fetchAndProcessCoingeckoTokens(COINGECKO_IDS_MAP)
fetchAndProcessUniswapTokens(COINGECKO_IDS_MAP)
fetchAndProcessCoingeckoTokens(COINGECKO_IDS_MAP, OVERRIDES)
fetchAndProcessUniswapTokens(COINGECKO_IDS_MAP, OVERRIDES)
}

main()
146 changes: 146 additions & 0 deletions src/scripts/auxLists/processTokenList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { SupportedChainId } from '@cowprotocol/cow-sdk'
import { TokenList } from '@uniswap/token-lists'
import * as fs from 'fs'
import path from 'path'
import { DISPLAY_CHAIN_NAMES, Overrides, TokenInfo } from './utils'

const FORMATTER = new Intl.NumberFormat('en-us', { style: 'currency', currency: 'USD' })

function getEmptyList(): Partial<TokenList> {
return {
keywords: ['defi'],
version: { major: 0, minor: 0, patch: 0 },
tokens: [],
}
}

function getListName(chain: SupportedChainId, prefix: string): string {
return `${prefix} on ${DISPLAY_CHAIN_NAMES[chain]}`
}

function getOutputPath(prefix: string, chainId: SupportedChainId): string {
return `src/public/${prefix}.${chainId}.json`
}

function getLocalTokenList(listPath: string, defaultEmptyList: Partial<TokenList>): Partial<TokenList> {
try {
return JSON.parse(fs.readFileSync(listPath, 'utf8'))
} catch (error) {
console.warn(`Error reading token list from ${listPath}:`, error)
return defaultEmptyList
}
}

function getTokenListVersion(list: Partial<TokenList>, tokens: TokenInfo[]): TokenList['version'] {
const version = list.version || { major: 0, minor: 0, patch: 0 }
const currentAddresses = new Set(list.tokens?.map((token) => token.address.toLowerCase()) || [])
const newAddresses = new Set(tokens.map((token) => token.address.toLowerCase()))

// Check for removed tokens
if (newAddresses.size < currentAddresses.size || !isSubsetOf(currentAddresses, newAddresses)) {
return { major: version.major + 1, minor: 0, patch: 0 }
}

// Check for added tokens
if (newAddresses.size > currentAddresses.size) {
return { ...version, minor: version.minor + 1, patch: 0 }
}

// Check for changes in token details
if (currentAddresses.size === newAddresses.size) {
for (const listToken of list.tokens || []) {
const token = tokens.find((token) => token.address.toLowerCase() === listToken.address.toLowerCase())
if (
token &&
(listToken.name !== token.name ||
listToken.symbol !== token.symbol ||
listToken.decimals !== token.decimals ||
listToken.logoURI !== token.logoURI)
) {
return { ...version, patch: version.patch + 1 }
}
}
}

return version
}

interface SaveUpdatedTokensParams {
chainId: SupportedChainId
prefix: string
logo: string
tokens: TokenInfo[]
listName: string
}

function saveUpdatedTokens({ chainId, prefix, logo, tokens, listName }: SaveUpdatedTokensParams): void {
const tokenListPath = path.join(getOutputPath(prefix, chainId))
const currentList = getLocalTokenList(tokenListPath, getEmptyList())

try {
const version = getTokenListVersion(currentList, tokens)

if (JSON.stringify(currentList.version) !== JSON.stringify(version)) {
const updatedList: TokenList = {
...currentList,
tokens,
name: listName,
logoURI: logo,
version,
timestamp: new Date().toISOString(),
}
fs.writeFileSync(tokenListPath, JSON.stringify(updatedList, null, 2))
console.log(`Token list saved to ${tokenListPath}`)
} else {
console.log(`No changes detected. Token list not updated.`)
}
} catch (error) {
console.error(`Error saving token list to ${tokenListPath}:`, error)
}
}

interface ProcessTokenListParams {
chainId: SupportedChainId
tokens: TokenInfo[]
prefix: string
logo: string
overrides?: Overrides
logMessage: string
}

export async function processTokenList({
chainId,
tokens,
prefix,
logo,
overrides = {},
logMessage,
}: ProcessTokenListParams): Promise<void> {
console.log(`🥇 ${logMessage} on chain ${chainId}`)

tokens.forEach((token, index) => {
const volumeStr = token.volume ? `: ${FORMATTER.format(token.volume)}` : ''
console.log(`\t-${(index + 1).toString().padStart(3, '0')}) ${token.name} (${token.symbol})${volumeStr}`)
})

const updatedTokens = tokens.map(({ volume: _, ...token }) => {
const override = overrides[token.address.toLowerCase()]
return {
...token,
...override,
logoURI: token.logoURI ? token.logoURI.replace(/thumb/, 'large') : undefined,
}
})

const listName = getListName(chainId, prefix)
saveUpdatedTokens({ chainId, prefix, logo, tokens: updatedTokens, listName })
}

function isSubsetOf(setA: Set<string>, setB: Set<string>): boolean {
for (let item of setA) {
if (!setB.has(item)) {
return false
}
}
return true
}
14 changes: 11 additions & 3 deletions src/scripts/auxLists/uniswap.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { SupportedChainId } from '@cowprotocol/cow-sdk'
import { COINGECKO_CHAINS, type CoingeckoIdsMap, getTokenList, processTokenList, TokenInfo } from './utils'
import { processTokenList } from './processTokenList'
import { COINGECKO_CHAINS, type CoingeckoIdsMap, getTokenList, Overrides, OverridesPerChain, TokenInfo } from './utils'

const UNISWAP_LIST = 'https://gateway.ipfs.io/ipns/tokens.uniswap.org'
const UNISWAP_LOGO = 'ipfs://QmNa8mQkrNKp1WEEeGjFezDmDeodkWRevGFN8JCV7b4Xir'
Expand Down Expand Up @@ -81,6 +82,7 @@ async function fetchAndProcessUniswapTokensForChain(
chainId: SupportedChainId,
coingeckoIdsMap: CoingeckoIdsMap,
uniswapTokens: TokenInfo[],
overrides: Overrides,
): Promise<void> {
try {
const coingeckoTokens = await getTokenList(chainId)
Expand All @@ -91,6 +93,7 @@ async function fetchAndProcessUniswapTokensForChain(
tokens,
prefix: 'Uniswap',
logo: UNISWAP_LOGO,
overrides,
logMessage: `Uniswap tokens`,
})
} catch (error) {
Expand All @@ -101,14 +104,19 @@ async function fetchAndProcessUniswapTokensForChain(
/**
* Main function to fetch and process Uniswap tokens for all supported chains
*/
export async function fetchAndProcessUniswapTokens(coingeckoIdsMap: CoingeckoIdsMap): Promise<void> {
export async function fetchAndProcessUniswapTokens(
coingeckoIdsMap: CoingeckoIdsMap,
overrides: OverridesPerChain,
): Promise<void> {
const uniTokens = await getUniswapTokens()

const supportedChains = Object.keys(COINGECKO_CHAINS)
.map(Number)
.filter((chain) => chain !== SupportedChainId.MAINNET && COINGECKO_CHAINS[chain as SupportedChainId])

await Promise.all(
supportedChains.map((chain) => fetchAndProcessUniswapTokensForChain(chain, coingeckoIdsMap, uniTokens)),
supportedChains.map((chain) =>
fetchAndProcessUniswapTokensForChain(chain, coingeckoIdsMap, uniTokens, overrides[chain as SupportedChainId]),
),
)
}
Loading

0 comments on commit d4f53bb

Please sign in to comment.