Last active
February 13, 2024 13:06
-
-
Save afsardo/44aa74664c3a322aba452b23541ce6f5 to your computer and use it in GitHub Desktop.
Generate asset list
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export type Token = { | |
protocol: string; | |
symbol: string; | |
token: string; | |
icon?: string; | |
chain?: string; | |
decimals?: number; | |
chainId?: string; | |
priceUsd?: number; | |
isBlocked?: boolean; | |
isHidden?: boolean; | |
coingeckoId?: string; | |
originDenom?: string; | |
originChainId?: string; | |
}; | |
export type TokenBaseList = Token[]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import fs from "fs"; | |
import path from "path"; | |
import { config as dotenvConfig } from "dotenv"; | |
import prettier from "prettier"; | |
import { TokenBaseList } from "~/types/local"; | |
import { logger } from "~/utils/logger"; | |
import { endTimer, startTimer } from "~/utils/timer"; | |
import { downloadAndSaveImage, tokensDir } from "./utils"; | |
dotenvConfig(); | |
const repo = "astroport-fi/astroport-token-lists"; | |
const GITHUB_RAW_DEFAULT_BASEURL = "https://raw.githubusercontent.com"; | |
const BRANCH_NAME = "main"; | |
function getFilePath({ | |
chainName, | |
mainnet, | |
}: { | |
chainName: string; | |
mainnet: boolean; | |
}) { | |
return `tokenLists${mainnet ? "" : "/testnets"}/${chainName}.json`; | |
} | |
async function generateAssetLists({ | |
chainAssets, | |
}: { | |
chainAssets: { | |
chainId: string; | |
assets: TokenBaseList; | |
}[]; | |
}) { | |
let content = ""; | |
content += ` | |
import { Token } from "~/types/local"; | |
export const AssetLists: { chainId: string; assets: Token[] }[] = ${JSON.stringify( | |
chainAssets, | |
null, | |
2, | |
// replace all icon paths with the generated path | |
).replaceAll("/img", tokensDir)}; | |
`; | |
const prettierConfig = await prettier.resolveConfig("./"); | |
const formatted = await prettier.format(content, { | |
...prettierConfig, | |
parser: "typescript", | |
}); | |
const dirPath = "src/config/generated"; | |
const fileName = "asset-lists.ts"; | |
try { | |
const filePath = path.join(dirPath, fileName); | |
if (!fs.existsSync(dirPath)) { | |
fs.mkdirSync(dirPath); | |
} | |
fs.writeFileSync(filePath, formatted, { | |
encoding: "utf8", | |
flag: "w", | |
}); | |
} catch (e) { | |
logger.error(`Error writing ${fileName}: ${e}`); | |
} | |
logger.debug("Successfully generated asset lists."); | |
} | |
async function generateAssetImages({ imageUrls }: { imageUrls: string[] }) { | |
startTimer("downloadImages"); | |
logger.debug("Successfully downloaded images."); | |
for await (const url of imageUrls) { | |
await downloadAndSaveImage(url); | |
} | |
const endTime = endTimer("downloadImages"); | |
logger.debug( | |
`Successfully downloaded images. (Execution time: ${endTime[0]}s ${endTime[1]}ms)`, | |
); | |
} | |
async function queryGithubFile<T>({ | |
repo, | |
filePath, | |
commitHash, | |
baseUrl = GITHUB_RAW_DEFAULT_BASEURL, | |
}: { | |
repo: string; | |
filePath: string; | |
commitHash?: string; | |
baseUrl?: string; | |
}): Promise<T> { | |
const url = new URL( | |
`/${repo}/${commitHash ? commitHash : BRANCH_NAME}/${filePath}`, | |
baseUrl, | |
); | |
try { | |
const res = await fetch(url.toString()); | |
return res.json(); | |
} catch (e) { | |
logger.error(`Error fetching ${url.toString()}: ${e}`); | |
return Promise.reject(e); | |
} | |
} | |
const chains = [ | |
{ | |
name: "terra2", | |
chainId: "phoenix-1", | |
mainnet: true, | |
}, | |
{ | |
name: "neutron", | |
chainId: "neutron-1", | |
mainnet: true, | |
}, | |
{ | |
name: "injective", | |
chainId: "injective-1", | |
mainnet: true, | |
}, | |
{ | |
name: "sei", | |
chainId: "pacific-1", | |
mainnet: true, | |
}, | |
{ | |
name: "terra2", | |
chainId: "pisco-1", | |
mainnet: false, | |
}, | |
{ | |
name: "neutron", | |
chainId: "pion-1", | |
mainnet: false, | |
}, | |
{ | |
name: "injective", | |
chainId: "injective-888", | |
mainnet: false, | |
}, | |
{ | |
name: "sei", | |
chainId: "atlantic-2", | |
mainnet: false, | |
}, | |
]; | |
async function main() { | |
const responses = await Promise.all( | |
chains.map(async ({ chainId, name, mainnet }) => { | |
const filePath = getFilePath({ chainName: name, mainnet: mainnet }); | |
const githubFile = await queryGithubFile<TokenBaseList>({ | |
repo, | |
filePath, | |
}); | |
return { | |
chainId: chainId, | |
assets: githubFile, | |
}; | |
}), | |
); | |
let assetList: TokenBaseList = []; | |
responses.forEach((chain) => { | |
assetList = [...assetList, ...chain.assets]; | |
}); | |
await generateAssetLists({ chainAssets: responses }); | |
await generateAssetImages({ | |
imageUrls: assetList.map( | |
(asset) => | |
`${GITHUB_RAW_DEFAULT_BASEURL}/${repo}/${BRANCH_NAME}${asset.icon}`, | |
), | |
}); | |
} | |
main().catch((e) => { | |
logger.error(e); | |
process.exit(1); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import fs from "fs"; | |
import path from "path"; | |
import { Readable } from "stream"; | |
import { finished } from "stream/promises"; | |
export const tokensDir = "/tokens/generated"; | |
export function getNodeImageRelativeFilePath(imageUrl: string) { | |
const urlParts = imageUrl.split("/"); | |
const fileName = urlParts[urlParts.length - 1]; | |
return path.join("/public", tokensDir, `${fileName}`); | |
} | |
/** | |
* Download an image from the provided URL and save it to the local file system. | |
* @param {string} imageUrl The URL of the image to download. | |
* @returns {Promise<string>} The filename of the saved image. | |
*/ | |
export async function downloadAndSaveImage(imageUrl: string) { | |
// Ensure the tokens directory exists. | |
if (!fs.existsSync(path.resolve() + "/public" + tokensDir)) { | |
fs.mkdirSync(path.resolve() + "/public" + tokensDir, { recursive: true }); | |
} | |
const filePath = path.resolve() + getNodeImageRelativeFilePath(imageUrl); | |
if (fs.existsSync(filePath)) { | |
return null; | |
} | |
// Fetch the image from the URL. | |
const response = await fetch(imageUrl); | |
if (!response.ok) { | |
throw new Error( | |
`Failed to fetch image from ${imageUrl}: ${response.statusText}`, | |
); | |
} | |
// Save the image to the file system. | |
const fileStream = fs.createWriteStream(filePath, { flags: "w" }); | |
await finished( | |
Readable.fromWeb( | |
response.body as import("stream/web").ReadableStream<any>, | |
).pipe(fileStream), | |
); | |
const splitPath = filePath.split("/"); | |
return splitPath[splitPath.length - 1]; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment