diff --git a/package.json b/package.json index 24e3fda..7e3fca5 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "json-bigint": "^1.0.0", "node-fetch": "^2.6.9", "object-hash": "^3.0.0", - "viem": "^1.15.1", + "viem": "^1.16.3", "zod": "^3.22.3" } } diff --git a/src/govv3/utils/markdownUtils.ts b/src/govv3/utils/markdownUtils.ts index 9eec6d2..c0a05a7 100644 --- a/src/govv3/utils/markdownUtils.ts +++ b/src/govv3/utils/markdownUtils.ts @@ -11,9 +11,10 @@ export function boolToMarkdown(value: boolean) { * @param address to be linked * @param code whether to link to the code tab */ -export function toAddressLink(address: Hex, md: boolean, client: PublicClient): string { +export function toAddressLink(address: Hex, md: boolean, client?: PublicClient): string { + if (!client) return address; const link = `${client.chain?.blockExplorers?.default.url}/address/${address}`; - if (md) return `[${address}](${link})`; + if (md) return toMarkdownLink(link, address); return link; } @@ -22,12 +23,17 @@ export function toAddressLink(address: Hex, md: boolean, client: PublicClient): * @param address to be linked * @param code whether to link to the code tab */ -export function toTxLink(txn: Hex, md: boolean, client: PublicClient): string { +export function toTxLink(txn: Hex, md: boolean, client?: PublicClient): string { + if (!client) return txn; const link = `${client.chain?.blockExplorers?.default.url}/tx/${txn}`; - if (md) return `[${txn}](${link})`; + if (md) return toMarkdownLink(link, txn); return link; } +export function toMarkdownLink(link: string, title?: any) { + return `[${title || link}](${link})`; +} + export function renderCheckResult(check: { name: string }, result: CheckResult) { let response = `### Check: ${check.name} ${boolToMarkdown(!result.errors.length)}\n\n`; if (result.errors.length) response += `#### Errors\n\n${result.errors.join('\n')}\n\n`; diff --git a/src/reports/reserve.ts b/src/reports/reserve.ts index e111773..e243948 100644 --- a/src/reports/reserve.ts +++ b/src/reports/reserve.ts @@ -1,59 +1,25 @@ -import { formatUnits } from 'viem'; +import { Hex, formatUnits } from 'viem'; import { AaveV3Reserve, CHAIN_ID } from './snapshot-types'; +import { toAddressLink } from '../govv3/utils/markdownUtils'; +import { CHAIN_ID_CLIENT_MAP } from '../utils/rpcClients'; -export const getBlockExplorerLink = (chain: number, address: string): string => { - switch (chain) { - case CHAIN_ID.MAINNET: return `[${address}](https://etherscan.io/address/${address})`; - case CHAIN_ID.OPTIMISM: return `[${address}](https://optimistic.etherscan.io/address/${address})`; - case CHAIN_ID.POLYGON: return `[${address}](https://polygonscan.com/address/${address})`; - case CHAIN_ID.FANTOM: return `[${address}](https://ftmscan.com/address/${address})`; - case CHAIN_ID.ARBITRUM: return `[${address}](https://arbiscan.io/address/${address})`; - case CHAIN_ID.AVALANCHE: return `[${address}](https://snowtrace.io/address/${address})`; - case CHAIN_ID.METIS: return `[${address}](https://andromeda-explorer.metis.io/address/${address})`; - case CHAIN_ID.BASE: return `[${address}](https://basescan.org/address/${address})`; - default: return address; - }; -} - -export function renderReserveValue( - key: T, - reserve: AaveV3Reserve, - chainId: CHAIN_ID -) { - if ( - [ - 'reserveFactor', - 'liquidationProtocolFee', - 'liquidationThreshold', - 'ltv', - ].includes(key) - ) +export function renderReserveValue(key: T, reserve: AaveV3Reserve, chainId: CHAIN_ID) { + if (['reserveFactor', 'liquidationProtocolFee', 'liquidationThreshold', 'ltv'].includes(key)) return `${formatUnits(BigInt(reserve[key]), 2)} %`; - if (['supplyCap', 'borrowCap'].includes(key)) - return `${reserve[key].toLocaleString('en-US')} ${reserve.symbol}`; - if (key === 'debtCeiling') - return `${Number(formatUnits(BigInt(reserve[key]), 2)).toLocaleString( - 'en-US' - )} $`; - if (key === 'liquidationBonus') - return reserve[key] === 0 - ? '0 %' - : `${((reserve[key] as number) - 10000) / 100} %`; - if (key === 'interestRateStrategy') - return getBlockExplorerLink(chainId, reserve[key] as string); + if (['supplyCap', 'borrowCap'].includes(key)) return `${reserve[key].toLocaleString('en-US')} ${reserve.symbol}`; + if (key === 'debtCeiling') return `${Number(formatUnits(BigInt(reserve[key]), 2)).toLocaleString('en-US')} $`; + if (key === 'liquidationBonus') return reserve[key] === 0 ? '0 %' : `${((reserve[key] as number) - 10000) / 100} %`; + if (key === 'interestRateStrategy') return toAddressLink(reserve[key] as Hex, true, CHAIN_ID_CLIENT_MAP[chainId]); if (key === 'oracleLatestAnswer' && reserve.oracleDecimals) return formatUnits(BigInt(reserve[key]), reserve.oracleDecimals); - if (typeof reserve[key] === 'number') - return reserve[key].toLocaleString('en-US'); + if (typeof reserve[key] === 'number') return reserve[key].toLocaleString('en-US'); if (typeof reserve[key] === 'string' && /0x.+/.test(reserve[key] as string)) - return getBlockExplorerLink(chainId, reserve[key] as string); + return toAddressLink(reserve[key] as Hex, true, CHAIN_ID_CLIENT_MAP[chainId]); return reserve[key]; } function renderReserveHeadline(reserve: AaveV3Reserve, chainId: CHAIN_ID) { - return `#### ${reserve.symbol} (${getBlockExplorerLink(chainId, - reserve.underlying - )})\n\n`; + return `#### ${reserve.symbol} (${toAddressLink(reserve.underlying as Hex, true, CHAIN_ID_CLIENT_MAP[chainId])})\n\n`; } const ORDER: (keyof AaveV3Reserve)[] = [ @@ -124,8 +90,7 @@ export type ReserveDiff = { export function renderReserveDiff(diff: ReserveDiff, chainId: CHAIN_ID) { let content = renderReserveHeadline(diff as AaveV3Reserve, chainId); - content += - '| description | value before | value after |\n| --- | --- | --- |\n'; + content += '| description | value before | value after |\n| --- | --- | --- |\n'; (Object.keys(diff) as (keyof AaveV3Reserve)[]) .filter((key) => diff[key].hasOwnProperty('from')) .sort(sortReserveKeys) @@ -134,11 +99,7 @@ export function renderReserveDiff(diff: ReserveDiff, chainId: CHAIN_ID) { key, { ...diff, [key]: diff[key].from }, chainId - )} | ${renderReserveValue( - key, - { ...diff, [key]: diff[key].to }, - chainId - )} |\n`; + )} | ${renderReserveValue(key, { ...diff, [key]: diff[key].to }, chainId)} |\n`; }); return content; } diff --git a/src/utils/rpcClients.ts b/src/utils/rpcClients.ts index db013be..7ba8ec8 100644 --- a/src/utils/rpcClients.ts +++ b/src/utils/rpcClients.ts @@ -1,5 +1,17 @@ -import { createPublicClient, http, fallback } from 'viem'; -import { mainnet, arbitrum, polygon, optimism, metis, base, sepolia, goerli, bsc } from 'viem/chains'; +import { createPublicClient, http, fallback, PublicClient } from 'viem'; +import { + mainnet, + arbitrum, + polygon, + optimism, + metis, + base, + sepolia, + goerli, + bsc, + fantom, + avalanche, +} from 'viem/chains'; export const mainnetClient = createPublicClient({ chain: mainnet, @@ -31,22 +43,35 @@ export const baseClient = createPublicClient({ transport: http(process.env.RPC_BASE), }); -export const bscClient = createPublicClient({ +export const fantomClient = createPublicClient({ + chain: fantom, + transport: http(process.env.RPC_FANTOM), +}); + +export const bnbClient = createPublicClient({ chain: bsc, - transport: http(process.env.RPC_BSC), + transport: http(process.env.RPC_BNB), +}); + +export const avalancheClient = createPublicClient({ + chain: avalanche, + transport: http(process.env.RPC_AVALANCHE), }); export const sepoliaClient = createPublicClient({ chain: sepolia, transport: http(process.env.RPC_SEPOLIA) }); export const goerliClient = createPublicClient({ chain: goerli, transport: http(process.env.RPC_GOERLI) }); -export const CHAIN_ID_CLIENT_MAP = { +export const CHAIN_ID_CLIENT_MAP: Record = { [mainnet.id]: mainnetClient, [arbitrum.id]: arbitrumClient, [polygon.id]: polygonClient, - [optimism.id]: optimismClient, + [optimism.id]: optimismClient as PublicClient, [metis.id]: metisClient, - [base.id]: baseClient, + [base.id]: baseClient as PublicClient, [sepolia.id]: sepoliaClient, [goerli.id]: goerliClient, + [fantom.id]: fantomClient, + [bsc.id]: bnbClient, + [avalanche.id]: avalancheClient, } as const; diff --git a/yarn.lock b/yarn.lock index e342b4d..cc6e968 100644 --- a/yarn.lock +++ b/yarn.lock @@ -594,13 +594,6 @@ resolved "https://registry.yarnpkg.com/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz#18b97a972f94f60a679fd5c796d96421b9abb9fd" integrity sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g== -"@types/ws@^8.5.5": - version "8.5.5" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.5.tgz#af587964aa06682702ee6dcbc7be41a80e4b28eb" - integrity sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg== - dependencies: - "@types/node" "*" - "@types/yargs-parser@*": version "21.0.0" resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz" @@ -1485,10 +1478,10 @@ isexe@^2.0.0: resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -isomorphic-ws@5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz" - integrity sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw== +isows@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.3.tgz#93c1cf0575daf56e7120bab5c8c448b0809d0d74" + integrity sha512-2cKei4vlmg2cxEjm3wVSqn8pcoRF/LX/wpifuuNquFO4SQmPwarClT+SUCA2lt+l581tTeZIPIZuIDo2jWN1fg== it-all@^1.0.5: version "1.0.6" @@ -2537,19 +2530,18 @@ varint@^6.0.0: resolved "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz" integrity sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg== -viem@^1.15.1: - version "1.15.1" - resolved "https://registry.yarnpkg.com/viem/-/viem-1.15.1.tgz#030fb900d8099e6a1bd16164fa4e1e5b8e24607a" - integrity sha512-lxk8wwUK7ZivYAUZ6pH+9Y6jjrfXXjafCOoASa4lw3ULUCT2BajU4SELarlxJQimpsFd7OZD4m4iEXYLF/bt6w== +viem@^1.16.3: + version "1.16.3" + resolved "https://registry.yarnpkg.com/viem/-/viem-1.16.3.tgz#154e9a93bc5434d46fcae49ed0d6ec0a34f4d2c9" + integrity sha512-5IErEXuJVrSoMnEYidOh5lzPpTOZ/e33riDiZCxToap2DpLBMDpZCKoZZgroQgH7mAjfZcylv/LS+YIP/Gmeyg== dependencies: "@adraffy/ens-normalize" "1.9.4" "@noble/curves" "1.2.0" "@noble/hashes" "1.3.2" "@scure/bip32" "1.3.2" "@scure/bip39" "1.2.1" - "@types/ws" "^8.5.5" abitype "0.9.8" - isomorphic-ws "5.0.0" + isows "1.0.3" ws "8.13.0" vite-node@0.34.3: