├── proxies.example.txt ├── .gitignore ├── privates.example.txt ├── .prettierrc ├── src ├── periphery │ ├── menu.ts │ ├── telegram.ts │ ├── utils.ts │ ├── relayBridge.ts │ ├── sushiswap.ts │ ├── odosAggregator.ts │ └── web3Client.ts ├── utils │ ├── types.ts │ ├── tokenlists │ │ ├── Opbnb.json │ │ ├── Nova.json │ │ ├── Scroll.json │ │ ├── Core.json │ │ ├── Mantle.json │ │ ├── Celo.json │ │ └── Linea.json │ ├── abi │ │ ├── WETH.json │ │ ├── ERC20.json │ │ ├── Multicall2.json │ │ └── UniswapV2Router02.json │ ├── helpers.ts │ └── constants.ts └── core │ ├── nativeSender.ts │ └── garbageCollector.ts ├── tsconfig.json ├── package.json ├── config.example.ts ├── index.ts └── README.md /proxies.example.txt: -------------------------------------------------------------------------------- 1 | login:pass@ip:port -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | typechain 3 | config.ts 4 | 5 | .env 6 | privates.txt 7 | proxies.txt 8 | *.csv 9 | KEY_CHECK_REGEXP -------------------------------------------------------------------------------- /privates.example.txt: -------------------------------------------------------------------------------- 1 | (Use this when addresses are not needed) 2 | key1 3 | key2 4 | ... 5 | 6 | OR 7 | 8 | (use this when addresses are needed) 9 | key1,addr1 10 | key2,addr2 11 | ... -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "useTabs": false, 4 | "semi": false, 5 | "printWidth": 150, 6 | "trailingComma": "none", 7 | "requireConfig": false, 8 | "singleQuote": true, 9 | "bracketSpacing": false, 10 | "experimentalTernaries": true 11 | } 12 | -------------------------------------------------------------------------------- /src/periphery/menu.ts: -------------------------------------------------------------------------------- 1 | import select from '@inquirer/select' 2 | import {scenarios} from '../utils/constants' 3 | 4 | class Menu { 5 | async chooseTask() { 6 | const questions = { 7 | name: 'mode', 8 | type: 'list', 9 | message: `Choose task`, 10 | choices: scenarios, 11 | default: 'Balance cheker', 12 | loop: true 13 | } 14 | if (questions.choices.length < 2) { 15 | return questions.choices[0].value 16 | } 17 | const answers = await select(questions) 18 | return answers 19 | } 20 | } 21 | 22 | export const menu = new Menu() 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "CommonJS", 5 | "moduleResolution": "Node", 6 | "outDir": "build", 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "skipLibCheck": true, 11 | // "isolatedModules": true, 12 | "allowJs": true, 13 | "resolveJsonModule": true, 14 | "sourceMap": true, 15 | "paths": { 16 | "ccxt": ["./node_modules/ccxt/js/ccxt.d.ts"] 17 | } 18 | }, 19 | // "include": [ 20 | // // "./src/**/*", 21 | // // "./src/config.json" 22 | // // "./src/utils/types.d.ts" 23 | // ] 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "github: ibsial | telegram: @findmeonchain", 3 | "scripts": { 4 | "start": "npx typechain --target ethers-v6 --out-dir typechain './src/utils/abi/*.json' && npx ts-node ./index.ts", 5 | "dev": "" 6 | }, 7 | "dependencies": { 8 | "@inquirer/select": "^2.3.1", 9 | "@types/cli-progress": "^3.11.5", 10 | "@types/node": "^20.11.16", 11 | "axios": "^1.6.1", 12 | "chalk": "^4.1.2", 13 | "cli-progress": "^3.12.0", 14 | "dotenv": "^16.4.1", 15 | "ethers": "^6.8.1", 16 | "https-proxy-agent": "^7.0.5", 17 | "ts-node": "^10.9.2", 18 | "typescript": "^5.3.3", 19 | "user-agents": "^1.1.111" 20 | }, 21 | "devDependencies": { 22 | "@typechain/ethers-v6": "^0.5.1", 23 | "typechain": "^8.3.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/types.ts: -------------------------------------------------------------------------------- 1 | export declare type FeeType = {maxFeePerGas: bigint; maxPriorityFeePerGas: bigint} | {gasPrice: bigint} 2 | 3 | export declare type Chain = { 4 | id: number 5 | lzId: string 6 | rpc: string[] 7 | explorer: string 8 | currency: {name: string; price?: number} 9 | tokens: { 10 | [key: string]: { 11 | name: string 12 | decimals: bigint 13 | address: string 14 | } 15 | } 16 | multicall?: string 17 | } 18 | 19 | export declare type ChainName = 20 | | 'Ethereum' 21 | | 'Arbitrum' 22 | | 'Optimism' 23 | | 'Base' 24 | | 'Linea' 25 | | 'Zksync' 26 | | 'Bsc' 27 | | 'Opbnb' 28 | | 'Polygon' 29 | | 'Avalanche' 30 | | 'Scroll' 31 | | 'Blast' 32 | | 'Mantle' 33 | | 'Gnosis' 34 | | 'Fantom' 35 | | 'Celo' 36 | | 'Core' 37 | | 'Manta' 38 | | 'Taiko' 39 | // | 'Zora' 40 | | 'Nova' 41 | 42 | export declare type NotChainName = 43 | | '!Ethereum' 44 | | '!Arbitrum' 45 | | '!Optimism' 46 | | '!Base' 47 | | '!Linea' 48 | | '!Zksync' 49 | | '!Bsc' 50 | | '!Opbnb' 51 | | '!Polygon' 52 | | '!Avalanche' 53 | | '!Scroll' 54 | | '!Blast' 55 | | '!Mantle' 56 | | '!Gnosis' 57 | | '!Fantom' 58 | | '!Celo' 59 | | '!Core' 60 | | '!Manta' 61 | | '!Taiko' 62 | // | '!Zora' 63 | | '!Nova' 64 | 65 | export declare type TokenlistResp = { 66 | name: string 67 | keywords: any 68 | logoURI: string 69 | timestamp: string 70 | version: any 71 | tokens: { 72 | chainId: number 73 | address: string 74 | name: string 75 | symbol: string 76 | decimals: number 77 | logoURI: string 78 | }[] 79 | } 80 | 81 | export declare type OdosQuoteType = { 82 | inTokens: string[] 83 | outTokens: string[] 84 | inAmounts: string[] 85 | outAmounts: string[] 86 | gasEstimate: number 87 | dataGasEstimate: number 88 | gweiPerGas: number 89 | gasEstimateValue: number 90 | inValues: number[] 91 | outValues: number[] 92 | netOutValue: number 93 | priceImpact: number 94 | percentDiff: number 95 | partnerFeePercent: number 96 | pathId: string 97 | pathViz?: any 98 | blockNumber?: number 99 | } 100 | export declare type OdosAssembleType = { 101 | deprecated?: any 102 | blockNumber: number 103 | gasEstimate: number 104 | gasEstimateValue: number 105 | inputTokens: { 106 | tokenAddress: string 107 | amount: string 108 | }[] 109 | outputTokens: { 110 | tokenAddress: string 111 | amount: string 112 | }[] 113 | netOutValue: number 114 | outValues: string[] 115 | transaction: { 116 | gas?: number 117 | gasPrice: number 118 | value: string 119 | to: string 120 | from: string 121 | data: string 122 | nonce: number 123 | chainId: number 124 | } 125 | simulation: { 126 | isSuccess: boolean 127 | amountsOut: number[] 128 | gasEstimate: number 129 | simulationError: any 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/periphery/telegram.ts: -------------------------------------------------------------------------------- 1 | import {telegramConfig} from '../../config' 2 | import axios from 'axios' 3 | import {c} from '../utils/helpers' 4 | class Telegram { 5 | needBot = true 6 | token 7 | id 8 | message = `` 9 | BaseUrl = `https://api.telegram.org/bot` 10 | constructor() { 11 | this.token = telegramConfig.telegramToken 12 | this.id = telegramConfig.telegramId 13 | this.needBot = telegramConfig.need 14 | } 15 | symbols(status: 'success' | 'fail' | 'party' | 'alien' | 'clown' | 'robot' | 'bridge' | 'scroll' | 'elixir') { 16 | switch (status) { 17 | case 'success': 18 | return `✅` 19 | case 'fail': 20 | return `❌` 21 | case 'party': 22 | return `🎉` 23 | case 'alien': 24 | return `👾` 25 | case 'clown': 26 | return `🤡` 27 | case 'robot': 28 | return `🤖` 29 | case 'bridge': 30 | return `🌉` 31 | case 'scroll': 32 | return `📜` 33 | case 'elixir': 34 | return `⚗️` 35 | } 36 | } 37 | applyFormatting(msg: string, formatting: 'normal' | 'bold' | 'italic' | 'strikethrough' | 'spoiler' | 'monospace' | 'url') { 38 | let formatedMsg 39 | switch (formatting) { 40 | case 'normal': 41 | formatedMsg = msg 42 | break 43 | case 'bold': 44 | formatedMsg = `*${msg}*` 45 | break 46 | case 'italic': 47 | formatedMsg = `_${msg}_` 48 | break 49 | case 'strikethrough': 50 | formatedMsg = `~${msg}~` 51 | break 52 | case 'spoiler': 53 | formatedMsg = `||${msg}||` 54 | break 55 | case 'monospace': 56 | formatedMsg = `\`${msg}\`` 57 | break 58 | case 'url': 59 | formatedMsg = '[link]' + `(${msg})` 60 | break 61 | } 62 | return formatedMsg 63 | } 64 | addMessage(msg: string, formatting: 'normal' | 'bold' | 'italic' | 'strikethrough' | 'spoiler' | 'monospace' | 'url' = 'normal') { 65 | if (!this.needBot) { 66 | return 67 | } 68 | this.message = this.message + this.applyFormatting(msg, formatting) + ' \n' 69 | } 70 | async sendMessage(message = '', formatting: 'normal' | 'bold' | 'italic' | 'strikethrough' | 'spoiler' | 'monospace' | 'url' = 'normal') { 71 | if (!this.needBot) { 72 | return true 73 | } 74 | try { 75 | let resp = await axios.get(this.BaseUrl + `${this.token}/sendMessage`, { 76 | data: { 77 | chat_id: this.id, 78 | parse_mode: 'markdown', 79 | text: message == '' ? this.message : this.applyFormatting(message, formatting) 80 | } 81 | }) 82 | } catch (e: any) { 83 | console.log(e?.message) 84 | console.log(c.red(`error on sending telegram paste`)) 85 | return false 86 | } 87 | if (message == '') { 88 | this.message = '' 89 | } 90 | return true 91 | } 92 | } 93 | export const telegram = new Telegram() 94 | -------------------------------------------------------------------------------- /src/periphery/utils.ts: -------------------------------------------------------------------------------- 1 | import {JsonRpcProvider, Network} from 'ethers' 2 | import {ChainName} from '../utils/types' 3 | import {chains, networkNameToCoingeckoQueryString} from '../utils/constants' 4 | import {RandomHelpers, retry} from '../utils/helpers' 5 | import axios, {AxiosInstance} from 'axios' 6 | import {HttpsProxyAgent} from 'https-proxy-agent' 7 | 8 | function getProvider(networkName: ChainName) { 9 | // TODO: add proxy & make static 10 | let provider = new JsonRpcProvider(RandomHelpers.getRandomElementFromArray(chains[networkName].rpc), Network.from(chains[networkName].id), { 11 | staticNetwork: true 12 | }) 13 | return provider 14 | } 15 | async function getNativeCoinPrice(networkName: ChainName): Promise { 16 | let price: number | undefined = await retry( 17 | async () => { 18 | if (chains[networkName].currency?.price != undefined && chains[networkName].currency?.price != 0) { 19 | return chains[networkName].currency?.price 20 | } 21 | let url = `https://min-api.cryptocompare.com/data/price?fsym=${chains[networkName].currency.name}&tsyms=USD` 22 | let resp = await axios.get(url) 23 | let body: {USD: number} = resp.data 24 | return body.USD 25 | }, 26 | {maxRetryCount: 3, retryInterval: 10, throwOnError: false} 27 | ) 28 | if (price == undefined) price = 0 29 | return price 30 | } 31 | async function getTokenPrices(networkName: ChainName, addresses: string[]): Promise<{[key: string]: number}> { 32 | let llamaNetworkName: string = networkName 33 | if (networkName == 'Zksync') { 34 | llamaNetworkName = 'era' 35 | } 36 | let prices: {[key: string]: number} | undefined = await retry( 37 | async () => { 38 | let url = `https://coins.llama.fi/prices/current/` 39 | for (let i = 0; i < addresses.length; i++) { 40 | // generally url looks like https://coins.llama.fi/prices/current/ethereum:0xaddress1,ethereum:0xaddress2 41 | url += `${llamaNetworkName.toLowerCase()}:${addresses[i]}${i + 1 == addresses.length ? '' : ','}` 42 | } 43 | let resp = await axios.get(url) 44 | let body: { 45 | // key -- ethereum:0xaddress1 46 | [key: string]: { 47 | decimals: number 48 | symbol: string 49 | price: number 50 | timestamp: number 51 | confidence: number 52 | } 53 | } = resp.data.coins 54 | let prices: {[key: string]: number} = {} 55 | for (let key of Object.keys(body)) { 56 | prices[key.split(':')[1]] = body[key].price 57 | } 58 | return prices 59 | }, 60 | {maxRetryCount: 3, retryInterval: 5, throwOnError: false} 61 | ) 62 | if (prices == undefined) prices = {} 63 | return prices 64 | } 65 | async function checkConnection(proxy?: string) { 66 | let session: AxiosInstance 67 | if (proxy) { 68 | session = axios.create({ 69 | httpAgent: new HttpsProxyAgent(`http://${proxy}`), 70 | httpsAgent: new HttpsProxyAgent(`http://${proxy}`), 71 | timeout: 5_000 72 | }) 73 | } else { 74 | session = axios.create({timeout: 5_000}) 75 | } 76 | try { 77 | let resp = session.get('https://api.odos.xyz/info/chains') 78 | return true 79 | } catch (e: any) { 80 | return false 81 | } 82 | } 83 | export {getProvider, getTokenPrices, getNativeCoinPrice, checkConnection} 84 | -------------------------------------------------------------------------------- /src/utils/tokenlists/Opbnb.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "chainId": 204, 4 | "address": "0xb01d49c26416a352fac4fbb3d555d5f2543e3247", 5 | "name": "CUBISWAP", 6 | "symbol": "CUBI", 7 | "decimals": 18, 8 | "logoURI": "https://assets.coingecko.com/coins/images/31803/thumb/IMG_20230917_222524_599.PNG?1696530618" 9 | }, 10 | { 11 | "chainId": 204, 12 | "address": "0xb26cfc2c8a5656cfe599fc344cbefec260ba9c0b", 13 | "name": "OpMoon", 14 | "symbol": "OPMOON", 15 | "decimals": 18, 16 | "logoURI": "https://assets.coingecko.com/coins/images/31664/thumb/opmoon.jpeg?1696530481" 17 | }, 18 | { 19 | "chainId": 204, 20 | "address": "0xe7798f023fc62146e8aa1b36da45fb70855a77ea", 21 | "name": "Bridged Binance Peg Ethereum opBNB ", 22 | "symbol": "ETH", 23 | "decimals": 18 24 | }, 25 | { 26 | "chainId": 204, 27 | "address": "0x9e5aac1ba1a2e6aed6b32689dfcf62a509ca96f3", 28 | "name": "Bridged Tether opBNB ", 29 | "symbol": "USDT", 30 | "decimals": 18, 31 | "logoURI": "https://assets.coingecko.com/coins/images/32613/thumb/usdt_opbnb_bridged.png?1698734789" 32 | }, 33 | { 34 | "chainId": 204, 35 | "address": "0xa41b3067ec694dbec668c389550ba8fc589e5797", 36 | "name": "Binary Swap", 37 | "symbol": "0101", 38 | "decimals": 18, 39 | "logoURI": "https://assets.coingecko.com/coins/images/31661/thumb/0101.jpeg?1696530478" 40 | }, 41 | { 42 | "chainId": 204, 43 | "address": "0x196ad5a70279fc112db4f8baf6f5022c9b1cf0a5", 44 | "name": "LuigiSwap", 45 | "symbol": "LUIGI", 46 | "decimals": 18, 47 | "logoURI": "https://assets.coingecko.com/coins/images/31802/thumb/luigi.png?1696530617" 48 | }, 49 | { 50 | "chainId": 204, 51 | "address": "0xaf9fe3b5ccdae78188b1f8b9a49da7ae9510f151", 52 | "name": "XCAD Network", 53 | "symbol": "XCAD", 54 | "decimals": 17, 55 | "logoURI": "https://assets.coingecko.com/coins/images/15857/thumb/xcad_logo.jpg?1707832192" 56 | }, 57 | { 58 | "chainId": 204, 59 | "address": "0x3e2e61f1c075881f3fb8dd568043d8c221fd5c61", 60 | "name": "Venus", 61 | "symbol": "XVS", 62 | "decimals": 18, 63 | "logoURI": "https://assets.coingecko.com/coins/images/12677/thumb/download.jpg?1696512482" 64 | }, 65 | { 66 | "chainId": 204, 67 | "address": "0xf0e2f2a84c898989c66fca1d5bce869e9bc85ddf", 68 | "name": "Miracle Play", 69 | "symbol": "MPT", 70 | "decimals": 18, 71 | "logoURI": "https://assets.coingecko.com/coins/images/32653/thumb/MPT.png?1698895300" 72 | }, 73 | { 74 | "chainId": 204, 75 | "address": "0x0c0efea731e3e9810c2b4822d5497eac107808ab", 76 | "name": "Ankr Staked BNB", 77 | "symbol": "ANKRBNB", 78 | "decimals": 18, 79 | "logoURI": "https://assets.coingecko.com/coins/images/28451/thumb/ankrBNB.png?1696527446" 80 | }, 81 | { 82 | "chainId": 204, 83 | "address": "0x9d94a7ff461e83f161c8c040e78557e31d8cba72", 84 | "name": "Thena", 85 | "symbol": "THE", 86 | "decimals": 18, 87 | "logoURI": "https://assets.coingecko.com/coins/images/28864/thumb/Symbol-Color200x200.png?1706810126" 88 | }, 89 | { 90 | "chainId": 204, 91 | "address": "0x4200000000000000000000000000000000000006", 92 | "name": "Wrapped BNB", 93 | "symbol": "WBNB", 94 | "decimals": 18, 95 | "logoURI": "https://assets.coingecko.com/coins/images/12591/thumb/binance-coin-logo.png?1696512401" 96 | }, 97 | { 98 | "chainId": 204, 99 | "address": "0xebb78043e29f4af24e6266a7d142f5a08443969e", 100 | "name": "Derp", 101 | "symbol": "DERP", 102 | "decimals": 18, 103 | "logoURI": "https://assets.coingecko.com/coins/images/33069/thumb/derpdex_%281%29.png?1700793428" 104 | }, 105 | { 106 | "chainId": 204, 107 | "address": "0x2b1d36f5b61addaf7da7ebbd11b35fd8cfb0de31", 108 | "name": "Interport Token", 109 | "symbol": "ITP", 110 | "decimals": 18, 111 | "logoURI": "https://assets.coingecko.com/coins/images/28338/thumb/ITP_Logo_200.png?1696527344" 112 | } 113 | ] -------------------------------------------------------------------------------- /src/utils/tokenlists/Nova.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "chainId": 42170, 4 | "address": "0x4b2576bc44310d6dfb4cfcf2630f25190fc60803", 5 | "name": "MoonsDust", 6 | "symbol": "MOOND", 7 | "decimals": 18, 8 | "logoURI": "https://assets.coingecko.com/coins/images/22050/thumb/moondlogo.png?1696521394" 9 | }, 10 | { 11 | "chainId": 42170, 12 | "address": "0x0057ac2d777797d31cd3f8f13bf5e927571d6ad0", 13 | "name": "r CryptoCurrency Moons", 14 | "symbol": "MOON", 15 | "decimals": 18, 16 | "logoURI": "https://assets.coingecko.com/coins/images/11222/thumb/Moons.png?1696511153" 17 | }, 18 | { 19 | "chainId": 42170, 20 | "address": "0x6dcb98f460457fe4952e12779ba852f82ecc62c1", 21 | "name": "r FortNiteBR Bricks", 22 | "symbol": "BRICK", 23 | "decimals": 18, 24 | "logoURI": "https://assets.coingecko.com/coins/images/11223/thumb/Brick.png?1696511154" 25 | }, 26 | { 27 | "chainId": 42170, 28 | "address": "0x750ba8b76187092b0d1e87e28daaf484d1b5273b", 29 | "name": "Arbitrum Bridged USDC Arbitrum Nova ", 30 | "symbol": "USDC", 31 | "decimals": 6, 32 | "logoURI": "https://assets.coingecko.com/coins/images/35259/thumb/USDC_Icon.png?1708007444" 33 | }, 34 | { 35 | "chainId": 42170, 36 | "address": "0x223fb0ceb2c6e5310264efe38151d7d083db91f1", 37 | "name": "Superpower Squad", 38 | "symbol": "SQUAD", 39 | "decimals": 18, 40 | "logoURI": "https://assets.coingecko.com/coins/images/28466/thumb/SQUAD200X200.png?1696527460" 41 | }, 42 | { 43 | "chainId": 42170, 44 | "address": "0x6ab6d61428fde76768d7b45d8bfeec19c6ef91a8", 45 | "name": "DPS Rum", 46 | "symbol": "RUM", 47 | "decimals": 18, 48 | "logoURI": "https://assets.coingecko.com/coins/images/29753/thumb/rum.png?1696528685" 49 | }, 50 | { 51 | "chainId": 42170, 52 | "address": "0x80a16016cc4a2e6a2caca8a4a498b1699ff0f844", 53 | "name": "DPS TreasureMaps", 54 | "symbol": "TMAP", 55 | "decimals": 18, 56 | "logoURI": "https://assets.coingecko.com/coins/images/29754/thumb/tmap.png?1696528686" 57 | }, 58 | { 59 | "chainId": 42170, 60 | "address": "0xb962150760f9a3bb00e3e9cf48297ee20ada4a33", 61 | "name": "Zoomer", 62 | "symbol": "ZOOMER", 63 | "decimals": 18, 64 | "logoURI": "https://assets.coingecko.com/coins/images/30894/thumb/zoooooooooomer.jpg?1696529740" 65 | }, 66 | { 67 | "chainId": 42170, 68 | "address": "0xf823c3cd3cebe0a1fa952ba88dc9eef8e0bf46ad", 69 | "name": "Arbitrum", 70 | "symbol": "ARB", 71 | "decimals": 18, 72 | "logoURI": "https://assets.coingecko.com/coins/images/16547/thumb/photo_2023-03-29_21.47.00.jpeg?1696516109" 73 | }, 74 | { 75 | "chainId": 42170, 76 | "address": "0xefaeee334f0fd1712f9a8cc375f427d9cdd40d73", 77 | "name": "DPS Doubloon", 78 | "symbol": "DBL", 79 | "decimals": 18, 80 | "logoURI": "https://assets.coingecko.com/coins/images/29755/thumb/doubloon.jpg?1696528687" 81 | }, 82 | { 83 | "chainId": 42170, 84 | "address": "0x8afe4055ebc86bd2afb3940c0095c9aca511d852", 85 | "name": "Arbius", 86 | "symbol": "AIUS", 87 | "decimals": 18, 88 | "logoURI": "https://assets.coingecko.com/coins/images/35246/thumb/arbius-200x-logo.png?1707987961" 89 | }, 90 | { 91 | "chainId": 42170, 92 | "address": "0x000000000026839b3f4181f2cf69336af6153b99", 93 | "name": "Reboot", 94 | "symbol": "GG", 95 | "decimals": 18, 96 | "logoURI": "https://assets.coingecko.com/coins/images/31228/thumb/gg_cyan_black_square.png?1703749951" 97 | }, 98 | { 99 | "chainId": 42170, 100 | "address": "0x722e8bdd2ce80a4422e880164f2079488e115365", 101 | "name": "WETH", 102 | "symbol": "WETH", 103 | "decimals": 18, 104 | "logoURI": "https://assets.coingecko.com/coins/images/2518/thumb/weth.png?1696503332" 105 | }, 106 | { 107 | "chainId": 42170, 108 | "address": "0xda10009cbd5d07dd0cecc66161fc93d7c9000da1", 109 | "name": "Dai", 110 | "symbol": "DAI", 111 | "decimals": 18, 112 | "logoURI": "https://assets.coingecko.com/coins/images/9956/thumb/Badge_Dai.png?1696509996" 113 | } 114 | ] -------------------------------------------------------------------------------- /config.example.ts: -------------------------------------------------------------------------------- 1 | import {ChainName, NotChainName} from './src/utils/types' 2 | 3 | export const DEV = false 4 | 5 | export const maxRetries = 2 6 | export const shuffleWallets = true 7 | 8 | export const goodGwei = 5 9 | export const sleepBetweenActions = {from: 5, to: 60} // secs 10 | export const sleepBetweenAccs = {from: 5 * 60, to: 15 * 60} // secs 11 | 12 | /** 13 | * THIS MODULE SWAPS ALL SHITCOINS INTO NATIVE COIN 14 | * First ODOS aggregator is used 15 | * Then SushiSwap v2 is used (you can disable it by setting 'trySushi = false') 16 | */ 17 | export class GarbageCollectorConfig { 18 | /*********************************************************/ 19 | /*********************** CHAIN LIST ********************** 20 | Ethereum | Arbitrum | Optimism | Base | Linea | 21 | Zksync | Bsc | Opbnb | Polygon | Avalanche | 22 | Scroll | Blast | Mantle | Gnosis | Fantom | 23 | Nova | Taiko | Core | Manta | Celo | 24 | **********************************************************/ 25 | /* 26 | If you want to run *ALL* chains except for *SOME* of them 27 | chainsToExclude could look like: ['Ethereum', 'Celo', 'Gnosis', 'Taiko', 'Manta', 'Opbnb', 'Nova'] 28 | 29 | If you want to run *ONLY ONE* specific chain 30 | chainsToExclude could look like: ['!Zksync'] -- this will run Zksync only 31 | 32 | Swaps not supported: Opbnb, Manta, Taiko 33 | */ 34 | chainsToExclude: (ChainName | NotChainName)[] = [] 35 | tokensToIgnore: string[] = [] // token address to ignore 36 | trySushi = true // true | false 37 | } 38 | 39 | export class NativeSenderConfig { 40 | /*********************************************************/ 41 | /*********************** CHAIN LIST ********************** 42 | Ethereum | Arbitrum | Optimism | Base | Linea | 43 | Zksync | Bsc | Opbnb | Polygon | Avalanche | 44 | Scroll | Blast | Mantle | Gnosis | Fantom | 45 | Nova | Taiko | Core | Manta | Celo | 46 | **********************************************************/ 47 | /** 48 | Since a lot of chains are parsed, we exclude some of them instead of "turning on" 49 | If you want to run *ALL* chains except for *SOME* of them 50 | chainsToExclude could look like: ['Ethereum', 'Celo', 'Gnosis', 'Taiko', 'Manta', 'Opbnb', 'Nova'] 51 | 52 | If you want to run *ONLY ONE* specific chain 53 | chainsToExclude could look like: ['!Zksync'] -- this will run Zksync only 54 | 55 | Note: Be careful with 'Gnosis', 'Taiko', 'Manta', 'Opbnb', 'Nova' -- not many exchanges accept their natives 56 | */ 57 | chainsToExclude: (ChainName | NotChainName)[] = ['Ethereum'] 58 | /** 59 | * You can set value 60 | * as NUMBER: {from: '0.1', to: '0.11'} 61 | * as PERCENTAGE: {from: '80%', to: '100%'} (you can also set both 100%) 62 | */ 63 | values: {from: string; to: string} = {from: '90%', to: '100%'} 64 | /** 65 | * If set to *true*, fee will be deducted before transfer: *(Value - Fee)* will be sent 66 | * If set to *false*, fee wont be deducted before transfer: *(Value)* will be sent 67 | */ 68 | deductFee: boolean = true 69 | } 70 | 71 | export class RelayBridgeConfig { 72 | /*********************************************************/ 73 | /*********************** CHAIN LIST ********************** 74 | Ethereum | Arbitrum | Optimism | Base | Linea | 75 | Zksync | | | | | 76 | Scroll | Blast | | | | 77 | | | | Taiko | | 78 | **********************************************************/ 79 | /** 80 | This module bridges ONLY ETH between ETH-chains 81 | 82 | You can set multiple *from* chains and one *to* chain 83 | 84 | */ 85 | fromNetworks: ChainName[] = ['Zksync'] 86 | 87 | toNetwork: ChainName = 'Linea' 88 | /** 89 | * You can set value 90 | * as NUMBER: {from: '0.1', to: '0.11'} 91 | * as PERCENTAGE: {from: '80%', to: '100%'} (you can also set both 100%) 92 | */ 93 | values: {from: string; to: string} = {from: '100%', to: '100%'} 94 | /** 95 | * If set to *true*, fee will be deducted before bridge: *(Value - Fee)* will be sent 96 | * If set to *false*, fee wont be deducted before bridge: *(Value)* will be sent 97 | */ 98 | deductFee: boolean = true 99 | } 100 | -------------------------------------------------------------------------------- /src/core/nativeSender.ts: -------------------------------------------------------------------------------- 1 | import {formatEther, parseEther, Wallet} from 'ethers' 2 | import {NativeSenderConfig, sleepBetweenActions} from '../../config' 3 | import {ChainName, NotChainName} from '../utils/types' 4 | import {bigintToPrettyStr, c, defaultSleep, RandomHelpers} from '../utils/helpers' 5 | import {chains} from '../utils/constants' 6 | import {estimateTx, getBalance, getGwei, transfer} from '../periphery/web3Client' 7 | import {getProvider} from '../periphery/utils' 8 | 9 | class NativeSender extends NativeSenderConfig { 10 | signer: Wallet 11 | receiver: string 12 | constructor(signer: Wallet, receiver: string) { 13 | super() 14 | this.signer = signer 15 | this.receiver = receiver 16 | if (receiver == undefined || receiver.match(/(0x)?[a-fA-F0-9]{40}/) == null || receiver.length > 42) { 17 | console.log(c.red(`${receiver} is not correct! You could've lost it ALL!`)) 18 | throw Error(`${receiver} is not correct! You could've lost it ALL!`) 19 | } 20 | this.chainsToExclude = this.chainsToExclude.map((elem) => elem.toLowerCase().trim()) as (ChainName | NotChainName)[] 21 | } 22 | 23 | async sendNative(): Promise { 24 | let networks = RandomHelpers.shuffleArray(Object.keys(chains)) 25 | for (let i = 0; i < networks.length; i++) { 26 | let networkName = networks[i] as ChainName 27 | if (this.chainsToExclude.length > 0 && this.chainsToExclude[0].includes('!')) { 28 | if (!networkName.toLowerCase().includes(this.chainsToExclude[0].split('!')[1])) { 29 | continue 30 | } 31 | } 32 | if (this.chainsToExclude.includes(networkName.toLowerCase() as ChainName | NotChainName)) { 33 | continue 34 | } 35 | 36 | try { 37 | let tx = { 38 | from: this.signer, 39 | to: this.receiver, 40 | value: 1n 41 | } 42 | let value = await this.getSendValue(networkName) 43 | if (this.deductFee) { 44 | let gasLimit = await estimateTx(this.signer.connect(getProvider(networkName)), tx, 1.1) 45 | let {gasPrice} = await getGwei(getProvider(networkName), 1.1) 46 | let txCost = gasLimit * gasPrice 47 | value = value - txCost 48 | if (value < 0n) { 49 | console.log(c.red(`Can't send negative value`)) 50 | return false 51 | } 52 | } 53 | let sendHash = await transfer(this.signer.connect(getProvider(networkName)), this.receiver, value, undefined, {price: 1, limit: 1}) 54 | console.log( 55 | c.green( 56 | `${bigintToPrettyStr(value, 18n, 4)} ${chains[networkName].currency.name} sent to ${this.receiver} ${ 57 | chains[networkName].explorer + sendHash 58 | }` 59 | ) 60 | ) 61 | await defaultSleep(RandomHelpers.getRandomNumber(sleepBetweenActions)) 62 | } catch (e: any) { 63 | console.log(e?.message) 64 | console.log(c.red(`Could not send ${chains[networkName].currency.name} to ${this.receiver}`)) 65 | } 66 | } 67 | return true 68 | } 69 | async getSendValue(networkName: ChainName): Promise { 70 | if (parseFloat(this.values.from) < 0 || parseFloat(this.values.to) < 0) { 71 | console.log(c.red(`Can't pass negative numbers to NativeSender`)) 72 | throw Error(`Can't pass negative numbers to NativeSender`) 73 | } 74 | if (this.values.from.includes('%') && this.values.to.includes('%')) { 75 | let precision = 1000 76 | let balance = await getBalance(getProvider(networkName), this.signer.address) 77 | let randomPortion = BigInt( 78 | (RandomHelpers.getRandomNumber({from: parseFloat(this.values.from), to: parseFloat(this.values.to)}, 3) * precision).toFixed() 79 | ) 80 | let value = (balance * randomPortion) / (100n * BigInt(precision)) 81 | return value 82 | } else if (!this.values.from.includes('%') && !this.values.to.includes('%')) { 83 | let value = parseEther(RandomHelpers.getRandomNumber({from: parseFloat(this.values.from), to: parseFloat(this.values.to)}).toFixed()) 84 | return value 85 | } else { 86 | console.log(c.red(`Your "values" in "NativeSenderConfig" are wrong. Should be *number* or *percentage*`)) 87 | throw Error(`Your "values" in "NativeSenderConfig" are wrong. Should be *number* or *percentage*`) 88 | } 89 | } 90 | } 91 | 92 | export {NativeSender} 93 | -------------------------------------------------------------------------------- /src/utils/abi/WETH.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "name", 6 | "outputs": [{"name": "", "type": "string"}], 7 | "payable": false, 8 | "stateMutability": "view", 9 | "type": "function" 10 | }, 11 | { 12 | "constant": false, 13 | "inputs": [ 14 | {"name": "guy", "type": "address"}, 15 | {"name": "wad", "type": "uint256"} 16 | ], 17 | "name": "approve", 18 | "outputs": [{"name": "", "type": "bool"}], 19 | "payable": false, 20 | "stateMutability": "nonpayable", 21 | "type": "function" 22 | }, 23 | { 24 | "constant": true, 25 | "inputs": [], 26 | "name": "totalSupply", 27 | "outputs": [{"name": "", "type": "uint256"}], 28 | "payable": false, 29 | "stateMutability": "view", 30 | "type": "function" 31 | }, 32 | { 33 | "constant": false, 34 | "inputs": [ 35 | {"name": "src", "type": "address"}, 36 | {"name": "dst", "type": "address"}, 37 | {"name": "wad", "type": "uint256"} 38 | ], 39 | "name": "transferFrom", 40 | "outputs": [{"name": "", "type": "bool"}], 41 | "payable": false, 42 | "stateMutability": "nonpayable", 43 | "type": "function" 44 | }, 45 | { 46 | "constant": false, 47 | "inputs": [{"name": "wad", "type": "uint256"}], 48 | "name": "withdraw", 49 | "outputs": [], 50 | "payable": false, 51 | "stateMutability": "nonpayable", 52 | "type": "function" 53 | }, 54 | { 55 | "constant": true, 56 | "inputs": [], 57 | "name": "decimals", 58 | "outputs": [{"name": "", "type": "uint8"}], 59 | "payable": false, 60 | "stateMutability": "view", 61 | "type": "function" 62 | }, 63 | { 64 | "constant": true, 65 | "inputs": [{"name": "", "type": "address"}], 66 | "name": "balanceOf", 67 | "outputs": [{"name": "", "type": "uint256"}], 68 | "payable": false, 69 | "stateMutability": "view", 70 | "type": "function" 71 | }, 72 | { 73 | "constant": true, 74 | "inputs": [], 75 | "name": "symbol", 76 | "outputs": [{"name": "", "type": "string"}], 77 | "payable": false, 78 | "stateMutability": "view", 79 | "type": "function" 80 | }, 81 | { 82 | "constant": false, 83 | "inputs": [ 84 | {"name": "dst", "type": "address"}, 85 | {"name": "wad", "type": "uint256"} 86 | ], 87 | "name": "transfer", 88 | "outputs": [{"name": "", "type": "bool"}], 89 | "payable": false, 90 | "stateMutability": "nonpayable", 91 | "type": "function" 92 | }, 93 | {"constant": false, "inputs": [], "name": "deposit", "outputs": [], "payable": true, "stateMutability": "payable", "type": "function"}, 94 | { 95 | "constant": true, 96 | "inputs": [ 97 | {"name": "", "type": "address"}, 98 | {"name": "", "type": "address"} 99 | ], 100 | "name": "allowance", 101 | "outputs": [{"name": "", "type": "uint256"}], 102 | "payable": false, 103 | "stateMutability": "view", 104 | "type": "function" 105 | }, 106 | {"payable": true, "stateMutability": "payable", "type": "fallback"}, 107 | { 108 | "anonymous": false, 109 | "inputs": [ 110 | {"indexed": true, "name": "src", "type": "address"}, 111 | {"indexed": true, "name": "guy", "type": "address"}, 112 | {"indexed": false, "name": "wad", "type": "uint256"} 113 | ], 114 | "name": "Approval", 115 | "type": "event" 116 | }, 117 | { 118 | "anonymous": false, 119 | "inputs": [ 120 | {"indexed": true, "name": "src", "type": "address"}, 121 | {"indexed": true, "name": "dst", "type": "address"}, 122 | {"indexed": false, "name": "wad", "type": "uint256"} 123 | ], 124 | "name": "Transfer", 125 | "type": "event" 126 | }, 127 | { 128 | "anonymous": false, 129 | "inputs": [ 130 | {"indexed": true, "name": "dst", "type": "address"}, 131 | {"indexed": false, "name": "wad", "type": "uint256"} 132 | ], 133 | "name": "Deposit", 134 | "type": "event" 135 | }, 136 | { 137 | "anonymous": false, 138 | "inputs": [ 139 | {"indexed": true, "name": "src", "type": "address"}, 140 | {"indexed": false, "name": "wad", "type": "uint256"} 141 | ], 142 | "name": "Withdrawal", 143 | "type": "event" 144 | } 145 | ] 146 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import {Wallet} from 'ethers' 2 | import {menu} from './src/periphery/menu' 3 | import {getProvider} from './src/periphery/utils' 4 | import {scenarios} from './src/utils/constants' 5 | import {c, defaultSleep, importAndValidatePrivateData, importProxies, RandomHelpers, sleep} from './src/utils/helpers' 6 | import {GarbageCollector} from './src/core/garbageCollector' 7 | import {goodGwei, shuffleWallets, sleepBetweenAccs, sleepBetweenActions} from './config' 8 | import {NativeSender} from './src/core/nativeSender' 9 | import {waitGwei} from './src/periphery/web3Client' 10 | import {RelayBridge} from './src/periphery/relayBridge' 11 | 12 | async function main() { 13 | let scenario = await menu.chooseTask() 14 | let proxies = await importProxies('./proxies.txt') 15 | let keysAndAddresses: {key: string; address: string}[] 16 | let provider = getProvider('Ethereum') 17 | let initialSigner: Wallet 18 | let garbageCollector: GarbageCollector 19 | let nativeSender: NativeSender 20 | 21 | switch (scenario) { 22 | case 'Balance cheker': 23 | keysAndAddresses = await importAndValidatePrivateData('./privates.txt', false) 24 | if (shuffleWallets) { 25 | keysAndAddresses = RandomHelpers.shuffleArray(keysAndAddresses) 26 | } 27 | initialSigner = new Wallet(keysAndAddresses[0].key) 28 | garbageCollector = new GarbageCollector(initialSigner, proxies) 29 | for (let i = 0; i < keysAndAddresses.length; i++) { 30 | let signer = new Wallet(keysAndAddresses[i].key, provider) 31 | console.log(c.cyan(`#${i + 1}/${keysAndAddresses.length} ${signer.address}`)) 32 | garbageCollector.connect(signer) 33 | await garbageCollector.getNonZeroTokens() 34 | } 35 | break 36 | case 'Garbage collector': 37 | keysAndAddresses = await importAndValidatePrivateData('./privates.txt', false) 38 | if (shuffleWallets) { 39 | keysAndAddresses = RandomHelpers.shuffleArray(keysAndAddresses) 40 | } 41 | initialSigner = new Wallet(keysAndAddresses[0].key) 42 | garbageCollector = new GarbageCollector(initialSigner, proxies) 43 | for (let i = 0; i < keysAndAddresses.length; i++) { 44 | let signer = new Wallet(keysAndAddresses[i].key, provider) 45 | console.log(c.cyan(`#${i + 1}/${keysAndAddresses.length} ${signer.address}`)) 46 | garbageCollector.connect(signer) 47 | await waitGwei(goodGwei) 48 | await garbageCollector.getNonZeroTokensAndSwap() 49 | await sleep(RandomHelpers.getRandomNumber(sleepBetweenAccs)) 50 | } 51 | break 52 | case 'Garbage collector & native sender': 53 | keysAndAddresses = await importAndValidatePrivateData('./privates.txt', true) 54 | if (shuffleWallets) { 55 | keysAndAddresses = RandomHelpers.shuffleArray(keysAndAddresses) 56 | } 57 | initialSigner = new Wallet(keysAndAddresses[0].key) 58 | garbageCollector = new GarbageCollector(initialSigner, proxies) 59 | for (let i = 0; i < keysAndAddresses.length; i++) { 60 | let signer = new Wallet(keysAndAddresses[i].key, provider) 61 | console.log(c.cyan(`#${i + 1}/${keysAndAddresses.length} ${signer.address}`)) 62 | garbageCollector.connect(signer) 63 | nativeSender = new NativeSender(signer, keysAndAddresses[i].address) 64 | await waitGwei(goodGwei) 65 | await garbageCollector.getNonZeroTokensAndSwap() 66 | await defaultSleep(RandomHelpers.getRandomNumber(sleepBetweenActions), true) 67 | await waitGwei(goodGwei) 68 | await nativeSender.sendNative() 69 | await sleep(RandomHelpers.getRandomNumber(sleepBetweenAccs)) 70 | } 71 | break 72 | case 'Relay bridge': 73 | keysAndAddresses = await importAndValidatePrivateData('./privates.txt', false) 74 | if (shuffleWallets) { 75 | keysAndAddresses = RandomHelpers.shuffleArray(keysAndAddresses) 76 | } 77 | for (let i = 0; i < keysAndAddresses.length; i++) { 78 | let signer = new Wallet(keysAndAddresses[i].key, provider) 79 | console.log(c.cyan(`#${i + 1}/${keysAndAddresses.length} ${signer.address}`)) 80 | await waitGwei(goodGwei) 81 | const relay = new RelayBridge(signer) 82 | await relay.executeRelayBridge(signer) 83 | await sleep(RandomHelpers.getRandomNumber(sleepBetweenAccs)) 84 | } 85 | break 86 | default: 87 | console.log(`I could not understand you... \nAvailable scenarios are: ${c.magenta(scenarios.map((elem) => elem.name).join(' | '))}`) 88 | } 89 | } 90 | 91 | main() 92 | -------------------------------------------------------------------------------- /src/utils/abi/ERC20.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "constant": true, 4 | "inputs": [], 5 | "name": "name", 6 | "outputs": [ 7 | { 8 | "name": "", 9 | "type": "string" 10 | } 11 | ], 12 | "payable": false, 13 | "stateMutability": "view", 14 | "type": "function" 15 | }, 16 | { 17 | "constant": false, 18 | "inputs": [ 19 | { 20 | "name": "_spender", 21 | "type": "address" 22 | }, 23 | { 24 | "name": "_value", 25 | "type": "uint256" 26 | } 27 | ], 28 | "name": "approve", 29 | "outputs": [ 30 | { 31 | "name": "", 32 | "type": "bool" 33 | } 34 | ], 35 | "payable": false, 36 | "stateMutability": "nonpayable", 37 | "type": "function" 38 | }, 39 | { 40 | "constant": true, 41 | "inputs": [], 42 | "name": "totalSupply", 43 | "outputs": [ 44 | { 45 | "name": "", 46 | "type": "uint256" 47 | } 48 | ], 49 | "payable": false, 50 | "stateMutability": "view", 51 | "type": "function" 52 | }, 53 | { 54 | "constant": false, 55 | "inputs": [ 56 | { 57 | "name": "_from", 58 | "type": "address" 59 | }, 60 | { 61 | "name": "_to", 62 | "type": "address" 63 | }, 64 | { 65 | "name": "_value", 66 | "type": "uint256" 67 | } 68 | ], 69 | "name": "transferFrom", 70 | "outputs": [ 71 | { 72 | "name": "", 73 | "type": "bool" 74 | } 75 | ], 76 | "payable": false, 77 | "stateMutability": "nonpayable", 78 | "type": "function" 79 | }, 80 | { 81 | "constant": true, 82 | "inputs": [], 83 | "name": "decimals", 84 | "outputs": [ 85 | { 86 | "name": "", 87 | "type": "uint8" 88 | } 89 | ], 90 | "payable": false, 91 | "stateMutability": "view", 92 | "type": "function" 93 | }, 94 | { 95 | "constant": true, 96 | "inputs": [ 97 | { 98 | "name": "_owner", 99 | "type": "address" 100 | } 101 | ], 102 | "name": "balanceOf", 103 | "outputs": [ 104 | { 105 | "name": "balance", 106 | "type": "uint256" 107 | } 108 | ], 109 | "payable": false, 110 | "stateMutability": "view", 111 | "type": "function" 112 | }, 113 | { 114 | "constant": true, 115 | "inputs": [], 116 | "name": "symbol", 117 | "outputs": [ 118 | { 119 | "name": "", 120 | "type": "string" 121 | } 122 | ], 123 | "payable": false, 124 | "stateMutability": "view", 125 | "type": "function" 126 | }, 127 | { 128 | "constant": false, 129 | "inputs": [ 130 | { 131 | "name": "_to", 132 | "type": "address" 133 | }, 134 | { 135 | "name": "_value", 136 | "type": "uint256" 137 | } 138 | ], 139 | "name": "transfer", 140 | "outputs": [ 141 | { 142 | "name": "", 143 | "type": "bool" 144 | } 145 | ], 146 | "payable": false, 147 | "stateMutability": "nonpayable", 148 | "type": "function" 149 | }, 150 | { 151 | "constant": true, 152 | "inputs": [ 153 | { 154 | "name": "_owner", 155 | "type": "address" 156 | }, 157 | { 158 | "name": "_spender", 159 | "type": "address" 160 | } 161 | ], 162 | "name": "allowance", 163 | "outputs": [ 164 | { 165 | "name": "", 166 | "type": "uint256" 167 | } 168 | ], 169 | "payable": false, 170 | "stateMutability": "view", 171 | "type": "function" 172 | }, 173 | { 174 | "payable": true, 175 | "stateMutability": "payable", 176 | "type": "fallback" 177 | }, 178 | { 179 | "anonymous": false, 180 | "inputs": [ 181 | { 182 | "indexed": true, 183 | "name": "owner", 184 | "type": "address" 185 | }, 186 | { 187 | "indexed": true, 188 | "name": "spender", 189 | "type": "address" 190 | }, 191 | { 192 | "indexed": false, 193 | "name": "value", 194 | "type": "uint256" 195 | } 196 | ], 197 | "name": "Approval", 198 | "type": "event" 199 | }, 200 | { 201 | "anonymous": false, 202 | "inputs": [ 203 | { 204 | "indexed": true, 205 | "name": "from", 206 | "type": "address" 207 | }, 208 | { 209 | "indexed": true, 210 | "name": "to", 211 | "type": "address" 212 | }, 213 | { 214 | "indexed": false, 215 | "name": "value", 216 | "type": "uint256" 217 | } 218 | ], 219 | "name": "Transfer", 220 | "type": "event" 221 | } 222 | ] 223 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GARBAGE COLLECTOR 2 | 3 | ## Основные фичи 4 | 5 | - Работа в **_20_** EVM сетях 6 | - Поддержка более **_10 000_** щитков 7 | - Безумный чекер балансов (Как нативки, так и токенов) 8 | - Сборка мусора -- свапы через Odos и Sushiswap V2 9 | - Автоматическое определение целесообразности свапа (Если транзакция дорогая, свап не произойдет) 10 | - Отправка нативки на нужный адрес 11 | - Бридж ETH под ноль через Relay Bridge 12 | 13 | --- 14 | 15 | ## Описание модулей и их нюансов 16 | 17 | ### ***Balance сhecker*** 18 | 19 | #### Что умеет: 20 | 21 | - Подтягивать токены для 20 EVM сетей и проверять балансы 22 | - Выводить в консоль баланс нативки и ненулевые балансы токенов 23 | 24 | #### Нюансы настройки: 25 | 26 | Настройка происходит в `GarbageCollectorConfig`. Можно выбрать _какие сети нужно исключить из перебора_ 27 | 28 | > [!TIP] 29 | > `chainsToExclude` 30 | > Помимо простого исключения сетей формата `['chain1', 'chain2', ... ]` можно проверить балансы только одной. Для этого нужно вписать название нужной сети с восклицательным знаком: `['!chain1']`. В таком случае проверится баланс только в `chain1`. 31 | 32 | #### Пример настройки: 33 | 34 | `chainsToExclude = []` (_Остальные переменные не влияют на чекер_) 35 | 36 | ### ***Garbage collector*** 37 | 38 | #### Что умеет: 39 | 40 | - Подтягивать токены для 20 EVM сетей, проверять балансы кошелька и обменивать токены в нативку сети через Odos и Sushiswap 41 | 42 | #### Нюансы настройки: 43 | 44 | Настройка происходит в `GarbageCollectorConfig`. Можно выбрать: 45 | 46 | - какие сети нужно исключить из перебора 47 | - какие токены нужно игнорировать 48 | - проводить ли обмен через Sushiswap V2, если невозможно обменять на Odos 49 | 50 | > [!TIP] 51 | > `chainsToExclude` 52 | > Помимо простого исключения сетей формата `['chain1', 'chain2', ... ]` можно собрать мусор только в одной. Для этого нужно вписать название нужной сети с восклицательным знаком: `['!chain1']`. В таком случае проверится баланс и сделаются свапы только в `chain1`. 53 | 54 | > [!IMPORTANT] 55 | > `tokensToIgnore` 56 | > В этот массив нужно добавить адреса тех токенов, которые вы бы не хотели обменивать на нативные монеты. 57 | 58 | > [!WARNING] 59 | > `trySushi` 60 | > Если Odos не способен выполнить обмен, он может быть выполнен на SushiSwap v2. Будьте осторожны, поскольку пулы на SushiSwap могут быть с низкой ликвидностью. Не пытайтесь обменивать там хоть сколько-нибудь дорогие токены. 61 | 62 | #### Пример настройки: 63 | 64 | Например, собирая мусор только в сети Zksync я не хочу, чтобы скрипт продал $ZK. Тогда настройка будет выглядеть так: 65 | 66 | `chainsToExclude = ['!Zksync']` 67 | `tokensToIgnore = ['0x5a7d6b2f92c77fad6ccabd7ee0624e64907eaf3e']` 68 | 69 | ### ***Garbage collector & native sender*** 70 | 71 | #### Что умеет: 72 | 73 | - Подтягивать токены для 20 EVM сетей, проверять балансы кошелька и обменивать токены в нативку сети через Odos и Sushiswap, а затем отправлять нативный токен по указанному адресу. 74 | 75 | > [!CAUTION] 76 | > Для работы этого модуля вам необходимо добавить адреса в файл privates.txt по формату: `private_key,receiver` 77 | 78 | #### Нюансы настройки: 79 | 80 | Настройка происходит в `NativeSenderConfig`. Можно выбрать: 81 | 82 | - какие сети нужно исключить 83 | - Сумму отправки 84 | - Вычитать ли из отправляемой суммы комиссию за транзакцию 85 | 86 | > [!TIP] 87 | > `chainsToExclude` 88 | > Помимо простого исключения сетей формата `['chain1', 'chain2', ... ]` можно собрать мусор только в одной. Для этого нужно вписать название нужной сети с восклицательным знаком: `['!chain1']`. В таком случае проверится баланс и сделаются свапы только в `chain1`. 89 | 90 | > [!IMPORTANT] 91 | > `values` 92 | > Это значение можно задавать как в числах, так и в процентах. 93 | > Значение в числах: `{from: '0.1', to: '0.2'}` - Отправим от 0.1 до 0.2 нативной монеты 94 | > Значение в процентах: `{from: '90%', to: '100%'}` - Отправим от 90% до 100% всего баланса нативной монеты 95 | 96 | > [!TIP] 97 | > `deductFee` 98 | > Вычитаем ли из отправляемой суммы комиссию транзакции? Для отправки всего баланса (`{from: '100%', to: '100%'}`) нужно поставить `true` 99 | 100 | #### Пример настройки: 101 | 102 | Например, собирая мусор только в сети Zksync я хочу, чтобы скрипт отправил весь баланс на биржу. Тогда настройка будет выглядеть так: 103 | 104 | `chainsToExclude = ['!Zksync']` 105 | `values = {from: '100%', to: '100%'}` 106 | `deductFee = true` 107 | 108 | ### ***Relay bridge*** 109 | 110 | #### Что умеет: 111 | 112 | - Отправлять ETH по сетям, где ETH является нативной монетой (Убедитесь в доступности маршрута [на сайте моста](https://relay.link/bridge/)) 113 | 114 | #### Нюансы настройки: 115 | 116 | Настройка происходит в `RelayBridgeConfig`. Можно выбрать: 117 | 118 | - Из каких сетей отправлять ETH 119 | - В какую сеть отправлять ETH 120 | - Отправляемую сумму 121 | - Вычитать ли из отправляемой суммы комиссию за транзакцию 122 | 123 | > [!TIP] 124 | > `fromNetworks` 125 | > Нужно перечислить сети, из которых отправляем эфир. 126 | 127 | > [!TIP] 128 | > `toNetwork` 129 | > Указывается одна сеть, куда отправляется эфир. 130 | 131 | > [!IMPORTANT] 132 | > `values` 133 | > Это значение можно задавать как в числах, так и в процентах. 134 | > Значение в числах: `{from: '0.1', to: '0.2'}` - Отправим от 0.1 до 0.2 нативной монеты 135 | > Значение в процентах: `{from: '90%', to: '100%'}` - Отправим от 90% до 100% всего баланса нативной монеты 136 | 137 | > [!TIP] 138 | > `deductFee` 139 | > Вычитаем ли из отправляемой суммы комиссию транзакции? Для отправки всего баланса (`{from: '100%', to: '100%'}`) нужно поставить `true` 140 | 141 | #### Пример настройки: 142 | 143 | При выводе ETH под ноль из Zksync и Arbitrum nova в Linea настройки будут такими: 144 | 145 | `fromNetworks = ['Zksync', 'Nova']` 146 | `toNetwork = ['Linea']` 147 | `values = {from: '100%', to: '100%'}` 148 | `deductFee = true` 149 | 150 | --- 151 | 152 | ## Запуск скрипта 153 | 154 | 1. Переименовать 155 | - `proxies.example.txt` --> `proxies.txt` 156 | - `privates.example.txt` --> `privates.txt` 157 | - `config.example.ts` --> `config.ts` 158 | 2. Установить зависимости 159 | - `npm i` 160 | 3. Запустить скрипт 161 | - `npm run start` 162 | 4. Выбрать необходимый сценарий нажав Enter 163 | 164 | 165 | # Donos and contact 166 | 167 | > telegram: **https://t.me/findmeonchain** 168 | donos: **[0x00000c7c61c5d7fbbf217ab9fc64f6016390d4ba](https://debank.com/profile/0x00000c7c61c5d7fbbf217ab9fc64f6016390d4ba)** 169 | 170 | 171 | 172 | ## Disclaimer/Предупреждение 173 | Я не несу какой-либо ответственности за написанный код. **Вы можете потерять деньги при его использовании.** Запускайте только на свой страх и риск и если действительно знаете, что делаете. -------------------------------------------------------------------------------- /src/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | import c from 'chalk' 2 | import {DEV, maxRetries} from '../../config' 3 | import {SingleBar, Presets} from 'cli-progress' 4 | import * as fs from 'fs' 5 | import * as readline from 'readline' 6 | import {once} from 'events' 7 | import {Wallet, formatUnits, isAddress} from 'ethers' 8 | 9 | const log = console.log 10 | 11 | async function sleep(sec: number) { 12 | if (sec > 1) { 13 | sec = Math.round(sec) 14 | } 15 | let bar = new SingleBar( 16 | { 17 | format: `${c.bgGrey('{bar}')} | ${c.blue('{value}/{total} sec')}`, 18 | barsize: 80 19 | }, 20 | Presets.shades_grey 21 | ) 22 | bar.start(sec, 0) 23 | for (let i = 0; i < sec; i++) { 24 | await new Promise((resolve) => setTimeout(resolve, 1 * 1000)) 25 | bar.increment() 26 | } 27 | bar.stop() 28 | process.stdout.clearLine(0) 29 | } 30 | async function defaultSleep(sec: number, needProgress = true) { 31 | if (needProgress) { 32 | let newpaste = ['-', `\\`, `|`, `/`] 33 | for (let i = 0; i < sec * 2; i++) { 34 | process.stdout.clearLine(0) // clear current text 35 | process.stdout.cursorTo(0) 36 | process.stdout.write(`${newpaste[i % 4]}`) 37 | await new Promise((resolve) => setTimeout(resolve, 500)) 38 | } 39 | process.stdout.clearLine(0) // clear current text 40 | process.stdout.cursorTo(0) 41 | return 42 | } 43 | return await new Promise((resolve) => setTimeout(resolve, sec * 1000)) 44 | } 45 | async function delayedPrint(paste: string, delay = 0.05) { 46 | for (let i = 0; i < paste.length; i++) { 47 | process.stdout.write(paste[i]) 48 | await defaultSleep(delay, false) 49 | } 50 | } 51 | const retry = async ( 52 | fn: any, 53 | {maxRetryCount = maxRetries ?? 5, retryInterval = 10, backoff = 1, needLog = true, throwOnError = true}, 54 | ...args: any 55 | ): Promise => { 56 | retryInterval = retryInterval * backoff 57 | let i = 1 58 | let lastError 59 | while (i <= maxRetryCount) { 60 | try { 61 | return await fn(...args) 62 | } catch (e: any) { 63 | lastError = e 64 | if (DEV) { 65 | console.log(e) 66 | } 67 | if (needLog) { 68 | console.log(e?.message) 69 | console.log(`catched error, retrying... [${i}]`) 70 | } 71 | // console.log(c.magenta('if you see this, please contact the author and tell about error above')) 72 | await defaultSleep(retryInterval, false) 73 | } 74 | i++ 75 | } 76 | if (!throwOnError) return 77 | throw lastError ?? new Error(`Could not execute ${fn.name} in ${maxRetryCount} tries`) 78 | } 79 | 80 | class Random { 81 | getRandomNumber(tier: {from: number; to: number}, precision = 6): number { 82 | return Number((Math.random() * (tier.to - tier.from) + tier.from).toFixed(precision)) 83 | } 84 | getRandomBigInt(tier: {from: bigint; to: bigint}) { 85 | const delta = tier.to - tier.from 86 | const randValue = BigInt((Math.random() * 1000).toFixed(0)) 87 | return tier.from + (randValue * delta) / 1000n 88 | } 89 | getRandomElementFromArray(arr: T[]): T { 90 | return arr[Math.floor(Math.random() * arr.length)] 91 | } 92 | shuffleArray(oldArray: T[]): T[] { 93 | let array = oldArray.slice() 94 | let buf 95 | for (let i = 0; i < array.length; i++) { 96 | buf = array[i] 97 | let randNum = Math.floor(Math.random() * array.length) 98 | array[i] = array[randNum] 99 | array[randNum] = buf 100 | } 101 | return array 102 | } 103 | getRandomStructKey(struct: {[key: string]: any}): string { 104 | var keys = Object.keys(struct) 105 | return keys[(keys.length * Math.random()) << 0] 106 | } 107 | } 108 | function bigintToPrettyStr(value: bigint, decimals = 18n, remainder = 4) { 109 | return parseFloat(formatUnits(value, decimals)).toFixed(remainder) 110 | } 111 | async function importPrivateData(path: string) { 112 | let data: string[][] = [] 113 | let instream = fs.createReadStream(path) 114 | let rl = readline.createInterface(instream) 115 | rl.on('line', (line) => { 116 | data.push(line.trim().split(',')) 117 | }) 118 | await once(rl, 'close') 119 | return data 120 | } 121 | async function importAndValidatePrivateData(path: string, validateAddr: boolean) { 122 | let intialData = await importPrivateData(path) 123 | // let privates: string[] = [] 124 | // let addresses: string[] = [] 125 | let keysAndAddresses: {key: string; address: string}[] = [] 126 | for (let i = 0; i < intialData.length; i++) { 127 | try { 128 | let signer = new Wallet(intialData[i][0]) 129 | } catch (e: any) { 130 | console.log(c.red(`INVALID private key #${i + 1}: ${intialData[i][0]}`)) 131 | throw new Error(`INVALID private key #${i + 1}: ${intialData[i][0]}`) 132 | } 133 | if (validateAddr) { 134 | if (intialData[i].length == 1) { 135 | console.log(c.red(`NO ADDRESS #${i + 1}: ${intialData[i][0]}`)) 136 | throw new Error(`NO ADDRESS #${i + 1}: ${intialData[i][0]}`) 137 | } else { 138 | if (!isAddress(intialData[i][1])) { 139 | throw new Error(`INVALID ADDRESS #${i + 1}: ${intialData[i][0]}`) 140 | } 141 | } 142 | } 143 | keysAndAddresses.push({key: intialData[i][0], address: intialData[i][1] == undefined ? '' : intialData[i][1]}) 144 | // privates.push(intialData[i][0]) 145 | // addresses.push(intialData[i][1] == undefined ? '' : intialData[i][1]) 146 | } 147 | // return [privates, addresses] 148 | return keysAndAddresses 149 | } 150 | async function importProxies(path: string) { 151 | let data: string[] = [] 152 | let instream = fs.createReadStream(path) 153 | let rl = readline.createInterface(instream) 154 | rl.on('line', (line) => { 155 | data.push(line) 156 | }) 157 | await once(rl, 'close') 158 | return data 159 | } 160 | function appendToFile(file: string, data: string) { 161 | fs.appendFileSync(`${file}`, data) 162 | } 163 | function writeToFile(file: string, data: string) { 164 | fs.writeFileSync(`${file}`, data) 165 | } 166 | const RandomHelpers = new Random() 167 | 168 | export { 169 | c, 170 | log, 171 | sleep, 172 | defaultSleep, 173 | delayedPrint, 174 | retry, 175 | RandomHelpers, 176 | bigintToPrettyStr, 177 | importPrivateData, 178 | importAndValidatePrivateData, 179 | importProxies, 180 | appendToFile, 181 | writeToFile 182 | } 183 | -------------------------------------------------------------------------------- /src/utils/abi/Multicall2.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "components": [ 6 | { "internalType": "address", "name": "target", "type": "address" }, 7 | { "internalType": "bytes", "name": "callData", "type": "bytes" } 8 | ], 9 | "internalType": "struct Multicall2.Call[]", 10 | "name": "calls", 11 | "type": "tuple[]" 12 | } 13 | ], 14 | "name": "aggregate", 15 | "outputs": [ 16 | { "internalType": "uint256", "name": "blockNumber", "type": "uint256" }, 17 | { "internalType": "bytes[]", "name": "returnData", "type": "bytes[]" } 18 | ], 19 | "stateMutability": "nonpayable", 20 | "type": "function" 21 | }, 22 | { 23 | "inputs": [ 24 | { 25 | "components": [ 26 | { "internalType": "address", "name": "target", "type": "address" }, 27 | { "internalType": "bytes", "name": "callData", "type": "bytes" } 28 | ], 29 | "internalType": "struct Multicall2.Call[]", 30 | "name": "calls", 31 | "type": "tuple[]" 32 | } 33 | ], 34 | "name": "blockAndAggregate", 35 | "outputs": [ 36 | { "internalType": "uint256", "name": "blockNumber", "type": "uint256" }, 37 | { "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }, 38 | { 39 | "components": [ 40 | { "internalType": "bool", "name": "success", "type": "bool" }, 41 | { "internalType": "bytes", "name": "returnData", "type": "bytes" } 42 | ], 43 | "internalType": "struct Multicall2.Result[]", 44 | "name": "returnData", 45 | "type": "tuple[]" 46 | } 47 | ], 48 | "stateMutability": "nonpayable", 49 | "type": "function" 50 | }, 51 | { 52 | "inputs": [{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" }], 53 | "name": "getBlockHash", 54 | "outputs": [{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }], 55 | "stateMutability": "view", 56 | "type": "function" 57 | }, 58 | { 59 | "inputs": [], 60 | "name": "getBlockNumber", 61 | "outputs": [{ "internalType": "uint256", "name": "blockNumber", "type": "uint256" }], 62 | "stateMutability": "view", 63 | "type": "function" 64 | }, 65 | { 66 | "inputs": [], 67 | "name": "getCurrentBlockCoinbase", 68 | "outputs": [{ "internalType": "address", "name": "coinbase", "type": "address" }], 69 | "stateMutability": "view", 70 | "type": "function" 71 | }, 72 | { 73 | "inputs": [], 74 | "name": "getCurrentBlockDifficulty", 75 | "outputs": [{ "internalType": "uint256", "name": "difficulty", "type": "uint256" }], 76 | "stateMutability": "view", 77 | "type": "function" 78 | }, 79 | { 80 | "inputs": [], 81 | "name": "getCurrentBlockGasLimit", 82 | "outputs": [{ "internalType": "uint256", "name": "gaslimit", "type": "uint256" }], 83 | "stateMutability": "view", 84 | "type": "function" 85 | }, 86 | { 87 | "inputs": [], 88 | "name": "getCurrentBlockTimestamp", 89 | "outputs": [{ "internalType": "uint256", "name": "timestamp", "type": "uint256" }], 90 | "stateMutability": "view", 91 | "type": "function" 92 | }, 93 | { 94 | "inputs": [{ "internalType": "address", "name": "addr", "type": "address" }], 95 | "name": "getEthBalance", 96 | "outputs": [{ "internalType": "uint256", "name": "balance", "type": "uint256" }], 97 | "stateMutability": "view", 98 | "type": "function" 99 | }, 100 | { 101 | "inputs": [], 102 | "name": "getL1BlockNumber", 103 | "outputs": [{ "internalType": "uint256", "name": "l1BlockNumber", "type": "uint256" }], 104 | "stateMutability": "view", 105 | "type": "function" 106 | }, 107 | { 108 | "inputs": [], 109 | "name": "getLastBlockHash", 110 | "outputs": [{ "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }], 111 | "stateMutability": "view", 112 | "type": "function" 113 | }, 114 | { 115 | "inputs": [ 116 | { "internalType": "bool", "name": "requireSuccess", "type": "bool" }, 117 | { 118 | "components": [ 119 | { "internalType": "address", "name": "target", "type": "address" }, 120 | { "internalType": "bytes", "name": "callData", "type": "bytes" } 121 | ], 122 | "internalType": "struct Multicall2.Call[]", 123 | "name": "calls", 124 | "type": "tuple[]" 125 | } 126 | ], 127 | "name": "tryAggregate", 128 | "outputs": [ 129 | { 130 | "components": [ 131 | { "internalType": "bool", "name": "success", "type": "bool" }, 132 | { "internalType": "bytes", "name": "returnData", "type": "bytes" } 133 | ], 134 | "internalType": "struct Multicall2.Result[]", 135 | "name": "returnData", 136 | "type": "tuple[]" 137 | } 138 | ], 139 | "stateMutability": "nonpayable", 140 | "type": "function" 141 | }, 142 | { 143 | "inputs": [ 144 | { "internalType": "bool", "name": "requireSuccess", "type": "bool" }, 145 | { 146 | "components": [ 147 | { "internalType": "address", "name": "target", "type": "address" }, 148 | { "internalType": "bytes", "name": "callData", "type": "bytes" } 149 | ], 150 | "internalType": "struct Multicall2.Call[]", 151 | "name": "calls", 152 | "type": "tuple[]" 153 | } 154 | ], 155 | "name": "tryBlockAndAggregate", 156 | "outputs": [ 157 | { "internalType": "uint256", "name": "blockNumber", "type": "uint256" }, 158 | { "internalType": "bytes32", "name": "blockHash", "type": "bytes32" }, 159 | { 160 | "components": [ 161 | { "internalType": "bool", "name": "success", "type": "bool" }, 162 | { "internalType": "bytes", "name": "returnData", "type": "bytes" } 163 | ], 164 | "internalType": "struct Multicall2.Result[]", 165 | "name": "returnData", 166 | "type": "tuple[]" 167 | } 168 | ], 169 | "stateMutability": "nonpayable", 170 | "type": "function" 171 | } 172 | ] 173 | -------------------------------------------------------------------------------- /src/periphery/relayBridge.ts: -------------------------------------------------------------------------------- 1 | import {formatEther, parseEther, Wallet} from 'ethers' 2 | import {chains} from '../utils/constants' 3 | import axios from 'axios' 4 | import {estimateTx, getBalance, sendRawTx} from './web3Client' 5 | import {c, defaultSleep, RandomHelpers, retry} from '../utils/helpers' 6 | import {maxRetries, RelayBridgeConfig, sleepBetweenActions} from '../../config' 7 | import {ChainName} from '../utils/types' 8 | import {getProvider} from './utils' 9 | 10 | class RelayBridge extends RelayBridgeConfig { 11 | signer: Wallet 12 | constructor(signer: Wallet) { 13 | super() 14 | this.signer = signer 15 | } 16 | async bridgeRelay(signer: Wallet, currency = 'ETH', fromNetwork: ChainName, toNetwork: ChainName, value: bigint): Promise { 17 | let result: boolean | undefined = await retry( 18 | async () => { 19 | const fromChainId = chains[fromNetwork].id.toString() 20 | const toChainId = chains[toNetwork].id.toString() 21 | let avgBridgeFee = 501_383_102_086_736n 22 | const quoteBridgeResp = await axios.post( 23 | 'https://api.relay.link/execute/bridge', 24 | { 25 | user: await signer.getAddress(), 26 | originChainId: fromChainId, 27 | destinationChainId: toChainId, 28 | currency: currency.toLowerCase(), 29 | recipient: await signer.getAddress(), 30 | amount: (value - avgBridgeFee).toString(), 31 | usePermit: false, 32 | source: 'relay.link' 33 | }, 34 | { 35 | headers: { 36 | Host: 'api.relay.link', 37 | Origin: 'https://relay.link', 38 | Referer: 'https://relay.link/', 39 | 'Content-Type': 'application/json', 40 | 'User-Agent': 41 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36' 42 | } 43 | } 44 | ) 45 | let bridgeFee = BigInt(quoteBridgeResp.data?.fees.relayer) 46 | let valueToBridge = this.deductFee ? value - bridgeFee : value 47 | const bridgeResp = await axios.post( 48 | 'https://api.relay.link/execute/bridge', 49 | { 50 | user: await signer.getAddress(), 51 | originChainId: fromChainId, 52 | destinationChainId: toChainId, 53 | currency: currency.toLowerCase(), 54 | recipient: await signer.getAddress(), 55 | amount: valueToBridge.toString(), 56 | usePermit: false, 57 | source: 'relay.link' 58 | }, 59 | { 60 | headers: { 61 | Host: 'api.relay.link', 62 | Origin: 'https://relay.link', 63 | Referer: 'https://relay.link/', 64 | 'Content-Type': 'application/json', 65 | 'User-Agent': 66 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36' 67 | } 68 | } 69 | ) 70 | let tx = bridgeResp.data?.steps[0].items[0].data 71 | let testTx = {...tx} 72 | testTx.value = 1000000000n 73 | let estimate = await estimateTx(signer, testTx) 74 | let cost = (BigInt(tx?.gasPrice ?? tx?.maxFeePerGas) * BigInt(estimate) * 16n) / 10n 75 | tx.value = this.deductFee ? BigInt(tx?.value) - cost : BigInt(tx?.value) 76 | tx.gasLimit = estimate 77 | console.log(c.yellow(`[relay] bridging ${formatEther(tx.value)} ETH from ${fromNetwork} to ${toNetwork}`)) 78 | let hash = await sendRawTx(signer, tx, true) 79 | console.log( 80 | c.green(`[relay] ${formatEther(tx.value)} ${currency}: ${fromNetwork} --> ${toNetwork} ${chains[fromNetwork].explorer + hash}`) 81 | ) 82 | return true 83 | }, 84 | {maxRetryCount: maxRetries, retryInterval: 10, throwOnError: false} 85 | ) 86 | if (result == undefined) { 87 | console.log(c.red(`[relay] Bridge from ${fromNetwork} to ${toNetwork} failed`)) 88 | return false 89 | } else { 90 | return result 91 | } 92 | } 93 | 94 | async executeRelayBridge(signer: Wallet, currency = 'ETH') { 95 | let networks = RandomHelpers.shuffleArray(this.fromNetworks) 96 | for (let i = 0; i < networks.length; i++) { 97 | let fromNetwork = networks[i] as ChainName 98 | let toNetwork = this.toNetwork 99 | // since relay bridge is good only for eth, require that from user 100 | if ( 101 | chains[fromNetwork].currency.name.toLowerCase() != currency.toLowerCase() || 102 | chains[toNetwork].currency.name.toLowerCase() != currency.toLowerCase() 103 | ) { 104 | console.log( 105 | '[relay]', 106 | c.red('You can bridge only ETH on ETH-specific chains.', `${fromNetwork} or ${toNetwork} is not ETH-specific.`) 107 | ) 108 | return false 109 | } 110 | let valueToBridge = await this.getSendValue(fromNetwork) 111 | if (valueToBridge < 0n) { 112 | console.log(c.red(`value to bridge must be > 0. Got: ${formatEther(valueToBridge)}`)) 113 | } 114 | let success = await this.bridgeRelay(signer.connect(getProvider(fromNetwork)), currency, fromNetwork, toNetwork, valueToBridge) 115 | if (success) { 116 | await defaultSleep(RandomHelpers.getRandomNumber(sleepBetweenActions)) 117 | } 118 | } 119 | } 120 | 121 | async getSendValue(networkName: ChainName): Promise { 122 | if (parseFloat(this.values.from) < 0 || parseFloat(this.values.to) < 0) { 123 | console.log(c.red(`Can't pass negative numbers to NativeSender`)) 124 | throw Error(`Can't pass negative numbers to NativeSender`) 125 | } 126 | if (this.values.from.includes('%') && this.values.to.includes('%')) { 127 | let precision = 1000 128 | let balance = await getBalance(getProvider(networkName), this.signer.address) 129 | let randomPortion = BigInt( 130 | (RandomHelpers.getRandomNumber({from: parseFloat(this.values.from), to: parseFloat(this.values.to)}, 3) * precision).toFixed() 131 | ) 132 | let value = (balance * randomPortion) / (100n * BigInt(precision)) 133 | return value 134 | } else if (!this.values.from.includes('%') && !this.values.to.includes('%')) { 135 | let value = parseEther(RandomHelpers.getRandomNumber({from: parseFloat(this.values.from), to: parseFloat(this.values.to)}).toFixed()) 136 | return value 137 | } else { 138 | console.log(c.red(`Your "values" in "NativeSenderConfig" are wrong. Should be *number* or *percentage*`)) 139 | throw Error(`Your "values" in "NativeSenderConfig" are wrong. Should be *number* or *percentage*`) 140 | } 141 | } 142 | } 143 | export {RelayBridge} 144 | -------------------------------------------------------------------------------- /src/periphery/sushiswap.ts: -------------------------------------------------------------------------------- 1 | import {MaxUint256, Wallet, ZeroAddress} from 'ethers' 2 | import {ChainName} from '../utils/types' 3 | import {c, RandomHelpers, retry} from '../utils/helpers' 4 | import {chains, sushiswapV2Routers} from '../utils/constants' 5 | import {approve, estimateTx, getGwei, sendTx} from './web3Client' 6 | import {DEV} from '../../config' 7 | import {ERC20__factory, UniswapV2Router02__factory} from '../../typechain' 8 | 9 | class Sushiswap { 10 | signer: Wallet 11 | networkName: ChainName 12 | constructor(signer: Wallet, network?: ChainName) { 13 | this.signer = signer 14 | this.networkName = network ?? 'Ethereum' 15 | } 16 | setNetwork(newNetworkName: ChainName) { 17 | this.networkName = newNetworkName 18 | return this 19 | } 20 | async swap( 21 | tokenIn: { 22 | address: string 23 | name: string 24 | symbol: string 25 | decimals: bigint 26 | }, 27 | tokenOut: { 28 | address: string 29 | name: string 30 | symbol: string 31 | decimals: bigint 32 | }, 33 | amountIn: bigint 34 | ): Promise { 35 | let quote = await this.#quoteSwap(tokenIn, tokenOut, amountIn) 36 | if (quote == undefined) { 37 | // console.log(`Sushiswap: ${tokenIn.symbol} --> ${tokenOut.symbol} swap is too expensive or is not available`) 38 | return false 39 | } 40 | try { 41 | let swapResult = await this.#executeSwap(tokenIn, tokenOut, amountIn, quote) 42 | if (!swapResult) { 43 | // console.log(`Sushiswap: ${tokenIn.symbol} --> ${tokenOut.symbol} swap is too expensive or is not available`) 44 | } 45 | return swapResult 46 | } catch (e: any) { 47 | console.log(c.red(`Sushiswap:swap Swap failed, reason: ${e?.message ?? 'unknown'}`)) 48 | return false 49 | } 50 | } 51 | async #quoteSwap( 52 | tokenIn: { 53 | address: string 54 | name: string 55 | symbol: string 56 | decimals: bigint 57 | }, 58 | tokenOut: { 59 | address: string 60 | name: string 61 | symbol: string 62 | decimals: bigint 63 | }, 64 | amountIn: bigint 65 | ) { 66 | let res: 67 | | { 68 | funcName: 69 | | 'swapExactETHForTokensSupportingFeeOnTransferTokens' 70 | | 'swapExactTokensForETHSupportingFeeOnTransferTokens' 71 | | 'swapExactTokensForTokensSupportingFeeOnTransferTokens' 72 | path: string[] 73 | amountOut: bigint 74 | } 75 | | undefined 76 | res = await retry( 77 | async () => { 78 | let routerAddress = sushiswapV2Routers[this.networkName] 79 | if (routerAddress == undefined || routerAddress == '') { 80 | if (DEV) { 81 | console.log(`SushiSwap:quoteSwap Sushiswap is not available on ${this.networkName}, can't swap`) 82 | } 83 | return 84 | } 85 | let sushiswapRouter = UniswapV2Router02__factory.connect(routerAddress, this.signer) 86 | let isTokenInNative = this.#isTokenNative(tokenIn.address) 87 | 88 | let isTokenOutNative = this.#isTokenNative(tokenOut.address) 89 | 90 | if (isTokenInNative && isTokenOutNative) { 91 | if (DEV) { 92 | console.log('Sushiswap:quoteSwap tokenIn and tokenOut are both native') 93 | } 94 | return 95 | } 96 | let path = this.#buildPath(tokenIn.address, tokenOut.address) 97 | let amountOut = (await sushiswapRouter.getAmountsOut(amountIn, path))[1] 98 | let funcName: 99 | | 'swapExactETHForTokensSupportingFeeOnTransferTokens' 100 | | 'swapExactTokensForETHSupportingFeeOnTransferTokens' 101 | | 'swapExactTokensForTokensSupportingFeeOnTransferTokens' 102 | 103 | funcName = isTokenInNative 104 | ? 'swapExactETHForTokensSupportingFeeOnTransferTokens' 105 | : isTokenOutNative 106 | ? 'swapExactTokensForETHSupportingFeeOnTransferTokens' 107 | : 'swapExactTokensForTokensSupportingFeeOnTransferTokens' 108 | return {funcName: funcName, path: path, amountOut: amountOut} 109 | }, 110 | {maxRetryCount: 3, retryInterval: 10, needLog: false, throwOnError: false} 111 | ) 112 | return res 113 | } 114 | async #executeSwap( 115 | tokenIn: { 116 | address: string 117 | name: string 118 | symbol: string 119 | decimals: bigint 120 | }, 121 | tokenOut: { 122 | address: string 123 | name: string 124 | symbol: string 125 | decimals: bigint 126 | }, 127 | amountIn: bigint, 128 | quote: { 129 | funcName: 130 | | 'swapExactETHForTokensSupportingFeeOnTransferTokens' 131 | | 'swapExactTokensForETHSupportingFeeOnTransferTokens' 132 | | 'swapExactTokensForTokensSupportingFeeOnTransferTokens' 133 | path: string[] 134 | amountOut: bigint 135 | } 136 | ) { 137 | let routerAddress = sushiswapV2Routers[this.networkName] 138 | let sushiswapRouter = UniswapV2Router02__factory.connect(routerAddress, this.signer) 139 | let token = ERC20__factory.connect(tokenIn.address, this.signer) 140 | 141 | let tx = { 142 | from: this.signer.address, 143 | to: await token.getAddress(), 144 | data: token.interface.encodeFunctionData('approve', [await sushiswapRouter.getAddress(), amountIn]), 145 | value: 0n 146 | } 147 | let approvalGasLimit = await estimateTx(this.signer, tx, 1.2) // ~60k 148 | let swapGasLimit = approvalGasLimit * 3n // ~120k but lets make it 3x 149 | let {gasPrice} = await getGwei(this.signer, 1.2) 150 | let executionCost = (approvalGasLimit + swapGasLimit) * gasPrice 151 | if (quote.funcName.includes('swapExactTokensForETHSupportingFeeOnTransferTokens')) { 152 | if (executionCost > quote.amountOut) { 153 | // console.log(`Sushiswap:executeSwap ${tokenIn.symbol}-->${tokenOut.name} swap cost exceeds received value`) 154 | return false 155 | } 156 | } 157 | if (!quote.funcName.includes('swapExactETHForTokensSupportingFeeOnTransferTokens')) { 158 | let approvalHash = await approve(this.signer, tokenIn.address, routerAddress, amountIn, amountIn) 159 | if (DEV) { 160 | console.log( 161 | c.blue( 162 | `Sushiswap:executeSwap ${tokenIn.symbol} approved ${ 163 | approvalHash == '' ? "don't need approve" : chains[this.networkName].explorer + approvalHash 164 | }` 165 | ) 166 | ) 167 | } 168 | } 169 | let deadline = Math.floor(new Date().getTime() / 1000) + Math.floor(RandomHelpers.getRandomNumber({from: 30 * 60, to: 1 * 60 * 60})) 170 | 171 | let swapTx = { 172 | from: this.signer.address, 173 | to: await sushiswapRouter.getAddress(), 174 | // @ts-ignore 175 | data: sushiswapRouter.interface.encodeFunctionData(quote.funcName, [ 176 | amountIn, 177 | (quote.amountOut * 98n) / 100n, 178 | quote.path, 179 | this.signer.address, 180 | deadline 181 | ]), 182 | value: this.#isTokenNative(tokenIn.address) ? amountIn : 0n 183 | } 184 | let swapHash = await sendTx(this.signer, swapTx, {price: 1.1, limit: 1.1}, true) 185 | console.log( 186 | `[Sushiswap]`, 187 | `$${c.bold(tokenIn.symbol)} --> $${c.bold(tokenOut.symbol)} ${c.green(chains[this.networkName].explorer + swapHash)}` 188 | ) 189 | return true 190 | } 191 | #buildPath(tokenIn: string, tokenOut: string): string[] { 192 | let token1 = this.#isTokenNative(tokenIn) ? chains[this.networkName].tokens.WNATIVE.address : tokenIn 193 | let token2 = this.#isTokenNative(tokenOut) ? chains[this.networkName].tokens.WNATIVE.address : tokenOut 194 | return [token1, token2] 195 | } 196 | #isTokenNative(token: string) { 197 | return ( 198 | token.toLowerCase() == ZeroAddress.toLowerCase() || 199 | token.toLowerCase() == '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'.toLowerCase() || 200 | token.toLowerCase() == '0x0000000000000000000000000000000000001010'.toLowerCase() // polygon MATIC address o_0 201 | ) 202 | } 203 | } 204 | 205 | export {Sushiswap} 206 | -------------------------------------------------------------------------------- /src/periphery/odosAggregator.ts: -------------------------------------------------------------------------------- 1 | import {formatUnits, TransactionRequest, Wallet, ZeroAddress} from 'ethers' 2 | import {ChainName, OdosAssembleType, OdosQuoteType} from '../utils/types' 3 | import axios, {AxiosInstance} from 'axios' 4 | import {c, retry} from '../utils/helpers' 5 | import {chains} from '../utils/constants' 6 | import {approve, getGwei, sendTx} from './web3Client' 7 | import {DEV, maxRetries} from '../../config' 8 | import {HttpsProxyAgent} from 'https-proxy-agent' 9 | 10 | class OdosAggregator { 11 | quoteUrl = 'https://api.odos.xyz/sor/quote/v2' 12 | assembleUrl = 'https://api.odos.xyz/sor/assemble' 13 | signer: Wallet 14 | networkName: ChainName 15 | session: AxiosInstance 16 | constructor(signer: Wallet, network?: ChainName, proxy?: string) { 17 | this.signer = signer 18 | this.networkName = network ?? 'Ethereum' 19 | if (proxy) { 20 | this.session = axios.create({ 21 | httpAgent: new HttpsProxyAgent(`http://${proxy}`), 22 | httpsAgent: new HttpsProxyAgent(`http://${proxy}`), 23 | timeout: 5_000 24 | }) 25 | } else { 26 | this.session = axios.create({timeout: 5_000}) 27 | } 28 | } 29 | setNetwork(newNetworkName: ChainName) { 30 | this.networkName = newNetworkName 31 | return this 32 | } 33 | async swap( 34 | tokenIn: { 35 | address: string 36 | name: string 37 | symbol: string 38 | decimals: bigint 39 | }, 40 | tokenOut: { 41 | address: string 42 | name: string 43 | symbol: string 44 | decimals: bigint 45 | }, 46 | amountIn: bigint 47 | ): Promise { 48 | let quote = await this.#quoteSwap(tokenIn, tokenOut, amountIn) 49 | if (quote == undefined) { 50 | // console.log(`[Odos] ${tokenIn.symbol} --> ${tokenOut.symbol} swap is too expensive or is not available`) 51 | return false 52 | } 53 | try { 54 | let swapResult = await this.#executeSwap(tokenIn, tokenOut, quote) 55 | if (swapResult) { 56 | } 57 | return swapResult 58 | } catch (e: any) { 59 | console.log(c.red(`OdosAggregator:swap Swap failed, reason: ${e?.message ?? 'unknown'}`)) 60 | return false 61 | } 62 | } 63 | async #quoteSwap( 64 | tokenIn: { 65 | address: string 66 | name: string 67 | symbol: string 68 | decimals: bigint 69 | }, 70 | tokenOut: { 71 | address: string 72 | name: string 73 | symbol: string 74 | decimals: bigint 75 | }, 76 | amountIn: bigint 77 | ) { 78 | let supportedNetworks: ChainName[] = [ 79 | 'Ethereum', 80 | 'Arbitrum', 81 | 'Avalanche', 82 | 'Polygon', 83 | 'Bsc', 84 | 'Optimism', 85 | 'Base', 86 | 'Fantom', 87 | 'Zksync', 88 | 'Linea', 89 | 'Scroll', 90 | 'Mantle' 91 | ] 92 | if (!supportedNetworks.includes(this.networkName)) { 93 | if (DEV) { 94 | console.log(`OdosAggregator:quoteSwap ${this.networkName} network not supported`) 95 | } 96 | return undefined 97 | } 98 | if (this.#isTokenNative(tokenIn.address) && this.#isTokenNative(tokenOut.address)) { 99 | if (DEV) { 100 | console.log(`[Odos] can't swap same asset (${tokenIn.name} --> ${tokenOut.name})`) 101 | } 102 | return undefined 103 | } 104 | const payload = { 105 | chainId: chains[this.networkName].id, 106 | inputTokens: [ 107 | { 108 | tokenAddress: this.#isTokenNative(tokenIn.address) ? ZeroAddress : tokenIn.address, 109 | amount: amountIn.toString() 110 | } 111 | ], 112 | outputTokens: [ 113 | { 114 | tokenAddress: this.#isTokenNative(tokenOut.address) ? ZeroAddress : tokenOut.address, 115 | proportion: 1 116 | } 117 | ], 118 | userAddr: this.signer.address, 119 | slippageLimitPercent: 10, 120 | pathViz: false, 121 | referralCode: 1, 122 | simple: true // simple quotes will be preferred 123 | } 124 | let res: OdosQuoteType | undefined = await retry( 125 | async () => { 126 | let resp = await this.session.post(this.quoteUrl, payload, { 127 | headers: { 128 | 'Content-Type': 'application/json' 129 | } 130 | }) 131 | let body: OdosQuoteType = resp.data 132 | if (body.netOutValue < 0.01) { 133 | return 134 | } 135 | return body 136 | }, 137 | {maxRetryCount: maxRetries, retryInterval: 5, needLog: false, throwOnError: false} 138 | ) 139 | return res 140 | } 141 | async #executeSwap( 142 | tokenIn: { 143 | address: string 144 | name: string 145 | symbol: string 146 | decimals: bigint 147 | }, 148 | tokenOut: { 149 | address: string 150 | name: string 151 | symbol: string 152 | decimals: bigint 153 | }, 154 | quote: OdosQuoteType 155 | ) { 156 | let approvalTarget: string | undefined = await retry( 157 | async () => { 158 | let resp = await this.session.get(`https://api.odos.xyz/info/contract-info/v2/${chains[this.networkName].id}`, { 159 | headers: {'Content-Type': 'application/json'} 160 | }) 161 | return resp.data.routerAddress 162 | }, 163 | {maxRetryCount: 5, retryInterval: 5, needLog: false, throwOnError: false} 164 | ) 165 | if (!approvalTarget || approvalTarget == undefined) { 166 | console.log('OdosAggregator:executeSwap Could not get approval target') 167 | return false 168 | } 169 | let approveHash = await approve(this.signer, quote.inTokens[0], approvalTarget, quote.inAmounts[0], quote.inAmounts[0]) 170 | if (DEV) { 171 | console.log( 172 | c.blue( 173 | `OdosAggregator:executeSwap ${tokenIn.symbol} approved ${ 174 | approveHash == '' ? "don't need approve" : chains[this.networkName].explorer + approveHash 175 | }` 176 | ) 177 | ) 178 | } 179 | let tx: OdosAssembleType['transaction'] | undefined = await retry( 180 | async () => { 181 | const assembleRequestBody = { 182 | userAddr: this.signer.address, // the checksummed address used to generate the quote 183 | pathId: quote.pathId, // Replace with the pathId from quote response in step 1 184 | simulate: true // this can be set to true if the user isn't doing their own estimate gas call for the transaction 185 | } 186 | let resp = await this.session.post(this.assembleUrl, assembleRequestBody, {headers: {'Content-Type': 'application/json'}}) 187 | let body: OdosAssembleType = resp.data 188 | if (body.simulation.isSuccess) { 189 | return body.transaction 190 | } else { 191 | // console.log('OdosAggregator:executeSwap swap simulation failed') 192 | throw Error('OdosAggregator:executeSwap swap simulation failed') 193 | } 194 | }, 195 | {maxRetryCount: maxRetries, retryInterval: 5, needLog: false, throwOnError: false} 196 | ) 197 | if (tx == undefined) { 198 | console.log(c.red('OdosAggregator:executeSwap swap simulation failed')) 199 | return false 200 | } 201 | let adjustedTx: TransactionRequest & OdosAssembleType['transaction'] = tx 202 | adjustedTx.gasLimit = tx.gas 203 | if (adjustedTx.gasLimit != undefined) { 204 | adjustedTx.gasLimit = (BigInt(adjustedTx.gasLimit) * 11n) / 10n 205 | } 206 | delete adjustedTx?.gas 207 | let gasPriceMultiplier = this.networkName == 'Ethereum' || this.networkName == 'Polygon' || this.networkName == 'Avalanche' ? 1.1 : 1 208 | let swapHash = await sendTx(this.signer, adjustedTx, {price: gasPriceMultiplier, limit: 1}, true) 209 | console.log( 210 | `[Odos] `, 211 | `$${c.bold(tokenIn.symbol)} --> $${c.bold(tokenOut.symbol)} ${c.green(chains[this.networkName].explorer + swapHash)}` 212 | ) 213 | return true 214 | } 215 | #isTokenNative(token: string) { 216 | return ( 217 | token.toLowerCase() == ZeroAddress.toLowerCase() || 218 | token.toLowerCase() == '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'.toLowerCase() || 219 | token.toLowerCase() == '0x0000000000000000000000000000000000001010'.toLowerCase() // polygon MATIC address o_0 220 | ) 221 | } 222 | } 223 | 224 | export {OdosAggregator} 225 | -------------------------------------------------------------------------------- /src/utils/tokenlists/Scroll.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "chainId": 534352, 4 | "address": "0x2147a89fb4608752807216d5070471c09a0dce32", 5 | "name": "Z Protocol", 6 | "symbol": "ZP", 7 | "decimals": 18, 8 | "logoURI": "https://assets.coingecko.com/coins/images/36011/thumb/about_img01.png?1710324761" 9 | }, 10 | { 11 | "chainId": 534352, 12 | "address": "0xeb466342c4d449bc9f53a865d5cb90586f405215", 13 | "name": "Bridged Axelar Wrapped USD Coin Scroll", 14 | "symbol": "AXLUSDC", 15 | "decimals": 6, 16 | "logoURI": "https://assets.coingecko.com/coins/images/32612/thumb/USDC.png?1698734090" 17 | }, 18 | { 19 | "chainId": 534352, 20 | "address": "0x0fc479e2f9b7310bfb1db606cf565dea6910eedc", 21 | "name": "Papyrus Swap", 22 | "symbol": "PAPYRUS", 23 | "decimals": 18, 24 | "logoURI": "https://assets.coingecko.com/coins/images/32479/thumb/PapyrusLogo.png?1698286849" 25 | }, 26 | { 27 | "chainId": 534352, 28 | "address": "0xecc68d0451e20292406967fe7c04280e5238ac7d", 29 | "name": "Axelar Bridged Frax Ether", 30 | "symbol": "AXLFRXETH", 31 | "decimals": 18, 32 | "logoURI": "https://assets.coingecko.com/coins/images/38976/thumb/Screen_Shot_2024-06-18_at_12.55.54_PM_2.png?1719714886" 33 | }, 34 | { 35 | "chainId": 534352, 36 | "address": "0x3c1bca5a656e69edcd0d4e36bebb3fcdaca60cf1", 37 | "name": "Bridged Wrapped Bitcoin Scroll ", 38 | "symbol": "WBTC", 39 | "decimals": 8, 40 | "logoURI": "https://assets.coingecko.com/coins/images/32614/thumb/wrapped_bitcoin_wbtc.png?1698735124" 41 | }, 42 | { 43 | "chainId": 534352, 44 | "address": "0x1a2fcb585b327fadec91f55d45829472b15f17a4", 45 | "name": "Tokan", 46 | "symbol": "TKN", 47 | "decimals": 18, 48 | "logoURI": "https://assets.coingecko.com/coins/images/37905/thumb/200.png?1715932528" 49 | }, 50 | { 51 | "chainId": 534352, 52 | "address": "0xf55bec9cafdbe8730f096aa55dad6d22d44099df", 53 | "name": "Bridged Tether Scroll ", 54 | "symbol": "USDT", 55 | "decimals": 6, 56 | "logoURI": "https://assets.coingecko.com/coins/images/32610/thumb/usdt_%281%29.png?1698733524" 57 | }, 58 | { 59 | "chainId": 534352, 60 | "address": "0x690f1d2da47d9a759a93dd2b0ace3c1627f216ba", 61 | "name": "Venium", 62 | "symbol": "VENIUM", 63 | "decimals": 18, 64 | "logoURI": "https://assets.coingecko.com/coins/images/36346/thumb/scrolliumlogo.png?1711187400" 65 | }, 66 | { 67 | "chainId": 534352, 68 | "address": "0x06efdbff2a14a7c8e15944d1f4a48f9f95f663a4", 69 | "name": "Bridged USD Coin Scroll ", 70 | "symbol": "USDC", 71 | "decimals": 6, 72 | "logoURI": "https://assets.coingecko.com/coins/images/32611/thumb/USDC.png?1698733754" 73 | }, 74 | { 75 | "chainId": 534352, 76 | "address": "0x6b7d1c9d519dfc3a5d8d1b7c15d4e5bbe8dde1cf", 77 | "name": "OmniKingdoms Gold", 78 | "symbol": "OMKG", 79 | "decimals": 18, 80 | "logoURI": "https://assets.coingecko.com/coins/images/32674/thumb/Avatar.png?1698911193" 81 | }, 82 | { 83 | "chainId": 534352, 84 | "address": "0x5300000000000000000000000000000000000004", 85 | "name": "Bridged Wrapped Ether Scroll ", 86 | "symbol": "WETH", 87 | "decimals": 18, 88 | "logoURI": "https://assets.coingecko.com/coins/images/32315/thumb/weth_%281%29.png?1697365181" 89 | }, 90 | { 91 | "chainId": 534352, 92 | "address": "0xf610a9dfb7c89644979b4a0f27063e9e7d7cda32", 93 | "name": "Bridged Wrapped Lido Staked Ether Scro", 94 | "symbol": "WSTETH", 95 | "decimals": 18, 96 | "logoURI": "https://assets.coingecko.com/coins/images/32615/thumb/wsteth.png?1698735772" 97 | }, 98 | { 99 | "chainId": 534352, 100 | "address": "0x750351a9f75f98f2c2e91d4edb3beb14e719557e", 101 | "name": "Scribes", 102 | "symbol": "SCRIBES", 103 | "decimals": 18, 104 | "logoURI": "https://assets.coingecko.com/coins/images/38185/thumb/scribes.png?1716788481" 105 | }, 106 | { 107 | "chainId": 534352, 108 | "address": "0xaaae8378809bb8815c08d3c59eb0c7d1529ad769", 109 | "name": "Nuri Exchange", 110 | "symbol": "NURI", 111 | "decimals": 18, 112 | "logoURI": "https://assets.coingecko.com/coins/images/38831/thumb/Nuri_token.png?1719192439" 113 | }, 114 | { 115 | "chainId": 534352, 116 | "address": "0x188b158caf5ea252012dbd6030afc030329c4961", 117 | "name": "Zen", 118 | "symbol": "ZEN", 119 | "decimals": 18, 120 | "logoURI": "https://assets.coingecko.com/coins/images/38087/thumb/circle_zen_png_200.png?1716477696" 121 | }, 122 | { 123 | "chainId": 534352, 124 | "address": "0x61a9cc561b6c1f9c31bcdeb447afecf25f33bbf9", 125 | "name": "Pandacoin Inu", 126 | "symbol": "PANDA", 127 | "decimals": 18, 128 | "logoURI": "https://assets.coingecko.com/coins/images/35746/thumb/panda.jpeg?1709714542" 129 | }, 130 | { 131 | "chainId": 534352, 132 | "address": "0x2fc5cf65fd0a660801f119832b2158756968266d", 133 | "name": "Chi USD", 134 | "symbol": "CHI", 135 | "decimals": 18, 136 | "logoURI": "https://assets.coingecko.com/coins/images/37906/thumb/CHI_PNG200x200.png?1715932591" 137 | }, 138 | { 139 | "chainId": 534352, 140 | "address": "0x95a52ec1d60e74cd3eb002fe54a2c74b185a4c16", 141 | "name": "Skydrome", 142 | "symbol": "SKY", 143 | "decimals": 18, 144 | "logoURI": "https://assets.coingecko.com/coins/images/33902/thumb/icon_200x200.png?1703237670" 145 | }, 146 | { 147 | "chainId": 534352, 148 | "address": "0xdd6a49995ad38fe7409b5d5cb5539261bd1bc901", 149 | "name": "Danjuan Scroll Cat", 150 | "symbol": "CAT", 151 | "decimals": 18, 152 | "logoURI": "https://assets.coingecko.com/coins/images/34637/thumb/200x200.jpg?1705555969" 153 | }, 154 | { 155 | "chainId": 534352, 156 | "address": "0x59debed8d46a0cb823d8be8b957add987ead39aa", 157 | "name": "Quack Token", 158 | "symbol": "QUACK", 159 | "decimals": 18, 160 | "logoURI": "https://assets.coingecko.com/coins/images/31436/thumb/0x639C0D019C257966C4907bD4E68E3F349bB58109.png?1696530251" 161 | }, 162 | { 163 | "chainId": 534352, 164 | "address": "0x47c337bd5b9344a6f3d6f58c474d9d8cd419d8ca", 165 | "name": "DackieSwap", 166 | "symbol": "DACKIE", 167 | "decimals": 18, 168 | "logoURI": "https://assets.coingecko.com/coins/images/30752/thumb/dackieswap_large.png?1707290196" 169 | }, 170 | { 171 | "chainId": 534352, 172 | "address": "0x0018d96c579121a94307249d47f053e2d687b5e7", 173 | "name": "Metavault Trade", 174 | "symbol": "MVX", 175 | "decimals": 18, 176 | "logoURI": "https://assets.coingecko.com/coins/images/25402/thumb/mvx.png?1696524534" 177 | }, 178 | { 179 | "chainId": 534352, 180 | "address": "0xdf474b7109b73b7d57926d43598d5934131136b2", 181 | "name": "Ankr Network", 182 | "symbol": "ANKR", 183 | "decimals": 18, 184 | "logoURI": "https://assets.coingecko.com/coins/images/4324/thumb/U85xTl2.png?1696504928" 185 | }, 186 | { 187 | "chainId": 534352, 188 | "address": "0x12d8ce035c5de3ce39b1fdd4c1d5a745eaba3b8c", 189 | "name": "Ankr Staked ETH", 190 | "symbol": "ANKRETH", 191 | "decimals": 18, 192 | "logoURI": "https://assets.coingecko.com/coins/images/13403/thumb/aETHc.png?1696513165" 193 | }, 194 | { 195 | "chainId": 534352, 196 | "address": "0x0a3bb08b3a15a19b4de82f8acfc862606fb69a2d", 197 | "name": "iZUMi Bond USD", 198 | "symbol": "IUSD", 199 | "decimals": 18, 200 | "logoURI": "https://assets.coingecko.com/coins/images/25388/thumb/iusd-logo-symbol-10k%E5%A4%A7%E5%B0%8F.png?1696524521" 201 | }, 202 | { 203 | "chainId": 534352, 204 | "address": "0x60d01ec2d5e98ac51c8b4cf84dfcce98d527c747", 205 | "name": "iZUMi Finance", 206 | "symbol": "IZI", 207 | "decimals": 18, 208 | "logoURI": "https://assets.coingecko.com/coins/images/21791/thumb/izumi-logo-symbol.png?1696521144" 209 | }, 210 | { 211 | "chainId": 534352, 212 | "address": "0xddeb23905f6987d5f786a93c00bbed3d97af1ccc", 213 | "name": "PunkSwap", 214 | "symbol": "PUNK", 215 | "decimals": 18, 216 | "logoURI": "https://assets.coingecko.com/coins/images/32270/thumb/punk.jpg?1697178661" 217 | }, 218 | { 219 | "chainId": 534352, 220 | "address": "0x2b1d36f5b61addaf7da7ebbd11b35fd8cfb0de31", 221 | "name": "Interport Token", 222 | "symbol": "ITP", 223 | "decimals": 18, 224 | "logoURI": "https://assets.coingecko.com/coins/images/28338/thumb/ITP_Logo_200.png?1696527344" 225 | }, 226 | { 227 | "chainId": 534352, 228 | "address": "0x1467b62a6ae5cdcb10a6a8173cfe187dd2c5a136", 229 | "name": "Symbiosis", 230 | "symbol": "SIS", 231 | "decimals": 18, 232 | "logoURI": "https://assets.coingecko.com/coins/images/20805/thumb/SymbiosisFinance_logo-150x150.jpeg?1696520198" 233 | } 234 | ] -------------------------------------------------------------------------------- /src/utils/tokenlists/Core.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "chainId": 1116, 4 | "address": "0xc24b642357d7dd1bbe33f3d8aa0101dfa2cf6eb9", 5 | "name": "Core Stake Token", 6 | "symbol": "CST", 7 | "decimals": 18, 8 | "logoURI": "https://assets.coingecko.com/coins/images/38937/thumb/CST_TOKEN.png?1719568464" 9 | }, 10 | { 11 | "chainId": 1116, 12 | "address": "0xb0788b601c0d712702bc829b52771199ad8e33ff", 13 | "name": "IceCreamSwap WCORE", 14 | "symbol": "WCORE", 15 | "decimals": 18, 16 | "logoURI": "https://assets.coingecko.com/coins/images/36831/thumb/core.jpeg?1712559362" 17 | }, 18 | { 19 | "chainId": 1116, 20 | "address": "0x40375c92d9faf44d2f9db9bd9ba41a3317a2404f", 21 | "name": "Wrapped CORE", 22 | "symbol": "WCORE", 23 | "decimals": 18, 24 | "logoURI": "https://assets.coingecko.com/coins/images/29276/thumb/wcore.png?1696528228" 25 | }, 26 | { 27 | "chainId": 1116, 28 | "address": "0xa20b3b97df3a02f9185175760300a06b4e0a2c05", 29 | "name": "Staked CORE", 30 | "symbol": "SCORE", 31 | "decimals": 18, 32 | "logoURI": "https://assets.coingecko.com/coins/images/29469/thumb/score.png?1696528414" 33 | }, 34 | { 35 | "chainId": 1116, 36 | "address": "0xc20e1e271c4b3db0e3f31ece50fcef73438f2783", 37 | "name": "OreSwap Old ", 38 | "symbol": "OST", 39 | "decimals": 18, 40 | "logoURI": "https://assets.coingecko.com/coins/images/36817/thumb/0xC20e1E271c4b3dB0e3F31ece50fCEf73438F2783.png?1712550676" 41 | }, 42 | { 43 | "chainId": 1116, 44 | "address": "0x1a639e150d2210a4be4a5f0857a9151b241e7ae4", 45 | "name": "Archerswap BOW", 46 | "symbol": "BOW", 47 | "decimals": 18, 48 | "logoURI": "https://assets.coingecko.com/coins/images/29458/thumb/bow200x200.png?1696528405" 49 | }, 50 | { 51 | "chainId": 1116, 52 | "address": "0x496bb259d0117e89b2e73433524e9838c3073e60", 53 | "name": "UnityCore", 54 | "symbol": "UCORE", 55 | "decimals": 18, 56 | "logoURI": "https://assets.coingecko.com/coins/images/30086/thumb/IMG_22042023_164828_%28200_x_200_pixel%29.png?1696529010" 57 | }, 58 | { 59 | "chainId": 1116, 60 | "address": "0xfd843e2eb0e5f7e652fb864477d57510a469b332", 61 | "name": "OpenEX Network Token", 62 | "symbol": "OEX", 63 | "decimals": 18, 64 | "logoURI": "https://assets.coingecko.com/coins/images/36770/thumb/oex-r-200.png?1712285533" 65 | }, 66 | { 67 | "chainId": 1116, 68 | "address": "0xe9aece1ba8bbd429a1ed33349c61280441ac8f99", 69 | "name": "Liquify Network", 70 | "symbol": "LIQUIFY", 71 | "decimals": 18, 72 | "logoURI": "https://assets.coingecko.com/coins/images/29401/thumb/full_logo.png?1696528351" 73 | }, 74 | { 75 | "chainId": 1116, 76 | "address": "0x1483a469ef2c5b7dd2cb1b9174b01545c9a7fb69", 77 | "name": "Pumpkin Cat", 78 | "symbol": "PUMP", 79 | "decimals": 18, 80 | "logoURI": "https://assets.coingecko.com/coins/images/37932/thumb/Pumpkin_Cat_200x200.png?1715941040" 81 | }, 82 | { 83 | "chainId": 1116, 84 | "address": "0x98564e70c7fcc6d947ffe6d9efed5ba68b306f2e", 85 | "name": "Ignore Fud", 86 | "symbol": "4TOKEN", 87 | "decimals": 18, 88 | "logoURI": "https://assets.coingecko.com/coins/images/29626/thumb/200x200.png?1696528562" 89 | }, 90 | { 91 | "chainId": 1116, 92 | "address": "0x900101d06a7426441ae63e9ab3b9b0f63be145f1", 93 | "name": "Bridged USDT Core ", 94 | "symbol": "USDT", 95 | "decimals": 6, 96 | "logoURI": "https://assets.coingecko.com/coins/images/34424/thumb/usdt_logo.png?1704857706" 97 | }, 98 | { 99 | "chainId": 1116, 100 | "address": "0xa4151b2b3e269645181dccf2d426ce75fcbdeca9", 101 | "name": "Bridged USDC Core ", 102 | "symbol": "USDC", 103 | "decimals": 6, 104 | "logoURI": "https://assets.coingecko.com/coins/images/34423/thumb/usdc.png?1704857726" 105 | }, 106 | { 107 | "chainId": 1116, 108 | "address": "0x5c44d3d2312aba4d5f2406a98bf374bc76455092", 109 | "name": "WOOF", 110 | "symbol": "WOOF", 111 | "decimals": 18, 112 | "logoURI": "https://assets.coingecko.com/coins/images/32430/thumb/200x200_woof_token-01.png?1698121853" 113 | }, 114 | { 115 | "chainId": 1116, 116 | "address": "0x962d45c91e2e4f29ddc96c626976ece600908ba6", 117 | "name": "ArcherSwap Hunter", 118 | "symbol": "HUNT", 119 | "decimals": 18, 120 | "logoURI": "https://assets.coingecko.com/coins/images/29649/thumb/hunt.png?1696528585" 121 | }, 122 | { 123 | "chainId": 1116, 124 | "address": "0x000000000e1d682cc39abe9b32285fdea1255374", 125 | "name": "CORE ID", 126 | "symbol": "CID", 127 | "decimals": 18, 128 | "logoURI": "https://assets.coingecko.com/coins/images/29425/thumb/cid.png?1696528374" 129 | }, 130 | { 131 | "chainId": 1116, 132 | "address": "0x42020c7962ce072f5048120cedeb36c020062af4", 133 | "name": "Crust Exchange", 134 | "symbol": "CRUST", 135 | "decimals": 18, 136 | "logoURI": "https://assets.coingecko.com/coins/images/29537/thumb/photo_2023-03-21_00-12-46.jpg?1696528479" 137 | }, 138 | { 139 | "chainId": 1116, 140 | "address": "0x74c7b589209ce095a219f8bdf941de117656cbd6", 141 | "name": "Oil Token", 142 | "symbol": "OIL", 143 | "decimals": 18, 144 | "logoURI": "https://assets.coingecko.com/coins/images/30214/thumb/oil2.jpeg?1696529125" 145 | }, 146 | { 147 | "chainId": 1116, 148 | "address": " 0x5c44d3d2312aba4d5f2406a98bf374bc76455092", 149 | "name": "Moondogs", 150 | "symbol": "WOOF", 151 | "decimals": 18, 152 | "logoURI": "https://assets.coingecko.com/coins/images/29467/thumb/woof.png?1696528412" 153 | }, 154 | { 155 | "chainId": 1116, 156 | "address": "0xb4a452e975c006619559e30069804f873475f111", 157 | "name": "Call of Memes Yacht Club", 158 | "symbol": "COME", 159 | "decimals": 18, 160 | "logoURI": "https://assets.coingecko.com/coins/images/38042/thumb/COME.jpg?1716337137" 161 | }, 162 | { 163 | "chainId": 1116, 164 | "address": "0x204e2d49b7cda6d93301bcf667a2da28fb0e5780", 165 | "name": "Aquarius Loan", 166 | "symbol": "ARS", 167 | "decimals": 18, 168 | "logoURI": "https://assets.coingecko.com/coins/images/31445/thumb/logo-200x200.png?1696530259" 169 | }, 170 | { 171 | "chainId": 1116, 172 | "address": "0x42077e348702f13ea80ce6a6a38b8b60fbb37b5d", 173 | "name": "cDAO", 174 | "symbol": "CDAO", 175 | "decimals": 18, 176 | "logoURI": "https://assets.coingecko.com/coins/images/33125/thumb/LmgRwWp9_400x400.jpg?1700795014" 177 | }, 178 | { 179 | "chainId": 1116, 180 | "address": "0xe0d829e913618a7d2e2ba27a5fc8c274a8c525cf", 181 | "name": "Gemholic", 182 | "symbol": "GEMS", 183 | "decimals": 9, 184 | "logoURI": "https://assets.coingecko.com/coins/images/29542/thumb/photo_2023-03-16_19-06-39.jpg?1696528483" 185 | }, 186 | { 187 | "chainId": 1116, 188 | "address": "0xf3cabeb4a292996a451f8094e14f4806d22604d4", 189 | "name": "OreSwap", 190 | "symbol": "OST", 191 | "decimals": 18, 192 | "logoURI": "https://assets.coingecko.com/coins/images/37087/thumb/0xF3CABeb4A292996a451f8094E14F4806d22604d4.png?1715046928" 193 | }, 194 | { 195 | "chainId": 1116, 196 | "address": "0xb555a396446570ac2f4a62fffb64b4b0d43f5b74", 197 | "name": "CoreDaoSwap", 198 | "symbol": "CDAO", 199 | "decimals": 18, 200 | "logoURI": "https://assets.coingecko.com/coins/images/29582/thumb/200200wIR5I0em_400x400.jpg?1696528522" 201 | }, 202 | { 203 | "chainId": 1116, 204 | "address": "0xc5cd3f4029269fe9f2d2d154594d02f13c9a5214", 205 | "name": "Core Keeper", 206 | "symbol": "COKE", 207 | "decimals": 9, 208 | "logoURI": "https://assets.coingecko.com/coins/images/37848/thumb/corekeeper-2.png?1715758831" 209 | }, 210 | { 211 | "chainId": 1116, 212 | "address": "0xf6f46bd1f85ebf00c6d7490678ad020bc73969a7", 213 | "name": "PlayZap", 214 | "symbol": "PZP", 215 | "decimals": 18, 216 | "logoURI": "https://assets.coingecko.com/coins/images/24727/thumb/200X200-111.png?1696523890" 217 | }, 218 | { 219 | "chainId": 1116, 220 | "address": "0xb28b43209d9de61306172af0320f4f55e50e2f29", 221 | "name": "ASX Capital", 222 | "symbol": "ASX", 223 | "decimals": 18, 224 | "logoURI": "https://assets.coingecko.com/coins/images/34993/thumb/AXVII_007_500px_Black_Background.png?1706950900" 225 | }, 226 | { 227 | "chainId": 1116, 228 | "address": "0xcfd38184c30832917a2871695f91e5e61bbd41ff", 229 | "name": "Miidas", 230 | "symbol": "MIIDAS", 231 | "decimals": 6, 232 | "logoURI": "https://assets.coingecko.com/coins/images/26286/thumb/logo_miidas_round_500.png?1696525369" 233 | }, 234 | { 235 | "chainId": 1116, 236 | "address": "0x01065d48363713fa32c6ca51387e0803023c872c", 237 | "name": "AGUS", 238 | "symbol": "AGUS", 239 | "decimals": 18, 240 | "logoURI": "https://assets.coingecko.com/coins/images/37094/thumb/0x04513ADaAc27ed0324549E15f52c051e64F81DAB.png?1713271473" 241 | } 242 | ] -------------------------------------------------------------------------------- /src/utils/tokenlists/Mantle.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "chainId": 5000, 4 | "address": "0x201eba5cc46d216ce6dc03f6a759e8e766e956ae", 5 | "name": "Mantle Bridged USDT Mantle ", 6 | "symbol": "USDT", 7 | "decimals": 6, 8 | "logoURI": "https://assets.coingecko.com/coins/images/35069/thumb/logo.png?1707291114" 9 | }, 10 | { 11 | "chainId": 5000, 12 | "address": "0xd2b4c9b0d70e3da1fbdd98f469bd02e77e12fc79", 13 | "name": "Aurelius USD", 14 | "symbol": "AUSD", 15 | "decimals": 18, 16 | "logoURI": "https://assets.coingecko.com/coins/images/37318/thumb/aUSD.png?1713975421" 17 | }, 18 | { 19 | "chainId": 5000, 20 | "address": "0xc1e0c8c30f251a07a894609616580ad2ceb547f2", 21 | "name": "Cleopatra", 22 | "symbol": "CLEO", 23 | "decimals": 18, 24 | "logoURI": "https://assets.coingecko.com/coins/images/34150/thumb/CLEO_token.png?1704190970" 25 | }, 26 | { 27 | "chainId": 5000, 28 | "address": "0xab575258d37eaa5c8956efabe71f4ee8f6397cf3", 29 | "name": "Mantle USD", 30 | "symbol": "MUSD", 31 | "decimals": 18 32 | }, 33 | { 34 | "chainId": 5000, 35 | "address": "0xabbeed1d173541e0546b38b1c0394975be200000", 36 | "name": "Slash Vision Labs", 37 | "symbol": "SVL", 38 | "decimals": 18, 39 | "logoURI": "https://assets.coingecko.com/coins/images/37444/thumb/svl.jpeg?1714443652" 40 | }, 41 | { 42 | "chainId": 5000, 43 | "address": "0x09bc4e0d864854c6afb6eb9a9cdf58ac190d0df9", 44 | "name": "Mantle Bridged USDC Mantle ", 45 | "symbol": "USDC", 46 | "decimals": 6, 47 | "logoURI": "https://assets.coingecko.com/coins/images/35296/thumb/USDC_Icon.png?1708082083" 48 | }, 49 | { 50 | "chainId": 5000, 51 | "address": "0x029d924928888697d3f3d169018d9d98d9f0d6b4", 52 | "name": "Muito Finance", 53 | "symbol": "MUTO", 54 | "decimals": 18, 55 | "logoURI": "https://assets.coingecko.com/coins/images/35954/thumb/200_200-3.png?1710251972" 56 | }, 57 | { 58 | "chainId": 5000, 59 | "address": "0x6a3b0eb5b57c9a4f5772fc900dae427e65f8c1a5", 60 | "name": "Cashtree Token", 61 | "symbol": "CTT", 62 | "decimals": 18, 63 | "logoURI": "https://assets.coingecko.com/coins/images/31475/thumb/Coin-CTT%28Hi-Res%29.png?1696530287" 64 | }, 65 | { 66 | "chainId": 5000, 67 | "address": "0xdeaddeaddeaddeaddeaddeaddeaddeaddead1111", 68 | "name": "Wrapped Ether Mantle Bridge ", 69 | "symbol": "WETH", 70 | "decimals": 18, 71 | "logoURI": "https://assets.coingecko.com/coins/images/31013/thumb/wrapped-eth-mantle-bridge.png?1696529850" 72 | }, 73 | { 74 | "chainId": 5000, 75 | "address": "0x78c1b0c915c4faa5fffa6cabf0219da63d7f4cb8", 76 | "name": "Wrapped Mantle", 77 | "symbol": "WMNT", 78 | "decimals": 18, 79 | "logoURI": "https://assets.coingecko.com/coins/images/30983/thumb/mantle.jpeg?1696529822" 80 | }, 81 | { 82 | "chainId": 5000, 83 | "address": "0x51cfe5b1e764dc253f4c8c1f19a081ff4c3517ed", 84 | "name": "Mantle Inu", 85 | "symbol": "MINU", 86 | "decimals": 18, 87 | "logoURI": "https://assets.coingecko.com/coins/images/31014/thumb/mantle-inu.jpeg?1696529851" 88 | }, 89 | { 90 | "chainId": 5000, 91 | "address": "0x5a093a9c4f440c6b105f0af7f7c4f1fbe45567f9", 92 | "name": "Stratum Exchange", 93 | "symbol": "STRAT", 94 | "decimals": 18, 95 | "logoURI": "https://assets.coingecko.com/coins/images/33646/thumb/coin_strat.png?1702541330" 96 | }, 97 | { 98 | "chainId": 5000, 99 | "address": "0x779f4e5fb773e17bc8e809f4ef1abb140861159a", 100 | "name": "KTX Finance", 101 | "symbol": "KTC", 102 | "decimals": 18, 103 | "logoURI": "https://assets.coingecko.com/coins/images/30537/thumb/KTX.jpeg?1696529409" 104 | }, 105 | { 106 | "chainId": 5000, 107 | "address": "0x26a6b0dcdcfb981362afa56d581e4a7dba3be140", 108 | "name": "Puff The Dragon", 109 | "symbol": "PUFF", 110 | "decimals": 18, 111 | "logoURI": "https://assets.coingecko.com/coins/images/35950/thumb/photo_2024-03-11_10.18.27.jpeg?1710240090" 112 | }, 113 | { 114 | "chainId": 5000, 115 | "address": "0x4515a45337f461a11ff0fe8abf3c606ae5dc00c9", 116 | "name": "MOE", 117 | "symbol": "MOE", 118 | "decimals": 18, 119 | "logoURI": "https://assets.coingecko.com/coins/images/34422/thumb/MOE.png?1704821927" 120 | }, 121 | { 122 | "chainId": 5000, 123 | "address": "0x25356aeca4210ef7553140edb9b8026089e49396", 124 | "name": "Lendle", 125 | "symbol": "LEND", 126 | "decimals": 18, 127 | "logoURI": "https://assets.coingecko.com/coins/images/31588/thumb/lendle_logo_200_200_cg.png?1696530405" 128 | }, 129 | { 130 | "chainId": 5000, 131 | "address": "0x5ecdb76feda945dc71f7d9ce62dfe7eafefffab4", 132 | "name": "Minterest", 133 | "symbol": "MINTY", 134 | "decimals": 18, 135 | "logoURI": "https://assets.coingecko.com/coins/images/29455/thumb/MINTY.png?1706817774" 136 | }, 137 | { 138 | "chainId": 5000, 139 | "address": "0xdeaddeaddeaddeaddeaddeaddeaddeaddead0000", 140 | "name": "Mantle", 141 | "symbol": "MNT", 142 | "decimals": 18, 143 | "logoURI": "https://assets.coingecko.com/coins/images/30980/thumb/token-logo.png?1696529819" 144 | }, 145 | { 146 | "chainId": 5000, 147 | "address": "0x74ccbe53f77b08632ce0cb91d3a545bf6b8e0979", 148 | "name": "Fantom Bomb", 149 | "symbol": "BOMB", 150 | "decimals": 18, 151 | "logoURI": "https://assets.coingecko.com/coins/images/24109/thumb/logo-blue.png?1696523301" 152 | }, 153 | { 154 | "chainId": 5000, 155 | "address": "0x5be26527e817998a7206475496fde1e68957c5a6", 156 | "name": "Ondo US Dollar Yield", 157 | "symbol": "USDY", 158 | "decimals": 18, 159 | "logoURI": "https://assets.coingecko.com/coins/images/31700/thumb/usdy_%281%29.png?1696530524" 160 | }, 161 | { 162 | "chainId": 5000, 163 | "address": "0x70ba1a777b3f639c58fd5bc6a5c8780ddcef533b", 164 | "name": "Bark AI", 165 | "symbol": "BARK", 166 | "decimals": 18, 167 | "logoURI": "https://assets.coingecko.com/coins/images/34415/thumb/Logo_BARK.jpg?1704819436" 168 | }, 169 | { 170 | "chainId": 5000, 171 | "address": "0xcda86a272531e8640cd7f1a92c01839911b90bb0", 172 | "name": "Mantle Staked Ether", 173 | "symbol": "METH", 174 | "decimals": 18, 175 | "logoURI": "https://assets.coingecko.com/coins/images/33345/thumb/symbol_transparent_bg.png?1701697066" 176 | }, 177 | { 178 | "chainId": 5000, 179 | "address": "0x981bd9f77c8aafc14ebc86769503f86a3cc29af5", 180 | "name": "Tarot", 181 | "symbol": "TAROT", 182 | "decimals": 18, 183 | "logoURI": "https://assets.coingecko.com/coins/images/31800/thumb/TAROT.jpg?1696530615" 184 | }, 185 | { 186 | "chainId": 5000, 187 | "address": "0x05f41f99e6c72511f157674c6e43eda2a2e599a0", 188 | "name": "KUMA Protocol US KUMA Interest Bearing ", 189 | "symbol": "USK", 190 | "decimals": 18, 191 | "logoURI": "https://assets.coingecko.com/coins/images/33445/thumb/USK.png?1701888523" 192 | }, 193 | { 194 | "chainId": 5000, 195 | "address": "0xeb466342c4d449bc9f53a865d5cb90586f405215", 196 | "name": "Axelar Bridged USDC", 197 | "symbol": "AXLUSDC", 198 | "decimals": 6, 199 | "logoURI": "https://assets.coingecko.com/coins/images/26476/thumb/uausdc_D_3x.png?1696525548" 200 | }, 201 | { 202 | "chainId": 5000, 203 | "address": "0x872952d3c1caf944852c5adda65633f1ef218a26", 204 | "name": "Liquid Crypto", 205 | "symbol": "LQDX", 206 | "decimals": 18, 207 | "logoURI": "https://assets.coingecko.com/coins/images/33627/thumb/LQDX.png?1702533263" 208 | }, 209 | { 210 | "chainId": 5000, 211 | "address": "0x60d01ec2d5e98ac51c8b4cf84dfcce98d527c747", 212 | "name": "iZUMi Finance", 213 | "symbol": "IZI", 214 | "decimals": 18, 215 | "logoURI": "https://assets.coingecko.com/coins/images/21791/thumb/izumi-logo-symbol.png?1696521144" 216 | }, 217 | { 218 | "chainId": 5000, 219 | "address": "0x371c7ec6d8039ff7933a2aa28eb827ffe1f52f07", 220 | "name": "JOE", 221 | "symbol": "JOE", 222 | "decimals": 18, 223 | "logoURI": "https://assets.coingecko.com/coins/images/17569/thumb/JoeToken.png?1703732357" 224 | }, 225 | { 226 | "chainId": 5000, 227 | "address": "0x91824fc3573c5043837f6357b72f60014a710501", 228 | "name": "Aperture Finance", 229 | "symbol": "APTR", 230 | "decimals": 6, 231 | "logoURI": "https://assets.coingecko.com/coins/images/37940/thumb/icon_white.png?1715967425" 232 | }, 233 | { 234 | "chainId": 5000, 235 | "address": "0x458ed78eb972a369799fb278c0243b25e5242a83", 236 | "name": "Wrapped stETH", 237 | "symbol": "WSTETH", 238 | "decimals": 18, 239 | "logoURI": "https://assets.coingecko.com/coins/images/18834/thumb/wstETH.png?1696518295" 240 | }, 241 | { 242 | "chainId": 5000, 243 | "address": "0x527856315a4bcd2f428ea7fa05ea251f7e96a50a", 244 | "name": "CeDeFiAi", 245 | "symbol": "CDFI", 246 | "decimals": 18, 247 | "logoURI": "https://assets.coingecko.com/coins/images/36863/thumb/CeDeFiAi.png?1712643622" 248 | } 249 | ] -------------------------------------------------------------------------------- /src/utils/tokenlists/Celo.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "chainId": 42220, 4 | "address": "0x00400fcbf0816bebb94654259de7273f4a05c762", 5 | "name": "PoofCash", 6 | "symbol": "POOF", 7 | "decimals": 18, 8 | "logoURI": "https://assets.coingecko.com/coins/images/18076/thumb/android-icon-192x192_%282%29.png?1696517583" 9 | }, 10 | { 11 | "chainId": 42220, 12 | "address": "0x00be915b9dcf56a3cbe739d9b9c202ca692409ec", 13 | "name": "Ubeswap OLD ", 14 | "symbol": "UBE", 15 | "decimals": 18, 16 | "logoURI": "https://assets.coingecko.com/coins/images/15317/thumb/ubeswap.png?1696514966" 17 | }, 18 | { 19 | "chainId": 42220, 20 | "address": "0x71e26d0e519d14591b9de9a0fe9513a398101490", 21 | "name": "Ubeswap", 22 | "symbol": "UBE", 23 | "decimals": 18, 24 | "logoURI": "https://assets.coingecko.com/coins/images/36950/thumb/ubeswap.png?1712898123" 25 | }, 26 | { 27 | "chainId": 42220, 28 | "address": "0x7ff62f59e3e89ea34163ea1458eebcc81177cfb6", 29 | "name": "MENTO", 30 | "symbol": "MENTO", 31 | "decimals": 18, 32 | "logoURI": "https://assets.coingecko.com/coins/images/38958/thumb/MENTO_TOKEN_icon_200.png?1719691251" 33 | }, 34 | { 35 | "chainId": 42220, 36 | "address": "0x456a3d042c0dbd3db53d5489e98dfb038553b0d0", 37 | "name": "Celo Kenyan Shilling", 38 | "symbol": "CKES", 39 | "decimals": 18, 40 | "logoURI": "https://assets.coingecko.com/coins/images/38052/thumb/cKES_200x200.png?1716403445" 41 | }, 42 | { 43 | "chainId": 42220, 44 | "address": "0xd8763cba276a3738e6de85b4b3bf5fded6d6ca73", 45 | "name": "Celo Euro", 46 | "symbol": "CEUR", 47 | "decimals": 18, 48 | "logoURI": "https://assets.coingecko.com/coins/images/16756/thumb/CEUR.png?1696516329" 49 | }, 50 | { 51 | "chainId": 42220, 52 | "address": "0x9802d866fde4563d088a6619f7cef82c0b991a55", 53 | "name": "Moola interest bearing CREAL", 54 | "symbol": "MCREAL", 55 | "decimals": 18, 56 | "logoURI": "https://assets.coingecko.com/coins/images/26214/thumb/asset_mcREAL.png?1696525299" 57 | }, 58 | { 59 | "chainId": 42220, 60 | "address": "0xe273ad7ee11dcfaa87383ad5977ee1504ac07568", 61 | "name": "mcEUR", 62 | "symbol": "MCEUR", 63 | "decimals": 18, 64 | "logoURI": "https://assets.coingecko.com/coins/images/22345/thumb/1OY4GRnl_400x400.jpg?1696521688" 65 | }, 66 | { 67 | "chainId": 42220, 68 | "address": "0xe8537a3d056da446677b9e9d6c5db704eaab4787", 69 | "name": "Celo Real cREAL ", 70 | "symbol": "CREAL", 71 | "decimals": 18, 72 | "logoURI": "https://assets.coingecko.com/coins/images/27205/thumb/creal.png?1696526254" 73 | }, 74 | { 75 | "chainId": 42220, 76 | "address": "0x74c0c58b99b68cf16a717279ac2d056a34ba2bfe", 77 | "name": "ReSource Protocol", 78 | "symbol": "SOURCE", 79 | "decimals": 18, 80 | "logoURI": "https://assets.coingecko.com/coins/images/20740/thumb/source.png?1696520138" 81 | }, 82 | { 83 | "chainId": 42220, 84 | "address": "0x7d00cd74ff385c955ea3d79e47bf06bd7386387d", 85 | "name": "mCELO", 86 | "symbol": "MCELO", 87 | "decimals": 18, 88 | "logoURI": "https://assets.coingecko.com/coins/images/22346/thumb/1OY4GRnl_400x400_%281%29.jpg?1696521689" 89 | }, 90 | { 91 | "chainId": 42220, 92 | "address": "0x471ece3750da237f93b8e339c536989b8978a438", 93 | "name": "Celo", 94 | "symbol": "CELO", 95 | "decimals": 18, 96 | "logoURI": "https://assets.coingecko.com/coins/images/11090/thumb/InjXBNx9_400x400.jpg?1696511031" 97 | }, 98 | { 99 | "chainId": 42220, 100 | "address": "0x73a210637f6f6b7005512677ba6b3c96bb4aa44b", 101 | "name": "Mobius Money", 102 | "symbol": "MOBI", 103 | "decimals": 18, 104 | "logoURI": "https://assets.coingecko.com/coins/images/18467/thumb/MOBI-200.png?1696517953" 105 | }, 106 | { 107 | "chainId": 42220, 108 | "address": "0x27cd006548df7c8c8e9fdc4a67fa05c2e3ca5cf9", 109 | "name": "Plastiks", 110 | "symbol": "PLASTIK", 111 | "decimals": 9, 112 | "logoURI": "https://assets.coingecko.com/coins/images/21212/thumb/plastik.png?1696520587" 113 | }, 114 | { 115 | "chainId": 42220, 116 | "address": "0xe685d21b7b0fc7a248a6a8e03b8db22d013aa2ee", 117 | "name": "ImmortalDAO", 118 | "symbol": "IMMO", 119 | "decimals": 9, 120 | "logoURI": "https://assets.coingecko.com/coins/images/22189/thumb/immortal_logo.png?1696521533" 121 | }, 122 | { 123 | "chainId": 42220, 124 | "address": "0x2b9018ceb303d540bbf08de8e7de64fddd63396c", 125 | "name": "impactMarket", 126 | "symbol": "PACT", 127 | "decimals": 18, 128 | "logoURI": "https://assets.coingecko.com/coins/images/38805/thumb/impact.jpg?1718963785" 129 | }, 130 | { 131 | "chainId": 42220, 132 | "address": "0x46c9757c5497c5b1f2eb73ae79b6b67d119b0b58", 133 | "name": "impactMarket OLD ", 134 | "symbol": "PACT", 135 | "decimals": 18, 136 | "logoURI": "https://assets.coingecko.com/coins/images/21907/thumb/PACT_Token_Ticker_Blue_2x.png?1696521258" 137 | }, 138 | { 139 | "chainId": 42220, 140 | "address": "0x918146359264c492bd6934071c6bd31c854edbc3", 141 | "name": "Moola Celo Dollars", 142 | "symbol": "MCUSD", 143 | "decimals": 18, 144 | "logoURI": "https://assets.coingecko.com/coins/images/22380/thumb/McUSD_Blue_128x128_Square.jpg?1696521724" 145 | }, 146 | { 147 | "chainId": 42220, 148 | "address": "0xae978582de8ca83a53eb1d8f879ba854895f96b1", 149 | "name": "Tegisto", 150 | "symbol": "TGS", 151 | "decimals": 18, 152 | "logoURI": "https://assets.coingecko.com/coins/images/29576/thumb/tgs.png?1696528515" 153 | }, 154 | { 155 | "chainId": 42220, 156 | "address": "0x765de816845861e75a25fca122bb6898b8b1282a", 157 | "name": "Celo Dollar", 158 | "symbol": "CUSD", 159 | "decimals": 18, 160 | "logoURI": "https://assets.coingecko.com/coins/images/13161/thumb/icon-celo-dollar-color-1000-circle-cropped.png?1696512945" 161 | }, 162 | { 163 | "chainId": 42220, 164 | "address": "0x17700282592d6917f6a73d0bf8accf4d578c131e", 165 | "name": "Moola Market", 166 | "symbol": "MOO", 167 | "decimals": 18, 168 | "logoURI": "https://assets.coingecko.com/coins/images/17719/thumb/MOO_Logo_blue.png?1696517246" 169 | }, 170 | { 171 | "chainId": 42220, 172 | "address": "0x1d18d0386f51ab03e7e84e71bda1681eba865f1f", 173 | "name": "JumpToken", 174 | "symbol": "JMPT", 175 | "decimals": 18, 176 | "logoURI": "https://assets.coingecko.com/coins/images/22603/thumb/200x200.png?1696521919" 177 | }, 178 | { 179 | "chainId": 42220, 180 | "address": "0x62b8b11039fcfe5ab0c56e502b1c372a3d2a9c7a", 181 | "name": "GoodDollar", 182 | "symbol": "G", 183 | "decimals": 18, 184 | "logoURI": "https://assets.coingecko.com/coins/images/14782/thumb/G__Coin_%281%29.png?1696514451" 185 | }, 186 | { 187 | "chainId": 42220, 188 | "address": "0x37f750b7cc259a2f741af45294f6a16572cf5cad", 189 | "name": "Bridged USD Coin Wormhole Ethereum ", 190 | "symbol": "USDCET", 191 | "decimals": 6, 192 | "logoURI": "https://assets.coingecko.com/coins/images/23019/thumb/USDCet_wh_small.png?1696522314" 193 | }, 194 | { 195 | "chainId": 42220, 196 | "address": "0x50e85c754929840b58614f48e29c64bc78c58345", 197 | "name": "Biochar", 198 | "symbol": "CHAR", 199 | "decimals": 18, 200 | "logoURI": "https://assets.coingecko.com/coins/images/38094/thumb/char_logo_toucan.png?1716488453" 201 | }, 202 | { 203 | "chainId": 42220, 204 | "address": "0x4f604735c1cf31399c6e711d5962b2b3e0225ad3", 205 | "name": "Glo Dollar", 206 | "symbol": "USDGLO", 207 | "decimals": 18, 208 | "logoURI": "https://assets.coingecko.com/coins/images/29319/thumb/GLO_logo_pine_on_cyan_1_3.png?1716971065" 209 | }, 210 | { 211 | "chainId": 42220, 212 | "address": "0xd15ec721c2a896512ad29c671997dd68f9593226", 213 | "name": "Sushi", 214 | "symbol": "SUSHI", 215 | "decimals": 18, 216 | "logoURI": "https://assets.coingecko.com/coins/images/12271/thumb/512x512_Logo_no_chop.png?1696512101" 217 | }, 218 | { 219 | "chainId": 42220, 220 | "address": "0xeb466342c4d449bc9f53a865d5cb90586f405215", 221 | "name": "Axelar Bridged USDC", 222 | "symbol": "AXLUSDC", 223 | "decimals": 6, 224 | "logoURI": "https://assets.coingecko.com/coins/images/26476/thumb/uausdc_D_3x.png?1696525548" 225 | }, 226 | { 227 | "chainId": 42220, 228 | "address": "0xbdd31effb9e9f7509feaac5b4091b31645a47e4b", 229 | "name": "Truefeedback", 230 | "symbol": "TFBX", 231 | "decimals": 18, 232 | "logoURI": "https://assets.coingecko.com/coins/images/8842/thumb/5rd7a55q_400x400.png?1696508994" 233 | }, 234 | { 235 | "chainId": 42220, 236 | "address": "0x48065fbbe25f71c9282ddf5e1cd6d6a887483d5e", 237 | "name": "Tether", 238 | "symbol": "USDT", 239 | "decimals": 6, 240 | "logoURI": "https://assets.coingecko.com/coins/images/325/thumb/Tether.png?1696501661" 241 | }, 242 | { 243 | "chainId": 42220, 244 | "address": "0xceba9300f2b948710d2653dd7b07f33a8b32118c", 245 | "name": "USDC", 246 | "symbol": "USDC", 247 | "decimals": 6, 248 | "logoURI": "https://assets.coingecko.com/coins/images/6319/thumb/usdc.png?1696506694" 249 | }, 250 | { 251 | "chainId": 42220, 252 | "address": "0xc16b81af351ba9e64c1a069e3ab18c244a1e3049", 253 | "name": "EURA", 254 | "symbol": "EURA", 255 | "decimals": 18, 256 | "logoURI": "https://assets.coingecko.com/coins/images/19479/thumb/agEUR-4.png?1710726218" 257 | }, 258 | { 259 | "chainId": 42220, 260 | "address": "0xd252e98c5b6ea1e29a7e2789a9ec0493707a60b9", 261 | "name": "KROWN", 262 | "symbol": "KRW", 263 | "decimals": 18, 264 | "logoURI": "https://assets.coingecko.com/coins/images/16530/thumb/KRW_token_logo_200x200.png?1696516093" 265 | }, 266 | { 267 | "chainId": 42220, 268 | "address": "0x2def4285787d58a2f811af24755a8150622f4361", 269 | "name": "WETH", 270 | "symbol": "WETH", 271 | "decimals": 18, 272 | "logoURI": "https://assets.coingecko.com/coins/images/2518/thumb/weth.png?1696503332" 273 | }, 274 | { 275 | "chainId": 42220, 276 | "address": "0x9995cc8f20db5896943afc8ee0ba463259c931ed", 277 | "name": "Ethix", 278 | "symbol": "ETHIX", 279 | "decimals": 18, 280 | "logoURI": "https://assets.coingecko.com/coins/images/3031/thumb/ETHIX_icon_256x256-256.png?1696503766" 281 | }, 282 | { 283 | "chainId": 42220, 284 | "address": "0xd629eb00deced2a080b7ec630ef6ac117e614f1b", 285 | "name": "Wrapped Bitcoin", 286 | "symbol": "WBTC", 287 | "decimals": 18, 288 | "logoURI": "https://assets.coingecko.com/coins/images/7598/thumb/wrapped_bitcoin_wbtc.png?1696507857" 289 | }, 290 | { 291 | "chainId": 42220, 292 | "address": "0x6e512bfc33be36f2666754e996ff103ad1680cc9", 293 | "name": "Allbridge", 294 | "symbol": "ABR", 295 | "decimals": 18, 296 | "logoURI": "https://assets.coingecko.com/coins/images/18690/thumb/logo.png?1701737985" 297 | }, 298 | { 299 | "chainId": 42220, 300 | "address": "0xb9c8f0d3254007ee4b98970b94544e473cd610ec", 301 | "name": "MAI", 302 | "symbol": "MIMATIC", 303 | "decimals": 18, 304 | "logoURI": "https://assets.coingecko.com/coins/images/15264/thumb/mimatic-red.png?1696514916" 305 | } 306 | ] -------------------------------------------------------------------------------- /src/core/garbageCollector.ts: -------------------------------------------------------------------------------- 1 | import {formatEther, formatUnits, Wallet} from 'ethers' 2 | import {DEV, GarbageCollectorConfig, sleepBetweenActions} from '../../config' 3 | import {c, defaultSleep, RandomHelpers, retry} from '../utils/helpers' 4 | import {ChainName, NotChainName, TokenlistResp} from '../utils/types' 5 | import {getBalance, Multicall, unwrap} from '../periphery/web3Client' 6 | import {chains, networkNameToCoingeckoQueryString} from '../utils/constants' 7 | import {checkConnection, getNativeCoinPrice, getProvider, getTokenPrices} from '../periphery/utils' 8 | import {OdosAggregator} from '../periphery/odosAggregator' 9 | import {Sushiswap} from '../periphery/sushiswap' 10 | import {readFileSync, writeFileSync} from 'fs' 11 | 12 | class GarbageCollector extends GarbageCollectorConfig { 13 | signer: Wallet 14 | proxies: string[] | undefined 15 | tokenlists: { 16 | [key: ChainName | string]: { 17 | chainId: number 18 | address: string 19 | name: string 20 | symbol: string 21 | decimals: number 22 | logoURI: string 23 | }[] 24 | } = {} 25 | nonzeroTokens: {[key: ChainName | string]: {address: string; name: string; symbol: string; decimals: bigint; balance: bigint}[]} = {} 26 | constructor(signer: Wallet, proxies?: string[]) { 27 | super() 28 | this.signer = signer 29 | this.proxies = proxies 30 | 31 | this.tokensToIgnore = this.tokensToIgnore.map((elem) => elem.toLowerCase().trim()) 32 | this.chainsToExclude = this.chainsToExclude.map((elem) => elem.toLowerCase().trim()) as (ChainName | NotChainName)[] 33 | } 34 | connect(signer: Wallet) { 35 | this.signer = signer 36 | this.nonzeroTokens = {} 37 | } 38 | async #fetchTokenList(networkName: ChainName) { 39 | if (!networkNameToCoingeckoQueryString[networkName]) return [] 40 | if (this.tokenlists[networkName] != undefined) return this.tokenlists[networkName] 41 | try { 42 | let res: TokenlistResp = await retry( 43 | async () => { 44 | let url = `https://tokens.coingecko.com/${networkNameToCoingeckoQueryString[networkName]}/all.json` 45 | let resp = await fetch(url, {method: 'GET'}) 46 | let body = await resp.json() 47 | return body 48 | }, 49 | {maxRetryCount: 5, retryInterval: 5, needLog: false} 50 | ) 51 | let tokens = res.tokens 52 | if (tokens.length > 0) { 53 | try { 54 | writeFileSync(`src/utils/tokenlists/${networkName}.json`, JSON.stringify(tokens, null, 4)) 55 | } catch (e: any) { 56 | console.log(c.red(`could not save tokenlist for ${networkName}`)) 57 | console.log(e) 58 | } 59 | } else { 60 | try { 61 | tokens = JSON.parse(readFileSync(`../utils/tokenlists/${networkName}.json`, 'utf-8')) 62 | } catch (e: any) { 63 | console.log(c.red(`could not read tokenlist from ${networkName}, this chain will be skipped`)) 64 | tokens = [] 65 | } 66 | } 67 | if (DEV) { 68 | console.log(`${networkName}: fetched tokenList with ${tokens.length} tokens`) 69 | } 70 | this.tokenlists[networkName] = tokens 71 | return res.tokens 72 | } catch (e: any) { 73 | try { 74 | return JSON.parse(readFileSync(`../utils/tokenlists/${networkName}.json`, 'utf-8')) 75 | } catch (e: any) { 76 | console.log(c.red(`could not read tokenlist from ${networkName}, this chain will be skipped`)) 77 | } 78 | if (DEV) { 79 | console.log(e) 80 | console.log(c.red(`fetchTokenList ${networkName}: ERROR`)) 81 | } 82 | return [] 83 | } 84 | } 85 | async getNonZeroTokensAndSwap() { 86 | let networks = RandomHelpers.shuffleArray(Object.keys(chains)) 87 | for (let i = 0; i < networks.length; i++) { 88 | let networkName = networks[i] as ChainName 89 | if (this.chainsToExclude.length > 0 && this.chainsToExclude[0].includes('!')) { 90 | if (!networkName.toLowerCase().includes(this.chainsToExclude[0].split('!')[1])) { 91 | continue 92 | } 93 | } 94 | if (this.chainsToExclude.includes(networkName.toLowerCase() as ChainName | NotChainName)) { 95 | continue 96 | } 97 | let tokenlist = await this.#fetchTokenList(networkName) 98 | if (tokenlist.length == 0) { 99 | continue 100 | } 101 | await this.getNonZeroTokensForChain(networkName) 102 | await this.swapTokensToNativeForChain(networkName) 103 | } 104 | } 105 | // unused yet 106 | async getNonZeroTokens() { 107 | for (let i = 0; i < Object.keys(chains).length; i++) { 108 | let networkName = Object.keys(chains)[i] as ChainName 109 | if (this.chainsToExclude.length > 0 && this.chainsToExclude[0].includes('!')) { 110 | if (!networkName.toLowerCase().includes(this.chainsToExclude[0].split('!')[1])) { 111 | continue 112 | } 113 | } 114 | if (this.chainsToExclude.includes(networkName.toLowerCase() as ChainName | NotChainName)) { 115 | continue 116 | } 117 | let tokenlist = await this.#fetchTokenList(networkName) 118 | if (tokenlist.length == 0) { 119 | continue 120 | } 121 | await this.getNonZeroTokensForChain(networkName) 122 | } 123 | } 124 | // unused yet 125 | async swapTokensToNative() { 126 | for (let i = 0; i < Object.keys(chains).length; i++) { 127 | let networkName = Object.keys(chains)[i] as ChainName 128 | if (this.chainsToExclude.length > 0 && this.chainsToExclude[0].includes('!')) { 129 | if (!networkName.includes(this.chainsToExclude[0].split('!')[1])) { 130 | continue 131 | } 132 | } 133 | if (this.chainsToExclude.includes(networkName)) { 134 | continue 135 | } 136 | await this.swapTokensToNativeForChain(networkName) 137 | } 138 | } 139 | async getNonZeroTokensForChain(networkName: ChainName) { 140 | let nonzeroTokens = await Multicall.setNetwork(networkName).callBalance( 141 | this.signer.address, 142 | this.tokenlists[networkName].map((elem) => elem.address) 143 | ) 144 | let nonzeroTokenData = await Multicall.getTokenInfo(nonzeroTokens.map((elem) => elem.token)) 145 | let nonzeroTokenList: {address: string; name: string; symbol: string; decimals: bigint; balance: bigint}[] = [] 146 | // could be done better lol 147 | for (let i = 0; i < nonzeroTokenData.length; i++) { 148 | nonzeroTokenList.push({ 149 | address: nonzeroTokenData[i].address, 150 | name: nonzeroTokenData[i].name, 151 | symbol: nonzeroTokenData[i].symbol, 152 | decimals: nonzeroTokenData[i].decimals, 153 | balance: nonzeroTokens[i].balance 154 | }) 155 | } 156 | let prices: {[key: string]: number} = {} 157 | if (nonzeroTokenData.length > 0) { 158 | prices = await getTokenPrices( 159 | networkName, 160 | nonzeroTokenData.map((elem) => elem.address) 161 | ) 162 | } 163 | let nativeBalance: number 164 | let nativePrice: number 165 | try { 166 | // i hecking love javascript 167 | // prettier-ignore 168 | nativeBalance = parseFloat(parseFloat(formatEther(await getBalance(this.signer.connect(getProvider(networkName)), this.signer.address))).toFixed(5)) 169 | nativePrice = await getNativeCoinPrice(networkName) 170 | } catch (e: any) { 171 | nativeBalance = 0 172 | nativePrice = 0 173 | } 174 | console.log(c.cyan.bold(networkName), c.yellow(`found ${nonzeroTokenList.length} nonzero tokens`)) 175 | { 176 | let nameOffset = 35 - 'Native'.length < 0 ? 0 : 35 - 'Native'.length 177 | let nameText = 'Native' + ' '.repeat(nameOffset) 178 | let symbolOffset = 8 - chains[networkName].currency.name.length < 0 ? 0 : 8 - chains[networkName].currency.name.length 179 | let symbolText = `(${chains[networkName].currency.name}):${' '.repeat(symbolOffset)}` 180 | let amount = nativeBalance > 99_999 ? 'a lot' : nativeBalance.toFixed(3) 181 | if (nativeBalance == 0) { 182 | amount = '???' 183 | } 184 | let price: string 185 | let priceLength = 3 186 | if (nativeBalance > 0 && nativePrice > 0) { 187 | price = (nativeBalance * nativePrice).toFixed(2) 188 | priceLength = price.split('.')[0].length + price.split('.')[1].length + 1 // +1 since dot is also there 189 | } else { 190 | price = '???' 191 | } 192 | let amountAndPriceOffset = 15 - amount.length - priceLength < 0 ? 0 : 15 - amount.length - priceLength 193 | let priceText = ' '.repeat(amountAndPriceOffset) + price 194 | console.log(c.blue.bold(` ${nameText} ${symbolText} ${amount} ${priceText} USD`)) 195 | } 196 | let networkValue = 0 197 | for (let nonzeroToken of nonzeroTokenList) { 198 | let nameOffset = 35 - nonzeroToken.name.length < 0 ? 0 : 35 - nonzeroToken.name.length 199 | let nameText = nonzeroToken.name + ' '.repeat(nameOffset) 200 | let symbolOffset = 8 - nonzeroToken.symbol.length < 0 ? 0 : 8 - nonzeroToken.symbol.length 201 | let symbolText = `(${c.yellow(nonzeroToken.symbol)}):${' '.repeat(symbolOffset)}` 202 | let amount = 203 | parseFloat(formatUnits(nonzeroToken.balance, nonzeroToken.decimals)) > 99_999 204 | ? 'a lot' 205 | : parseFloat(formatUnits(nonzeroToken.balance, nonzeroToken.decimals)).toFixed(1) 206 | let price = (parseFloat(formatUnits(nonzeroToken.balance, nonzeroToken.decimals)) * (prices[nonzeroToken.address] ?? 0)).toFixed(2) 207 | let amountAndPriceOffset = 15 - amount.length - price.length < 0 ? 0 : 15 - amount.length - price.length 208 | let priceText = ' '.repeat(amountAndPriceOffset) + c.green(price) 209 | console.log(` ${nameText} ${symbolText} ${amount} ${priceText} USD`) 210 | networkValue += parseFloat(price) 211 | } 212 | let networkValueText = `network value: ${networkValue.toFixed(2)} USD` 213 | // 71 -- whole paste's length built above 214 | let networkValueOffset = 71 - networkValueText.length < 0 ? 0 : 71 - networkValueText.length 215 | console.log(c.magenta.bold(' '.repeat(networkValueOffset) + networkValueText)) 216 | this.nonzeroTokens[networkName] = nonzeroTokenList 217 | } 218 | async swapTokensToNativeForChain(networkName: ChainName) { 219 | let shuffledTokens = RandomHelpers.shuffleArray(this.nonzeroTokens[networkName]) 220 | for (let token of shuffledTokens) { 221 | let nativeToken = { 222 | address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', 223 | name: chains[networkName].currency.name, 224 | symbol: chains[networkName].currency.name, 225 | decimals: 18n 226 | } 227 | if (this.tokensToIgnore.includes(token.address.toLowerCase())) { 228 | let symbolOffset = 8 - token.symbol.length < 0 ? 0 : 8 - token.symbol.length 229 | console.log(`${c.blue(token.symbol) + ' '.repeat(symbolOffset)} is ignored`) 230 | continue 231 | } 232 | if (token.address.toLowerCase() == chains[networkName].tokens.WNATIVE.address.toLowerCase()) { 233 | // CELO does not have a wTOKEN. WTF? 234 | if (networkName == 'Celo') { 235 | continue 236 | } 237 | // need to unwrap in this case, not swap 238 | try { 239 | if (token.balance < 10000000000000n) { 240 | continue 241 | } 242 | let unwrapHash = await unwrap(this.signer.connect(getProvider(networkName)), token.address, token.balance, {limit: 1, price: 1.1}) 243 | console.log(c.green(`unwrapped ${formatEther(token.balance)} ${token.symbol} ${chains[networkName].explorer + unwrapHash}`)) 244 | } catch (e: any) { 245 | console.log(c.red(`Unwrap failed. Reason: ${e?.message ?? 'undefined'}`)) 246 | } 247 | continue 248 | } 249 | let proxy: string | undefined 250 | if (this.proxies != undefined) { 251 | if (this.proxies.length > 0) { 252 | this.proxies = RandomHelpers.shuffleArray(this.proxies) 253 | for (let testProxy of this.proxies) { 254 | let isProxyGood = await checkConnection(testProxy) 255 | if (isProxyGood) { 256 | proxy = testProxy 257 | break 258 | } 259 | await defaultSleep(0.1, false) 260 | } 261 | if (proxy == undefined) { 262 | console.log(c.red('No valid proxy found, trying to go "as is"')) 263 | } 264 | } else { 265 | console.log(c.magenta.bold('Proxy not set, trying to go "as is"')) 266 | proxy = undefined 267 | } 268 | } else { 269 | console.log(c.magenta.bold('Proxy not set, trying to go "as is"')) 270 | proxy = undefined 271 | } 272 | const odos = new OdosAggregator(this.signer.connect(getProvider(networkName)), networkName, proxy) 273 | let odosSuccess = await odos.swap(token, nativeToken, token.balance) 274 | let sushiSuccess 275 | if (!odosSuccess && this.trySushi) { 276 | let sushi = new Sushiswap(this.signer.connect(getProvider(networkName)), networkName) 277 | sushiSuccess = await sushi.swap(token, nativeToken, token.balance) 278 | } 279 | if (odosSuccess || sushiSuccess) { 280 | await defaultSleep(RandomHelpers.getRandomNumber(sleepBetweenActions)) 281 | } else { 282 | await defaultSleep(1, false) 283 | } 284 | } 285 | } 286 | } 287 | 288 | export {GarbageCollector} 289 | -------------------------------------------------------------------------------- /src/utils/abi/UniswapV2Router02.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | {"internalType": "address", "name": "_factory", "type": "address"}, 5 | {"internalType": "address", "name": "_WETH", "type": "address"} 6 | ], 7 | "stateMutability": "nonpayable", 8 | "type": "constructor" 9 | }, 10 | { 11 | "inputs": [], 12 | "name": "WETH", 13 | "outputs": [{"internalType": "address", "name": "", "type": "address"}], 14 | "stateMutability": "view", 15 | "type": "function" 16 | }, 17 | { 18 | "inputs": [ 19 | {"internalType": "address", "name": "tokenA", "type": "address"}, 20 | {"internalType": "address", "name": "tokenB", "type": "address"}, 21 | {"internalType": "uint256", "name": "amountADesired", "type": "uint256"}, 22 | {"internalType": "uint256", "name": "amountBDesired", "type": "uint256"}, 23 | {"internalType": "uint256", "name": "amountAMin", "type": "uint256"}, 24 | {"internalType": "uint256", "name": "amountBMin", "type": "uint256"}, 25 | {"internalType": "address", "name": "to", "type": "address"}, 26 | {"internalType": "uint256", "name": "deadline", "type": "uint256"} 27 | ], 28 | "name": "addLiquidity", 29 | "outputs": [ 30 | {"internalType": "uint256", "name": "amountA", "type": "uint256"}, 31 | {"internalType": "uint256", "name": "amountB", "type": "uint256"}, 32 | {"internalType": "uint256", "name": "liquidity", "type": "uint256"} 33 | ], 34 | "stateMutability": "nonpayable", 35 | "type": "function" 36 | }, 37 | { 38 | "inputs": [ 39 | {"internalType": "address", "name": "token", "type": "address"}, 40 | {"internalType": "uint256", "name": "amountTokenDesired", "type": "uint256"}, 41 | {"internalType": "uint256", "name": "amountTokenMin", "type": "uint256"}, 42 | {"internalType": "uint256", "name": "amountETHMin", "type": "uint256"}, 43 | {"internalType": "address", "name": "to", "type": "address"}, 44 | {"internalType": "uint256", "name": "deadline", "type": "uint256"} 45 | ], 46 | "name": "addLiquidityETH", 47 | "outputs": [ 48 | {"internalType": "uint256", "name": "amountToken", "type": "uint256"}, 49 | {"internalType": "uint256", "name": "amountETH", "type": "uint256"}, 50 | {"internalType": "uint256", "name": "liquidity", "type": "uint256"} 51 | ], 52 | "stateMutability": "payable", 53 | "type": "function" 54 | }, 55 | { 56 | "inputs": [], 57 | "name": "factory", 58 | "outputs": [{"internalType": "address", "name": "", "type": "address"}], 59 | "stateMutability": "view", 60 | "type": "function" 61 | }, 62 | { 63 | "inputs": [ 64 | {"internalType": "uint256", "name": "amountOut", "type": "uint256"}, 65 | {"internalType": "uint256", "name": "reserveIn", "type": "uint256"}, 66 | {"internalType": "uint256", "name": "reserveOut", "type": "uint256"} 67 | ], 68 | "name": "getAmountIn", 69 | "outputs": [{"internalType": "uint256", "name": "amountIn", "type": "uint256"}], 70 | "stateMutability": "pure", 71 | "type": "function" 72 | }, 73 | { 74 | "inputs": [ 75 | {"internalType": "uint256", "name": "amountIn", "type": "uint256"}, 76 | {"internalType": "uint256", "name": "reserveIn", "type": "uint256"}, 77 | {"internalType": "uint256", "name": "reserveOut", "type": "uint256"} 78 | ], 79 | "name": "getAmountOut", 80 | "outputs": [{"internalType": "uint256", "name": "amountOut", "type": "uint256"}], 81 | "stateMutability": "pure", 82 | "type": "function" 83 | }, 84 | { 85 | "inputs": [ 86 | {"internalType": "uint256", "name": "amountOut", "type": "uint256"}, 87 | {"internalType": "address[]", "name": "path", "type": "address[]"} 88 | ], 89 | "name": "getAmountsIn", 90 | "outputs": [{"internalType": "uint256[]", "name": "amounts", "type": "uint256[]"}], 91 | "stateMutability": "view", 92 | "type": "function" 93 | }, 94 | { 95 | "inputs": [ 96 | {"internalType": "uint256", "name": "amountIn", "type": "uint256"}, 97 | {"internalType": "address[]", "name": "path", "type": "address[]"} 98 | ], 99 | "name": "getAmountsOut", 100 | "outputs": [{"internalType": "uint256[]", "name": "amounts", "type": "uint256[]"}], 101 | "stateMutability": "view", 102 | "type": "function" 103 | }, 104 | { 105 | "inputs": [ 106 | {"internalType": "uint256", "name": "amountA", "type": "uint256"}, 107 | {"internalType": "uint256", "name": "reserveA", "type": "uint256"}, 108 | {"internalType": "uint256", "name": "reserveB", "type": "uint256"} 109 | ], 110 | "name": "quote", 111 | "outputs": [{"internalType": "uint256", "name": "amountB", "type": "uint256"}], 112 | "stateMutability": "pure", 113 | "type": "function" 114 | }, 115 | { 116 | "inputs": [ 117 | {"internalType": "address", "name": "tokenA", "type": "address"}, 118 | {"internalType": "address", "name": "tokenB", "type": "address"}, 119 | {"internalType": "uint256", "name": "liquidity", "type": "uint256"}, 120 | {"internalType": "uint256", "name": "amountAMin", "type": "uint256"}, 121 | {"internalType": "uint256", "name": "amountBMin", "type": "uint256"}, 122 | {"internalType": "address", "name": "to", "type": "address"}, 123 | {"internalType": "uint256", "name": "deadline", "type": "uint256"} 124 | ], 125 | "name": "removeLiquidity", 126 | "outputs": [ 127 | {"internalType": "uint256", "name": "amountA", "type": "uint256"}, 128 | {"internalType": "uint256", "name": "amountB", "type": "uint256"} 129 | ], 130 | "stateMutability": "nonpayable", 131 | "type": "function" 132 | }, 133 | { 134 | "inputs": [ 135 | {"internalType": "address", "name": "token", "type": "address"}, 136 | {"internalType": "uint256", "name": "liquidity", "type": "uint256"}, 137 | {"internalType": "uint256", "name": "amountTokenMin", "type": "uint256"}, 138 | {"internalType": "uint256", "name": "amountETHMin", "type": "uint256"}, 139 | {"internalType": "address", "name": "to", "type": "address"}, 140 | {"internalType": "uint256", "name": "deadline", "type": "uint256"} 141 | ], 142 | "name": "removeLiquidityETH", 143 | "outputs": [ 144 | {"internalType": "uint256", "name": "amountToken", "type": "uint256"}, 145 | {"internalType": "uint256", "name": "amountETH", "type": "uint256"} 146 | ], 147 | "stateMutability": "nonpayable", 148 | "type": "function" 149 | }, 150 | { 151 | "inputs": [ 152 | {"internalType": "address", "name": "token", "type": "address"}, 153 | {"internalType": "uint256", "name": "liquidity", "type": "uint256"}, 154 | {"internalType": "uint256", "name": "amountTokenMin", "type": "uint256"}, 155 | {"internalType": "uint256", "name": "amountETHMin", "type": "uint256"}, 156 | {"internalType": "address", "name": "to", "type": "address"}, 157 | {"internalType": "uint256", "name": "deadline", "type": "uint256"} 158 | ], 159 | "name": "removeLiquidityETHSupportingFeeOnTransferTokens", 160 | "outputs": [{"internalType": "uint256", "name": "amountETH", "type": "uint256"}], 161 | "stateMutability": "nonpayable", 162 | "type": "function" 163 | }, 164 | { 165 | "inputs": [ 166 | {"internalType": "address", "name": "token", "type": "address"}, 167 | {"internalType": "uint256", "name": "liquidity", "type": "uint256"}, 168 | {"internalType": "uint256", "name": "amountTokenMin", "type": "uint256"}, 169 | {"internalType": "uint256", "name": "amountETHMin", "type": "uint256"}, 170 | {"internalType": "address", "name": "to", "type": "address"}, 171 | {"internalType": "uint256", "name": "deadline", "type": "uint256"}, 172 | {"internalType": "bool", "name": "approveMax", "type": "bool"}, 173 | {"internalType": "uint8", "name": "v", "type": "uint8"}, 174 | {"internalType": "bytes32", "name": "r", "type": "bytes32"}, 175 | {"internalType": "bytes32", "name": "s", "type": "bytes32"} 176 | ], 177 | "name": "removeLiquidityETHWithPermit", 178 | "outputs": [ 179 | {"internalType": "uint256", "name": "amountToken", "type": "uint256"}, 180 | {"internalType": "uint256", "name": "amountETH", "type": "uint256"} 181 | ], 182 | "stateMutability": "nonpayable", 183 | "type": "function" 184 | }, 185 | { 186 | "inputs": [ 187 | {"internalType": "address", "name": "token", "type": "address"}, 188 | {"internalType": "uint256", "name": "liquidity", "type": "uint256"}, 189 | {"internalType": "uint256", "name": "amountTokenMin", "type": "uint256"}, 190 | {"internalType": "uint256", "name": "amountETHMin", "type": "uint256"}, 191 | {"internalType": "address", "name": "to", "type": "address"}, 192 | {"internalType": "uint256", "name": "deadline", "type": "uint256"}, 193 | {"internalType": "bool", "name": "approveMax", "type": "bool"}, 194 | {"internalType": "uint8", "name": "v", "type": "uint8"}, 195 | {"internalType": "bytes32", "name": "r", "type": "bytes32"}, 196 | {"internalType": "bytes32", "name": "s", "type": "bytes32"} 197 | ], 198 | "name": "removeLiquidityETHWithPermitSupportingFeeOnTransferTokens", 199 | "outputs": [{"internalType": "uint256", "name": "amountETH", "type": "uint256"}], 200 | "stateMutability": "nonpayable", 201 | "type": "function" 202 | }, 203 | { 204 | "inputs": [ 205 | {"internalType": "address", "name": "tokenA", "type": "address"}, 206 | {"internalType": "address", "name": "tokenB", "type": "address"}, 207 | {"internalType": "uint256", "name": "liquidity", "type": "uint256"}, 208 | {"internalType": "uint256", "name": "amountAMin", "type": "uint256"}, 209 | {"internalType": "uint256", "name": "amountBMin", "type": "uint256"}, 210 | {"internalType": "address", "name": "to", "type": "address"}, 211 | {"internalType": "uint256", "name": "deadline", "type": "uint256"}, 212 | {"internalType": "bool", "name": "approveMax", "type": "bool"}, 213 | {"internalType": "uint8", "name": "v", "type": "uint8"}, 214 | {"internalType": "bytes32", "name": "r", "type": "bytes32"}, 215 | {"internalType": "bytes32", "name": "s", "type": "bytes32"} 216 | ], 217 | "name": "removeLiquidityWithPermit", 218 | "outputs": [ 219 | {"internalType": "uint256", "name": "amountA", "type": "uint256"}, 220 | {"internalType": "uint256", "name": "amountB", "type": "uint256"} 221 | ], 222 | "stateMutability": "nonpayable", 223 | "type": "function" 224 | }, 225 | { 226 | "inputs": [ 227 | {"internalType": "uint256", "name": "amountOut", "type": "uint256"}, 228 | {"internalType": "address[]", "name": "path", "type": "address[]"}, 229 | {"internalType": "address", "name": "to", "type": "address"}, 230 | {"internalType": "uint256", "name": "deadline", "type": "uint256"} 231 | ], 232 | "name": "swapETHForExactTokens", 233 | "outputs": [{"internalType": "uint256[]", "name": "amounts", "type": "uint256[]"}], 234 | "stateMutability": "payable", 235 | "type": "function" 236 | }, 237 | { 238 | "inputs": [ 239 | {"internalType": "uint256", "name": "amountOutMin", "type": "uint256"}, 240 | {"internalType": "address[]", "name": "path", "type": "address[]"}, 241 | {"internalType": "address", "name": "to", "type": "address"}, 242 | {"internalType": "uint256", "name": "deadline", "type": "uint256"} 243 | ], 244 | "name": "swapExactETHForTokens", 245 | "outputs": [{"internalType": "uint256[]", "name": "amounts", "type": "uint256[]"}], 246 | "stateMutability": "payable", 247 | "type": "function" 248 | }, 249 | { 250 | "inputs": [ 251 | {"internalType": "uint256", "name": "amountOutMin", "type": "uint256"}, 252 | {"internalType": "address[]", "name": "path", "type": "address[]"}, 253 | {"internalType": "address", "name": "to", "type": "address"}, 254 | {"internalType": "uint256", "name": "deadline", "type": "uint256"} 255 | ], 256 | "name": "swapExactETHForTokensSupportingFeeOnTransferTokens", 257 | "outputs": [], 258 | "stateMutability": "payable", 259 | "type": "function" 260 | }, 261 | { 262 | "inputs": [ 263 | {"internalType": "uint256", "name": "amountIn", "type": "uint256"}, 264 | {"internalType": "uint256", "name": "amountOutMin", "type": "uint256"}, 265 | {"internalType": "address[]", "name": "path", "type": "address[]"}, 266 | {"internalType": "address", "name": "to", "type": "address"}, 267 | {"internalType": "uint256", "name": "deadline", "type": "uint256"} 268 | ], 269 | "name": "swapExactTokensForETH", 270 | "outputs": [{"internalType": "uint256[]", "name": "amounts", "type": "uint256[]"}], 271 | "stateMutability": "nonpayable", 272 | "type": "function" 273 | }, 274 | { 275 | "inputs": [ 276 | {"internalType": "uint256", "name": "amountIn", "type": "uint256"}, 277 | {"internalType": "uint256", "name": "amountOutMin", "type": "uint256"}, 278 | {"internalType": "address[]", "name": "path", "type": "address[]"}, 279 | {"internalType": "address", "name": "to", "type": "address"}, 280 | {"internalType": "uint256", "name": "deadline", "type": "uint256"} 281 | ], 282 | "name": "swapExactTokensForETHSupportingFeeOnTransferTokens", 283 | "outputs": [], 284 | "stateMutability": "nonpayable", 285 | "type": "function" 286 | }, 287 | { 288 | "inputs": [ 289 | {"internalType": "uint256", "name": "amountIn", "type": "uint256"}, 290 | {"internalType": "uint256", "name": "amountOutMin", "type": "uint256"}, 291 | {"internalType": "address[]", "name": "path", "type": "address[]"}, 292 | {"internalType": "address", "name": "to", "type": "address"}, 293 | {"internalType": "uint256", "name": "deadline", "type": "uint256"} 294 | ], 295 | "name": "swapExactTokensForTokens", 296 | "outputs": [{"internalType": "uint256[]", "name": "amounts", "type": "uint256[]"}], 297 | "stateMutability": "nonpayable", 298 | "type": "function" 299 | }, 300 | { 301 | "inputs": [ 302 | {"internalType": "uint256", "name": "amountIn", "type": "uint256"}, 303 | {"internalType": "uint256", "name": "amountOutMin", "type": "uint256"}, 304 | {"internalType": "address[]", "name": "path", "type": "address[]"}, 305 | {"internalType": "address", "name": "to", "type": "address"}, 306 | {"internalType": "uint256", "name": "deadline", "type": "uint256"} 307 | ], 308 | "name": "swapExactTokensForTokensSupportingFeeOnTransferTokens", 309 | "outputs": [], 310 | "stateMutability": "nonpayable", 311 | "type": "function" 312 | }, 313 | { 314 | "inputs": [ 315 | {"internalType": "uint256", "name": "amountOut", "type": "uint256"}, 316 | {"internalType": "uint256", "name": "amountInMax", "type": "uint256"}, 317 | {"internalType": "address[]", "name": "path", "type": "address[]"}, 318 | {"internalType": "address", "name": "to", "type": "address"}, 319 | {"internalType": "uint256", "name": "deadline", "type": "uint256"} 320 | ], 321 | "name": "swapTokensForExactETH", 322 | "outputs": [{"internalType": "uint256[]", "name": "amounts", "type": "uint256[]"}], 323 | "stateMutability": "nonpayable", 324 | "type": "function" 325 | }, 326 | { 327 | "inputs": [ 328 | {"internalType": "uint256", "name": "amountOut", "type": "uint256"}, 329 | {"internalType": "uint256", "name": "amountInMax", "type": "uint256"}, 330 | {"internalType": "address[]", "name": "path", "type": "address[]"}, 331 | {"internalType": "address", "name": "to", "type": "address"}, 332 | {"internalType": "uint256", "name": "deadline", "type": "uint256"} 333 | ], 334 | "name": "swapTokensForExactTokens", 335 | "outputs": [{"internalType": "uint256[]", "name": "amounts", "type": "uint256[]"}], 336 | "stateMutability": "nonpayable", 337 | "type": "function" 338 | }, 339 | {"stateMutability": "payable", "type": "receive"} 340 | ] 341 | -------------------------------------------------------------------------------- /src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | import {Chain, ChainName} from './types' 2 | 3 | export const timeout = 5 4 | 5 | export const scenarios = [ 6 | { 7 | name: `Balance cheker`, 8 | value: 'Balance cheker' 9 | }, 10 | { 11 | name: `Garbage collector`, 12 | value: 'Garbage collector' 13 | }, 14 | { 15 | name: `Garbage collector & native sender`, 16 | value: 'Garbage collector & native sender' 17 | }, 18 | { 19 | name: `Relay bridge`, 20 | value: 'Relay bridge' 21 | } 22 | ] 23 | // prettier-ignore 24 | export const sushiswapV2Routers: {[key in ChainName]: string} = { 25 | Ethereum: '0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F', 26 | Arbitrum: '0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506', 27 | Optimism: '0x2ABf469074dc0b54d793850807E6eb5Faf2625b1', 28 | Base: '0x6BDED42c6DA8FBf0d2bA55B2fa120C5e0c8D7891', 29 | Linea: '0x2ABf469074dc0b54d793850807E6eb5Faf2625b1', 30 | Zksync: '', 31 | Bsc: '0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506', 32 | Opbnb: '', 33 | Polygon: '0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506', 34 | Avalanche:'0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506', 35 | Scroll: '0x9B3336186a38E1b6c21955d112dbb0343Ee061eE', 36 | Blast: '0x54CF3d259a06601b5bC45F61A16443ed5404DD64', 37 | Mantle: '', 38 | Gnosis: '0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506', 39 | Fantom: '0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506', 40 | Celo: '0x1421bDe4B10e8dd459b3BCb598810B1337D56842', // router exists, butthey have no WTOKEN. No idea why.. 41 | Core: '0x9B3336186a38E1b6c21955d112dbb0343Ee061eE', 42 | Manta: '', 43 | Taiko: '', 44 | // Zora: '', 45 | Nova: '0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506' 46 | } 47 | export const networkNameToCoingeckoQueryString: {[key in ChainName]: string} = { 48 | Ethereum: 'ethereum', 49 | Arbitrum: 'arbitrum-one', 50 | Optimism: 'optimistic-ethereum', 51 | Base: 'base', 52 | Linea: 'linea', 53 | Zksync: 'zksync', 54 | Bsc: 'binance-smart-chain', 55 | Opbnb: 'opbnb', 56 | Polygon: 'polygon-pos', 57 | Avalanche: 'avalanche', 58 | Scroll: 'scroll', 59 | Blast: 'blast', 60 | Mantle: 'mantle', 61 | Gnosis: 'xdai', 62 | Fantom: 'fantom', 63 | Celo: 'celo', 64 | Core: 'core', 65 | Manta: '', 66 | Taiko: '', 67 | // Zora: 'zora', 68 | Nova: 'arbitrum-nova' 69 | } 70 | export const withdrawNetworks: {[key: string]: {name: string; token: string; fee: string}} = { 71 | Optimism: { 72 | name: 'Optimism', 73 | token: 'ETH', 74 | fee: '0.00004' 75 | }, 76 | Arbitrum: { 77 | name: 'Arbitrum One', 78 | token: 'ETH', 79 | fee: '0.0001' 80 | }, 81 | Linea: { 82 | name: 'Linea', 83 | token: 'ETH', 84 | fee: '0.0002' 85 | }, 86 | Base: { 87 | name: 'Base', 88 | token: 'ETH', 89 | fee: '0.00004' 90 | }, 91 | Zksync: { 92 | name: 'zkSync Era', 93 | token: 'ETH', 94 | fee: '0.000041' 95 | }, 96 | Ethereum: { 97 | name: 'ERC20', 98 | token: 'ETH', 99 | fee: '0.0016' 100 | } 101 | } 102 | 103 | export const chains: {[key: string]: Chain} = { 104 | Ethereum: { 105 | id: 1, 106 | lzId: '101', 107 | 108 | rpc: ['https://ethereum.publicnode.com'], 109 | explorer: 'https://etherscan.io/tx/', 110 | currency: {name: 'ETH'}, 111 | tokens: { 112 | ETH: { 113 | name: 'Ethereum', 114 | decimals: 18n, 115 | address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' 116 | }, 117 | WNATIVE: { 118 | name: 'WETH', 119 | decimals: 18n, 120 | address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2' 121 | } 122 | }, 123 | multicall: '0xcA11bde05977b3631167028862bE2a173976CA11' 124 | }, 125 | Arbitrum: { 126 | id: 42161, 127 | lzId: '110', 128 | rpc: ['https://arbitrum-one.publicnode.com'], 129 | explorer: 'https://arbiscan.io/tx/', 130 | currency: {name: 'ETH'}, 131 | tokens: { 132 | ETH: { 133 | name: 'Ethereum', 134 | decimals: 18n, 135 | address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' 136 | }, 137 | WNATIVE: { 138 | name: 'WETH', 139 | decimals: 18n, 140 | address: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1' 141 | } 142 | }, 143 | multicall: '0xcA11bde05977b3631167028862bE2a173976CA11' 144 | }, 145 | Optimism: { 146 | id: 10, 147 | lzId: '111', 148 | rpc: ['https://optimism-rpc.publicnode.com'], 149 | explorer: 'https://optimistic.etherscan.io/tx/', 150 | currency: {name: 'ETH'}, 151 | tokens: { 152 | ETH: { 153 | name: 'Ethereum', 154 | decimals: 18n, 155 | address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' 156 | }, 157 | WNATIVE: { 158 | name: 'WETH', 159 | decimals: 18n, 160 | address: '0x4200000000000000000000000000000000000006' 161 | } 162 | }, 163 | multicall: '0xcA11bde05977b3631167028862bE2a173976CA11' 164 | }, 165 | Base: { 166 | id: 8453, 167 | lzId: '184', 168 | rpc: ['https://base.publicnode.com'], 169 | explorer: 'https://basescan.org/tx/', 170 | currency: {name: 'ETH'}, 171 | tokens: { 172 | ETH: { 173 | name: 'Ethereum', 174 | decimals: 18n, 175 | address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' 176 | }, 177 | WNATIVE: { 178 | name: 'WETH', 179 | decimals: 18n, 180 | address: '0x4200000000000000000000000000000000000006' 181 | } 182 | }, 183 | multicall: '0xcA11bde05977b3631167028862bE2a173976CA11' 184 | }, 185 | Linea: { 186 | id: 59144, 187 | lzId: '183', 188 | rpc: ['https://rpc.linea.build'], 189 | explorer: 'https://lineascan.build/tx/', 190 | currency: {name: 'ETH'}, 191 | tokens: { 192 | ETH: { 193 | name: 'Ethereum', 194 | decimals: 18n, 195 | address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' 196 | }, 197 | WNATIVE: { 198 | name: 'WETH', 199 | decimals: 18n, 200 | address: '0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f' 201 | } 202 | }, 203 | multicall: '0xcA11bde05977b3631167028862bE2a173976CA11' 204 | }, 205 | Zksync: { 206 | id: 324, 207 | lzId: '165', 208 | rpc: ['https://mainnet.era.zksync.io'], 209 | explorer: 'https://era.zksync.network/tx/', 210 | currency: {name: 'ETH'}, 211 | tokens: { 212 | ETH: { 213 | name: 'Ethereum', 214 | decimals: 18n, 215 | address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' 216 | }, 217 | WNATIVE: { 218 | name: 'WETH', 219 | decimals: 18n, 220 | address: '0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91' 221 | } 222 | }, 223 | multicall: '0xb1F9b5FCD56122CdfD7086e017ec63E50dC075e7' 224 | }, 225 | Bsc: { 226 | id: 56, 227 | lzId: '102', 228 | rpc: ['https://rpc.ankr.com/bsc'], 229 | explorer: 'https://bscscan.com/tx/', 230 | currency: {name: 'BNB'}, 231 | tokens: { 232 | BNB: { 233 | name: 'Binance coin', 234 | decimals: 18n, 235 | address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' 236 | }, 237 | WNATIVE: { 238 | name: 'WBNB', 239 | decimals: 18n, 240 | address: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c' 241 | } 242 | }, 243 | multicall: '0xcA11bde05977b3631167028862bE2a173976CA11' 244 | }, 245 | Opbnb: { 246 | id: 204, 247 | lzId: '202', 248 | rpc: ['https://opbnb-mainnet-rpc.bnbchain.org'], 249 | explorer: 'https://opbnbscan.com/tx/', 250 | currency: {name: 'BNB'}, 251 | tokens: { 252 | BNB: { 253 | name: 'Binance coin', 254 | decimals: 18n, 255 | address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' 256 | }, 257 | WNATIVE: { 258 | name: 'WBNB', 259 | decimals: 18n, 260 | address: '0x4200000000000000000000000000000000000006' 261 | } 262 | }, 263 | multicall: '0xcA11bde05977b3631167028862bE2a173976CA11' 264 | }, 265 | Polygon: { 266 | id: 137, 267 | lzId: '109', 268 | rpc: ['https://polygon-rpc.com', 'https://rpc.ankr.com/polygon'], 269 | explorer: 'https://polygonscan.com/tx/', 270 | currency: {name: 'MATIC'}, 271 | tokens: { 272 | MATIC: { 273 | name: 'MATIC', 274 | decimals: 18n, 275 | address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' 276 | }, 277 | WNATIVE: { 278 | name: 'WMATIC', 279 | decimals: 18n, 280 | address: '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270' 281 | } 282 | }, 283 | multicall: '0xcA11bde05977b3631167028862bE2a173976CA11' 284 | }, 285 | Avalanche: { 286 | id: 43114, 287 | lzId: '106', 288 | 289 | rpc: ['https://avalanche.public-rpc.com'], 290 | explorer: 'https://snowtrace.io/tx/', 291 | currency: {name: 'AVAX'}, 292 | tokens: { 293 | AVAX: { 294 | name: 'AVAX', 295 | decimals: 18n, 296 | address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' 297 | }, 298 | WNATIVE: { 299 | name: 'WAVAX', 300 | decimals: 18n, 301 | address: '0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7' 302 | } 303 | }, 304 | multicall: '0xcA11bde05977b3631167028862bE2a173976CA11' 305 | }, 306 | Scroll: { 307 | id: 534352, 308 | lzId: '214', 309 | rpc: ['https://rpc.scroll.io'], 310 | explorer: 'https://scrollscan.com/tx/', 311 | currency: {name: 'ETH'}, 312 | tokens: { 313 | ETH: { 314 | name: 'Ethereum', 315 | decimals: 18n, 316 | address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' 317 | }, 318 | WNATIVE: { 319 | name: 'WETH', 320 | decimals: 18n, 321 | address: '0x5300000000000000000000000000000000000004' 322 | } 323 | }, 324 | multicall: '0xcA11bde05977b3631167028862bE2a173976CA11' 325 | }, 326 | Blast: { 327 | id: 81457, 328 | lzId: '243', 329 | 330 | rpc: ['https://rpc.blast.io'], 331 | explorer: 'https://blastscan.io/tx/', 332 | currency: {name: 'ETH'}, 333 | tokens: { 334 | ETH: { 335 | name: 'ETH', 336 | decimals: 18n, 337 | address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' 338 | }, 339 | WNATIVE: { 340 | name: 'WETH', 341 | decimals: 18n, 342 | address: '0x4300000000000000000000000000000000000004' 343 | } 344 | }, 345 | multicall: '0xcA11bde05977b3631167028862bE2a173976CA11' 346 | }, 347 | Mantle: { 348 | id: 5000, 349 | lzId: '181', 350 | rpc: ['https://rpc.mantle.xyz'], 351 | explorer: 'https://mantlescan.info/tx/', 352 | currency: {name: 'MNT'}, 353 | tokens: { 354 | MNT: { 355 | name: 'Mantle', 356 | decimals: 18n, 357 | address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' 358 | }, 359 | WNATIVE: { 360 | name: 'WMNT', 361 | decimals: 18n, 362 | address: '0x78c1b0C915c4FAA5FffA6CAbf0219DA63d7f4cb8' 363 | } 364 | }, 365 | multicall: '0xcA11bde05977b3631167028862bE2a173976CA11' 366 | }, 367 | Gnosis: { 368 | id: 100, 369 | lzId: '145', 370 | rpc: ['https://rpc.gnosischain.com'], 371 | explorer: 'https://gnosisscan.io/tx/', 372 | currency: {name: 'xDAI'}, 373 | tokens: { 374 | xDAI: { 375 | name: 'xDAI', 376 | decimals: 18n, 377 | address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' 378 | }, 379 | WNATIVE: { 380 | name: 'WxDAI', 381 | decimals: 18n, 382 | address: '0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d' 383 | } 384 | }, 385 | multicall: '0xcA11bde05977b3631167028862bE2a173976CA11' 386 | }, 387 | Fantom: { 388 | id: 250, 389 | lzId: '112', 390 | rpc: ['https://rpc.fantom.network'], 391 | explorer: 'https://ftmscan.com/tx/', 392 | currency: {name: 'FTM'}, 393 | tokens: { 394 | FTM: { 395 | name: 'FTM', 396 | decimals: 18n, 397 | address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' 398 | }, 399 | WNATIVE: { 400 | name: 'WFTM', 401 | decimals: 18n, 402 | address: '0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83' 403 | } 404 | }, 405 | multicall: '0xcA11bde05977b3631167028862bE2a173976CA11' 406 | }, 407 | Celo: { 408 | id: 42220, 409 | lzId: '125', 410 | rpc: ['https://forno.celo.org'], 411 | explorer: 'https://celoscan.io/tx/', 412 | currency: {name: 'CELO'}, 413 | tokens: { 414 | CELO: { 415 | name: 'Celo', 416 | decimals: 18n, 417 | address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' 418 | }, 419 | WNATIVE: { 420 | name: 'Celo', 421 | decimals: 18n, 422 | address: '0x471ece3750da237f93b8e339c536989b8978a438' // Celo actually doesn't have wCELO lol 423 | } 424 | }, 425 | multicall: '0xcA11bde05977b3631167028862bE2a173976CA11' 426 | }, 427 | Core: { 428 | id: 1116, 429 | lzId: '153', 430 | rpc: ['https://rpc.coredao.org'], 431 | explorer: 'https://scan.coredao.org/tx/', 432 | currency: {name: 'CORE'}, 433 | tokens: { 434 | CORE: { 435 | name: 'Core', 436 | decimals: 18n, 437 | address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' 438 | }, 439 | WNATIVE: { 440 | name: 'WCORE', 441 | decimals: 18n, 442 | address: '0x40375C92d9FAf44d2f9db9Bd9ba41a3317a2404f' 443 | } 444 | }, 445 | multicall: '0xcA11bde05977b3631167028862bE2a173976CA11' 446 | }, 447 | Manta: { 448 | id: 169, 449 | lzId: '217', 450 | rpc: ['https://manta-pacific.drpc.org'], 451 | explorer: 'https://manta.socialscan.io/tx/', 452 | currency: {name: 'ETH'}, 453 | tokens: { 454 | ETH: { 455 | name: 'ETH', 456 | decimals: 18n, 457 | address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' 458 | }, 459 | WNATIVE: { 460 | name: 'WETH', 461 | decimals: 18n, 462 | address: '0x0dc808adce2099a9f62aa87d9670745aba741746' 463 | } 464 | }, 465 | multicall: '0xcA11bde05977b3631167028862bE2a173976CA11' 466 | }, 467 | Taiko: { 468 | id: 167000, 469 | lzId: '290', 470 | rpc: ['https://rpc.taiko.xyz'], 471 | explorer: 'https://taikoscan.io/tx/', 472 | currency: {name: 'ETH'}, 473 | tokens: { 474 | ETH: { 475 | name: 'ETH', 476 | decimals: 18n, 477 | address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' 478 | }, 479 | WNATIVE: { 480 | name: 'WETH', 481 | decimals: 18n, 482 | address: '0xA51894664A773981C6C112C43ce576f315d5b1B6' 483 | } 484 | }, 485 | multicall: '0xcA11bde05977b3631167028862bE2a173976CA11' 486 | }, 487 | // Zora: { // not supported by coingecko xdd 488 | // id: 7777777, 489 | // lzId: '290', 490 | // rpc: ['https://7777777.rpc.thirdweb.com'], 491 | // explorer: 'https://zora.superscan.network/tx/', 492 | // currency: {name: 'ETH'}, 493 | // tokens: { 494 | // ETH: { 495 | // name: 'ETH', 496 | // decimals: 18n, 497 | // address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' 498 | // } 499 | // }, 500 | // multicall: '0xcA11bde05977b3631167028862bE2a173976CA11' 501 | // } 502 | Nova: { 503 | id: 42170, 504 | lzId: '175', 505 | rpc: ['https://arbitrum-nova-rpc.publicnode.com'], 506 | explorer: 'https://nova.arbiscan.io/tx/', 507 | currency: {name: 'ETH'}, 508 | tokens: { 509 | ETH: { 510 | name: 'ETH', 511 | decimals: 18n, 512 | address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' 513 | }, 514 | WNATIVE: { 515 | name: 'WETH', 516 | decimals: 18n, 517 | address: '0xA51894664A773981C6C112C43ce576f315d5b1B6' 518 | } 519 | }, 520 | multicall: '0xcA11bde05977b3631167028862bE2a173976CA11' 521 | } 522 | } 523 | -------------------------------------------------------------------------------- /src/periphery/web3Client.ts: -------------------------------------------------------------------------------- 1 | import {ERC20__factory, Multicall2, Multicall2__factory, WETH__factory} from '../../typechain' 2 | import {ethers, Wallet, JsonRpcProvider, TransactionRequest, parseUnits, BigNumberish, TransactionResponse, formatEther} from 'ethers' 3 | import {defaultSleep, retry} from '../utils/helpers' 4 | import {DEV, maxRetries} from '../../config' 5 | import {chains} from '../utils/constants' 6 | import {ChainName} from '../utils/types' 7 | import {getProvider} from './utils' 8 | 9 | require('dotenv').config() 10 | 11 | async function getNativeBalance(signerOrProvider: Wallet | JsonRpcProvider, address: string): Promise { 12 | return signerOrProvider.provider?.getBalance(address)! 13 | } 14 | async function getTokenBalance(signerOrProvider: Wallet | JsonRpcProvider, tokenAddress: string, address: string): Promise { 15 | const tokenContract = ERC20__factory.connect(tokenAddress, signerOrProvider) 16 | return tokenContract.balanceOf(address) 17 | } 18 | async function getBalance(signerOrProvider: Wallet | JsonRpcProvider, address: string, tokenAddress?: string): Promise { 19 | return retry( 20 | async () => { 21 | if (tokenAddress) { 22 | return getTokenBalance(signerOrProvider, tokenAddress, address) 23 | } else { 24 | return getNativeBalance(signerOrProvider, address) 25 | } 26 | }, 27 | {maxRetryCount: 20, retryInterval: 10} 28 | ) 29 | } 30 | async function waitBalance(signerOrProvider: Wallet | JsonRpcProvider, address: string, balanceBefore: bigint, tokenAddress?: string) { 31 | process.stdout.write(`waiting balance`) 32 | let currentBalance = await getBalance(signerOrProvider, address, tokenAddress) 33 | while (currentBalance <= balanceBefore) { 34 | currentBalance = await getBalance(signerOrProvider, address, tokenAddress) 35 | await defaultSleep(10, false) 36 | } 37 | process.stdout.write(` --> received ${formatEther(currentBalance - balanceBefore)}\n`) 38 | return true 39 | } 40 | async function needApprove( 41 | signerOrProvider: Wallet | JsonRpcProvider, 42 | tokenAddress: string, 43 | from: string, 44 | to: string, 45 | minAllowance: BigNumberish 46 | ): Promise { 47 | return retry( 48 | async () => { 49 | const tokenContract = ERC20__factory.connect(tokenAddress, signerOrProvider) 50 | let allowance = await tokenContract.allowance(from, to) 51 | if (DEV) { 52 | console.log(`allowance:${allowance}, want allowance: ${minAllowance}`) 53 | } 54 | if (allowance >= BigInt(minAllowance)) { 55 | return false 56 | } else { 57 | return true 58 | } 59 | }, 60 | {maxRetryCount: 20, retryInterval: 10} 61 | ) 62 | } 63 | async function approve(signer: Wallet, tokenAddress: string, to: string, amount: BigNumberish, minAllowance?: BigNumberish) { 64 | if (minAllowance) { 65 | let approveRequired = await needApprove(signer, tokenAddress, await signer.getAddress(), to, minAllowance) 66 | if (!approveRequired) { 67 | return '' 68 | } 69 | } 70 | const tokenContract = ERC20__factory.connect(tokenAddress, signer) 71 | let tx = { 72 | from: await signer.getAddress(), 73 | to: await tokenContract.getAddress(), 74 | data: tokenContract.interface.encodeFunctionData('approve', [to, amount]) 75 | } 76 | return sendTx(signer, tx) 77 | } 78 | async function transfer( 79 | signer: Wallet, 80 | to: string, 81 | amount: BigNumberish, 82 | tokenAddress?: string, 83 | gasMultipliers?: { 84 | price: number 85 | limit: number 86 | } 87 | ) { 88 | if (tokenAddress) { 89 | const tokenContract = ERC20__factory.connect(tokenAddress, signer) 90 | let tx = { 91 | from: await signer.getAddress(), 92 | to: await tokenContract.getAddress(), 93 | data: tokenContract.interface.encodeFunctionData('transfer', [to, amount]) 94 | } 95 | return sendTx(signer, tx, gasMultipliers) 96 | } else { 97 | let tx = { 98 | from: await signer.getAddress(), 99 | to: to, 100 | value: amount 101 | } 102 | return sendTx(signer, tx, gasMultipliers) 103 | } 104 | } 105 | async function unwrap( 106 | signer: Wallet, 107 | wrappedToken: string, 108 | value?: BigNumberish, 109 | gasMultipliers?: { 110 | price: number 111 | limit: number 112 | }, 113 | waitConfirmation = true 114 | ) { 115 | let wnative = WETH__factory.connect(wrappedToken, signer) 116 | return retry( 117 | async () => { 118 | let tokenBalance = value ?? (await getBalance(signer, signer.address, wrappedToken)) 119 | let tx = { 120 | from: signer.address, 121 | to: await wnative.getAddress(), 122 | data: wnative.interface.encodeFunctionData('withdraw', [tokenBalance]), 123 | value: 0n 124 | } 125 | return sendTx(signer, tx, gasMultipliers, waitConfirmation) 126 | }, 127 | {maxRetryCount: 3, retryInterval: 10} 128 | ) 129 | } 130 | async function getGwei(signerOrProvider: Wallet | JsonRpcProvider, multiplier = 1.3): Promise<{gasPrice: bigint}> { 131 | return retry( 132 | async () => { 133 | let fee = await signerOrProvider.provider!.getFeeData() 134 | if (fee?.gasPrice! != undefined) { 135 | return {gasPrice: (fee?.gasPrice! * parseUnits(multiplier.toString(), 3)) / 1000n} 136 | } else { 137 | return {gasPrice: (fee?.maxFeePerGas! * parseUnits(multiplier.toString(), 3)) / 1000n} 138 | } 139 | }, 140 | {maxRetryCount: 20, retryInterval: 10} 141 | ) 142 | } 143 | async function getGasPrice( 144 | signerOrProvider: Wallet | JsonRpcProvider, 145 | multiplier = 1.3 146 | ): Promise<{maxFeePerGas: bigint; maxPriorityFeePerGas: bigint} | {gasPrice: bigint}> { 147 | return retry( 148 | async () => { 149 | let fee = await signerOrProvider.provider!.getFeeData() 150 | if (fee.gasPrice !== null) { 151 | return {gasPrice: (fee?.gasPrice! * parseUnits(multiplier.toString(), 3)) / 1000n} 152 | } else { 153 | return { 154 | maxFeePerGas: (fee?.maxFeePerGas! * parseUnits(multiplier.toString(), 3)) / 1000n, 155 | maxPriorityFeePerGas: (fee?.maxPriorityFeePerGas! * parseUnits(multiplier.toString(), 3)) / 1000n 156 | } 157 | } 158 | }, 159 | {maxRetryCount: 20, retryInterval: 10} 160 | ) 161 | } 162 | async function waitGwei(want: number = 40) { 163 | let signerOrProvider = getProvider('Ethereum') 164 | let {gasPrice} = await getGwei(signerOrProvider, 1) 165 | let printed = false 166 | while ((gasPrice * 95n) / 100n > parseUnits(want.toString(), 'gwei')) { 167 | if (!printed) { 168 | console.log(`wait gwei ${new Date().toLocaleString()}`) 169 | printed = true 170 | } 171 | await defaultSleep(60) 172 | gasPrice = (await getGwei(signerOrProvider, 1)).gasPrice 173 | } 174 | } 175 | async function getTxStatus(signerOrProvider: Wallet | JsonRpcProvider, hash: string, maxWaitTime = 3 * 60): Promise { 176 | return retry( 177 | async () => { 178 | let time = 0 179 | while (time < maxWaitTime) { 180 | let receipt = await signerOrProvider.provider?.getTransactionReceipt(hash) 181 | if (receipt?.status == 1) { 182 | return receipt.hash 183 | } else if (receipt?.status == 0) { 184 | throw new Error('Tx failed') 185 | } else { 186 | await new Promise((resolve) => setTimeout(resolve, 5 * 1000)) 187 | time += 5 188 | } 189 | } 190 | console.log(`could not get tx status in ${(maxWaitTime / 60).toFixed(1)} minutes`) 191 | throw new Error('Tx failed or receipt not found') 192 | }, 193 | {maxRetryCount: 20, retryInterval: 10} 194 | ) 195 | } 196 | async function estimateTx(signer: Wallet, txBody: TransactionRequest, multiplier = 1.3) { 197 | return retry( 198 | async () => { 199 | return ((await signer.estimateGas(txBody)) * parseUnits(multiplier.toString(), 3)) / 1000n 200 | }, 201 | {maxRetryCount: 3, retryInterval: 10, needLog: false} 202 | ) 203 | } 204 | async function sendTx(signer: Wallet, txBody: TransactionRequest, gasMultipliers = {price: 1.3, limit: 1.3}, waitConfirmation = true) { 205 | let gasLimit = txBody?.gasLimit ?? (await estimateTx(signer, txBody, gasMultipliers.limit)) 206 | txBody.gasLimit = gasLimit 207 | if (txBody?.gasPrice == undefined && txBody?.maxFeePerGas == undefined) { 208 | let fee = await getGasPrice(signer, gasMultipliers.price) 209 | txBody = {...txBody, ...fee} 210 | } 211 | let txReceipt: TransactionResponse = await retry( 212 | signer.sendTransaction.bind(signer), 213 | {maxRetryCount: 3, retryInterval: 20, needLog: false}, 214 | txBody 215 | ) 216 | if (waitConfirmation) { 217 | return getTxStatus(signer, txReceipt.hash) 218 | } else { 219 | return txReceipt.hash 220 | } 221 | } 222 | async function sendRawTx(signer: Wallet, txBody: TransactionRequest, waitConfirmation = true) { 223 | let txReceipt: TransactionResponse = await retry(signer.sendTransaction.bind(signer), {maxRetryCount: 3, retryInterval: 20}, txBody) 224 | if (waitConfirmation) { 225 | return getTxStatus(signer, txReceipt.hash) 226 | } else { 227 | return txReceipt.hash 228 | } 229 | } 230 | 231 | class MultiCall { 232 | networkName: ChainName 233 | constructor(networkName?: ChainName) { 234 | this.networkName = networkName ?? 'Ethereum' 235 | } 236 | setNetwork(newNetworkName: ChainName) { 237 | this.networkName = newNetworkName 238 | return this 239 | } 240 | async callBalance(walletAddress: string, tokens: string[]) { 241 | let provider = getProvider(this.networkName) 242 | if (chains[this.networkName]?.multicall != undefined) { 243 | let balances: {token: string; balance: bigint}[] = [] 244 | let calls: {target: string; callData: string}[] = [] 245 | const multicall = Multicall2__factory.connect(chains[this.networkName].multicall!, provider) 246 | let iface = ERC20__factory.createInterface() 247 | const batchSize = 500 248 | for (let i = 0; i < tokens.length; i++) { 249 | let token = tokens[i] 250 | if (token.match(/(0x)?[a-fA-F0-9]{40}/) == null || token.length > 42) { 251 | if (DEV) { 252 | console.log('cant check ens token:', token) 253 | } 254 | continue 255 | } 256 | if (token.toLowerCase() == '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'.toLowerCase()) { 257 | calls.push({ 258 | target: await multicall.getAddress(), 259 | callData: multicall.interface.encodeFunctionData('getEthBalance', [walletAddress]) 260 | }) 261 | } else { 262 | calls.push({target: token, callData: iface.encodeFunctionData('balanceOf', [walletAddress])}) 263 | } 264 | if (((i + 1) % batchSize == 0 && i != 0) || i + 1 >= tokens.length) { 265 | let res: Multicall2.ResultStructOutput[] = [] 266 | let retryCount = 0 267 | while (retryCount < maxRetries) { 268 | try { 269 | res = await multicall.tryAggregate.staticCall(false, calls) 270 | break 271 | } catch (e: any) { 272 | await defaultSleep(5, false) 273 | } 274 | retryCount++ 275 | } 276 | for (let k = 0; k < res.length; k++) { 277 | if (!res[k][0]) continue 278 | if (res[k][1] == '0x') continue 279 | let value = res[k][1] 280 | // for some reason multicall sometimes returns too much bytes instead of uint256 and it causes overflow O_o 281 | if (value.length > 66) { 282 | value = value.slice(0, 66) 283 | } 284 | if (BigInt(value) > 0n) { 285 | balances.push({token: calls[k].target, balance: BigInt(value)}) 286 | } 287 | } 288 | 289 | calls = [] 290 | await defaultSleep(0.2, false) 291 | } 292 | } 293 | return balances 294 | } else { 295 | let balances: {token: string; balance: bigint}[] = [] 296 | for (let token of tokens) { 297 | if (token.match(/(0x)?[a-fA-F0-9]{40}/) == null || token.length > 42) { 298 | if (DEV) { 299 | console.log('cant check ens token:', token) 300 | } 301 | continue 302 | } 303 | if (token.toLowerCase() == '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'.toLowerCase()) { 304 | let balance = await getBalance(provider, walletAddress) 305 | if (balance > 0n) balances.push({token: token, balance: balance}) 306 | } else { 307 | let balance = await getBalance(provider, walletAddress, token) 308 | if (balance > 0n) balances.push({token: token, balance: balance}) 309 | } 310 | await defaultSleep(0.2, false) 311 | } 312 | return balances 313 | } 314 | } 315 | async getTokenInfo(tokens: string[]) { 316 | let provider = getProvider(this.networkName) 317 | 318 | if (chains[this.networkName]?.multicall != undefined) { 319 | let data: {address: string; name: string; symbol: string; decimals: bigint}[] = [] 320 | let calls: {target: string; callData: string}[] = [] 321 | const multicall = Multicall2__factory.connect(chains[this.networkName].multicall!, provider) 322 | let iface = ERC20__factory.createInterface() 323 | 324 | const batchSize = 100 325 | for (let i = 0; i < tokens.length; i++) { 326 | let token = tokens[i] 327 | if (token.match(/(0x)?[a-fA-F0-9]{40}/) == null || token.length > 42) { 328 | if (DEV) { 329 | console.log('cant check ens token:', token) 330 | } 331 | continue 332 | } 333 | if (token.toLowerCase() == '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'.toLowerCase()) { 334 | continue 335 | } else { 336 | calls.push({target: token, callData: iface.encodeFunctionData('name')}) 337 | calls.push({target: token, callData: iface.encodeFunctionData('symbol')}) 338 | calls.push({target: token, callData: iface.encodeFunctionData('decimals')}) 339 | } 340 | if (((i + 1) % batchSize == 0 && i != 0) || i + 1 >= tokens.length) { 341 | let res: Multicall2.ResultStructOutput[] = [] 342 | let retryCount = 0 343 | while (retryCount < maxRetries) { 344 | try { 345 | res = await multicall.tryAggregate.staticCall(false, calls) 346 | break 347 | } catch (e: any) { 348 | await defaultSleep(5, false) 349 | } 350 | } 351 | for (let k = 0; k < res.length; k += 3) { 352 | if (!res[k][0] || !res[k + 1][0] || !res[k + 2][0]) continue 353 | // for some reason multicall sometimes returns too much bytes instead of uint256 and it causes overflow O_o 354 | let decimals = res[k + 2][1] 355 | if (decimals.length > 66) { 356 | decimals = decimals.slice(0, 66) 357 | } 358 | data.push({ 359 | address: calls[k].target, 360 | name: iface.decodeFunctionResult('name', res[k][1])[0], 361 | symbol: iface.decodeFunctionResult('symbol', res[k + 1][1])[0], 362 | decimals: BigInt(decimals) 363 | }) 364 | } 365 | 366 | calls = [] 367 | await defaultSleep(0.2) 368 | } 369 | } 370 | return data 371 | } else { 372 | let data: {address: string; name: string; symbol: string; decimals: bigint}[] = [] 373 | for (let token of tokens) { 374 | if (token.match(/(0x)?[a-fA-F0-9]{40}/) == null || token.length > 42) { 375 | if (DEV) { 376 | console.log('cant check ens token:', token) 377 | } 378 | continue 379 | } 380 | if (token.toLowerCase() == '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'.toLowerCase()) { 381 | continue 382 | } else { 383 | let erc20 = ERC20__factory.connect(token, provider) 384 | let name = await erc20.name() 385 | let symbol = await erc20.symbol() 386 | let decimals = await erc20.decimals() 387 | data.push({address: token, name: name, symbol: symbol, decimals: BigInt(decimals)}) 388 | } 389 | await defaultSleep(0.2, false) 390 | } 391 | return data 392 | } 393 | } 394 | } 395 | const Multicall = new MultiCall() 396 | 397 | export {getGwei, waitGwei, estimateTx, getNativeBalance, getBalance, waitBalance, approve, transfer, unwrap, sendTx, sendRawTx, Multicall} 398 | -------------------------------------------------------------------------------- /src/utils/tokenlists/Linea.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "chainId": 59144, 4 | "address": "0xd2671165570f41bbb3b0097893300b6eb6101e6c", 5 | "name": "Wrapped rsETH", 6 | "symbol": "WRSETH", 7 | "decimals": 18, 8 | "logoURI": "https://assets.coingecko.com/coins/images/37919/thumb/rseth.png?1715936438" 9 | }, 10 | { 11 | "chainId": 59144, 12 | "address": "0xe5d7c2a44ffddf6b295a15c148167daaaf5cf34f", 13 | "name": "Bridged Wrapped Ether Linea ", 14 | "symbol": "WETH", 15 | "decimals": 18, 16 | "logoURI": "https://assets.coingecko.com/coins/images/31019/thumb/download_%2817%29.png?1696529855" 17 | }, 18 | { 19 | "chainId": 59144, 20 | "address": "0xf3b001d64c656e30a62fbaaca003b1336b4ce12a", 21 | "name": "MAI Linea ", 22 | "symbol": "MIMATIC", 23 | "decimals": 18, 24 | "logoURI": "https://assets.coingecko.com/coins/images/35569/thumb/mimatic-red.png?1709192002" 25 | }, 26 | { 27 | "chainId": 59144, 28 | "address": "0x176211869ca2b568f2a7d4ee941e073a821ee1ff", 29 | "name": "Bridged USD Coin Linea ", 30 | "symbol": "USDC", 31 | "decimals": 6, 32 | "logoURI": "https://assets.coingecko.com/coins/images/31270/thumb/USDC-icon.png?1696530094" 33 | }, 34 | { 35 | "chainId": 59144, 36 | "address": "0x4186bfc76e2e237523cbc30fd220fe055156b41f", 37 | "name": "LayerZero Bridged rsETH Linea ", 38 | "symbol": "RSETH", 39 | "decimals": 18, 40 | "logoURI": "https://assets.coingecko.com/coins/images/37843/thumb/29242.png?1715747269" 41 | }, 42 | { 43 | "chainId": 59144, 44 | "address": "0x1a51b19ce03dbe0cb44c1528e34a7edd7771e9af", 45 | "name": "Lynex", 46 | "symbol": "LYNX", 47 | "decimals": 18, 48 | "logoURI": "https://assets.coingecko.com/coins/images/34874/thumb/LogoCircular_200x200.png?1706462851" 49 | }, 50 | { 51 | "chainId": 59144, 52 | "address": "0x43e8809ea748eff3204ee01f08872f063e44065f", 53 | "name": "Mendi Finance", 54 | "symbol": "MENDI", 55 | "decimals": 18, 56 | "logoURI": "https://assets.coingecko.com/coins/images/31418/thumb/mendi_finance_token_logo_v1.png?1696530233" 57 | }, 58 | { 59 | "chainId": 59144, 60 | "address": "0x5fbdf89403270a1846f5ae7d113a989f850d1566", 61 | "name": "Foxy", 62 | "symbol": "FOXY", 63 | "decimals": 18, 64 | "logoURI": "https://assets.coingecko.com/coins/images/36870/thumb/Foxy_Logo_Square_200x200.png?1712645286" 65 | }, 66 | { 67 | "chainId": 59144, 68 | "address": "0xa219439258ca9da29e9cc4ce5596924745e12b93", 69 | "name": "Bridged Tether Linea ", 70 | "symbol": "USDT", 71 | "decimals": 6, 72 | "logoURI": "https://assets.coingecko.com/coins/images/31271/thumb/usdt.jpeg?1696530095" 73 | }, 74 | { 75 | "chainId": 59144, 76 | "address": "0x82cc61354d78b846016b559e3ccd766fa7e793d5", 77 | "name": "Linda", 78 | "symbol": "LINDA", 79 | "decimals": 18, 80 | "logoURI": "https://assets.coingecko.com/coins/images/33699/thumb/linda-logo-200.png?1705166731" 81 | }, 82 | { 83 | "chainId": 59144, 84 | "address": "0x4af15ec2a0bd43db75dd04e62faa3b8ef36b00d5", 85 | "name": "Bridged Dai Stablecoin Linea ", 86 | "symbol": "DAI", 87 | "decimals": 18, 88 | "logoURI": "https://assets.coingecko.com/coins/images/31272/thumb/dai-stablecoin.png?1696530095" 89 | }, 90 | { 91 | "chainId": 59144, 92 | "address": "0x11f98c7e42a367dab4f200d2fdc460fb445ce9a8", 93 | "name": "SpartaDEX", 94 | "symbol": "SPARTA", 95 | "decimals": 18, 96 | "logoURI": "https://assets.coingecko.com/coins/images/30836/thumb/SpartaDex_200x200.jpg?1696529694" 97 | }, 98 | { 99 | "chainId": 59144, 100 | "address": "0x9201f3b9dfab7c13cd659ac5695d12d605b5f1e6", 101 | "name": "EchoDEX Community Portion", 102 | "symbol": "ECP", 103 | "decimals": 18, 104 | "logoURI": "https://assets.coingecko.com/coins/images/31112/thumb/EchoDex.logo.200x200_%281%29.png?1696529942" 105 | }, 106 | { 107 | "chainId": 59144, 108 | "address": "0x1f63d0ec7193964142ef6b13d901462d0e5cbb50", 109 | "name": "ONEPUNCH", 110 | "symbol": "ONEPUNCH", 111 | "decimals": 18, 112 | "logoURI": "https://assets.coingecko.com/coins/images/38183/thumb/onepunch.png?1716788353" 113 | }, 114 | { 115 | "chainId": 59144, 116 | "address": "0x7d43aabc515c356145049227cee54b608342c0ad", 117 | "name": "Binance USD Linea ", 118 | "symbol": "BUSD", 119 | "decimals": 18, 120 | "logoURI": "https://assets.coingecko.com/coins/images/31020/thumb/download_%2816%29.png?1696529856" 121 | }, 122 | { 123 | "chainId": 59144, 124 | "address": "0xacb54d07ca167934f57f829bee2cc665e1a5ebef", 125 | "name": "Croak", 126 | "symbol": "CROAK", 127 | "decimals": 18, 128 | "logoURI": "https://assets.coingecko.com/coins/images/38592/thumb/Croak-for-coingeko.png?1718092516" 129 | }, 130 | { 131 | "chainId": 59144, 132 | "address": "0xdd3b8084af79b9bae3d1b668c0de08ccc2c9429a", 133 | "name": "Magic Internet Money Linea ", 134 | "symbol": "MIM", 135 | "decimals": 18, 136 | "logoURI": "https://assets.coingecko.com/coins/images/37660/thumb/mim.png?1715165629" 137 | }, 138 | { 139 | "chainId": 59144, 140 | "address": "0xa0e4c84693266a9d3bbef2f394b33712c76599ab", 141 | "name": "EURO3", 142 | "symbol": "EURO3", 143 | "decimals": 18, 144 | "logoURI": "https://assets.coingecko.com/coins/images/33118/thumb/EURO3.png?1700732918" 145 | }, 146 | { 147 | "chainId": 59144, 148 | "address": "0x1bf74c010e6320bab11e2e5a532b5ac15e0b8aa6", 149 | "name": "LayerZero Bridged weETH Linea ", 150 | "symbol": "WEETH", 151 | "decimals": 18, 152 | "logoURI": "https://assets.coingecko.com/coins/images/37842/thumb/weETH_%281%29.png?1715747077" 153 | }, 154 | { 155 | "chainId": 59144, 156 | "address": "0xe2a6e74118e708f7652fc4c74d2f9ee5fa210563", 157 | "name": "NotWifGary", 158 | "symbol": "NWG", 159 | "decimals": 18, 160 | "logoURI": "https://assets.coingecko.com/coins/images/38213/thumb/notwifgary.jpeg?1716823153" 161 | }, 162 | { 163 | "chainId": 59144, 164 | "address": "0x1e1f509963a6d33e169d9497b11c7dbfe73b7f13", 165 | "name": "Overnight fi USDT ", 166 | "symbol": "USDT+", 167 | "decimals": 6, 168 | "logoURI": "https://assets.coingecko.com/coins/images/30168/thumb/USDT_.png?1696529088" 169 | }, 170 | { 171 | "chainId": 59144, 172 | "address": "0x0b1a02a7309dfbfad1cd4adc096582c87e8a3ac1", 173 | "name": "Horizon", 174 | "symbol": "HZN", 175 | "decimals": 18, 176 | "logoURI": "https://assets.coingecko.com/coins/images/31156/thumb/Circle_logo_black_%281%29.png?1696529983" 177 | }, 178 | { 179 | "chainId": 59144, 180 | "address": "0x1be3735dd0c0eb229fb11094b6c277192349ebbf", 181 | "name": "LUBE", 182 | "symbol": "LUBE", 183 | "decimals": 18, 184 | "logoURI": "https://assets.coingecko.com/coins/images/33070/thumb/Lube2-icon-200x200.png?1708581050" 185 | }, 186 | { 187 | "chainId": 59144, 188 | "address": "0x78354f8dccb269a615a7e0a24f9b0718fdc3c7a7", 189 | "name": "ZeroLend", 190 | "symbol": "ZERO", 191 | "decimals": 18, 192 | "logoURI": "https://assets.coingecko.com/coins/images/37375/thumb/image.png?1714884543" 193 | }, 194 | { 195 | "chainId": 59144, 196 | "address": "0xd83af4fbd77f3ab65c3b1dc4b38d7e67aecf599a", 197 | "name": "Linea Voyage XP", 198 | "symbol": "LXP", 199 | "decimals": 18, 200 | "logoURI": "https://assets.coingecko.com/coins/images/34795/thumb/lxp-1.png?1706032525" 201 | }, 202 | { 203 | "chainId": 59144, 204 | "address": "0xcc22f6aa610d1b2a0e89ef228079cb3e1831b1d1", 205 | "name": "Linea Velocore", 206 | "symbol": "LVC", 207 | "decimals": 18, 208 | "logoURI": "https://assets.coingecko.com/coins/images/31537/thumb/LVC.png?1696530346" 209 | }, 210 | { 211 | "chainId": 59144, 212 | "address": "0xaaaac83751090c6ea42379626435f805ddf54dc8", 213 | "name": "Nile", 214 | "symbol": "NILE", 215 | "decimals": 18, 216 | "logoURI": "https://assets.coingecko.com/coins/images/34828/thumb/nile.png?1716935377" 217 | }, 218 | { 219 | "chainId": 59144, 220 | "address": "0x796000fad0d00b003b9dd8e531ba90cff39e01e0", 221 | "name": "Yellow Duckies", 222 | "symbol": "DUCKIES", 223 | "decimals": 8, 224 | "logoURI": "https://assets.coingecko.com/coins/images/27630/thumb/duckies_logo.png?1706528164" 225 | }, 226 | { 227 | "chainId": 59144, 228 | "address": "0xba2f9e7ae9f5f03fce7d560f986743659e768bbf", 229 | "name": "ARYZE eUSD", 230 | "symbol": "EUSD", 231 | "decimals": 18, 232 | "logoURI": "https://assets.coingecko.com/coins/images/32422/thumb/ARYZE_eUSD.png?1698118313" 233 | }, 234 | { 235 | "chainId": 59144, 236 | "address": "0xeb466342c4d449bc9f53a865d5cb90586f405215", 237 | "name": "Axelar Bridged USDC", 238 | "symbol": "AXLUSDC", 239 | "decimals": 6, 240 | "logoURI": "https://assets.coingecko.com/coins/images/26476/thumb/uausdc_D_3x.png?1696525548" 241 | }, 242 | { 243 | "chainId": 59144, 244 | "address": "0x6ef95b6f3b0f39508e3e04054be96d5ee39ede0d", 245 | "name": "Symbiosis", 246 | "symbol": "SIS", 247 | "decimals": 18, 248 | "logoURI": "https://assets.coingecko.com/coins/images/20805/thumb/SymbiosisFinance_logo-150x150.jpeg?1696520198" 249 | }, 250 | { 251 | "chainId": 59144, 252 | "address": "0x5471ea8f739dd37e9b81be9c5c77754d8aa953e4", 253 | "name": "Wrapped AVAX", 254 | "symbol": "WAVAX", 255 | "decimals": 18, 256 | "logoURI": "https://assets.coingecko.com/coins/images/15075/thumb/wrapped-avax.png?1696514734" 257 | }, 258 | { 259 | "chainId": 59144, 260 | "address": "0x93f4d0ab6a8b4271f4a28db399b5e30612d21116", 261 | "name": "StakeStone ETH", 262 | "symbol": "STONE", 263 | "decimals": 18, 264 | "logoURI": "https://assets.coingecko.com/coins/images/33103/thumb/200_200.png?1702602672" 265 | }, 266 | { 267 | "chainId": 59144, 268 | "address": "0x13a7f090d46c74acba98c51786a5c46ed9a474f0", 269 | "name": "ScamFari", 270 | "symbol": "SCM", 271 | "decimals": 18, 272 | "logoURI": "https://assets.coingecko.com/coins/images/31267/thumb/Ava_Scamfari_%281%29.png?1696530091" 273 | }, 274 | { 275 | "chainId": 59144, 276 | "address": "0xa334884bf6b0a066d553d19e507315e839409e62", 277 | "name": "Ethos Reserve Note", 278 | "symbol": "ERN", 279 | "decimals": 18, 280 | "logoURI": "https://assets.coingecko.com/coins/images/29744/thumb/ERN200x200.png?1696528676" 281 | }, 282 | { 283 | "chainId": 59144, 284 | "address": "0x5cc5e64ab764a0f1e97f23984e20fd4528356a6a", 285 | "name": "XRGB", 286 | "symbol": "XRGB", 287 | "decimals": 18, 288 | "logoURI": "https://assets.coingecko.com/coins/images/35447/thumb/log2.png?1708620430" 289 | }, 290 | { 291 | "chainId": 59144, 292 | "address": "0xecc68d0451e20292406967fe7c04280e5238ac7d", 293 | "name": "Axelar Bridged Frax Ether", 294 | "symbol": "AXLFRXETH", 295 | "decimals": 18, 296 | "logoURI": "https://assets.coingecko.com/coins/images/38976/thumb/Screen_Shot_2024-06-18_at_12.55.54_PM_2.png?1719714886" 297 | }, 298 | { 299 | "chainId": 59144, 300 | "address": "0xf5c6825015280cdfd0b56903f9f8b5a2233476f5", 301 | "name": "Wrapped BNB", 302 | "symbol": "WBNB", 303 | "decimals": 18, 304 | "logoURI": "https://assets.coingecko.com/coins/images/12591/thumb/binance-coin-logo.png?1696512401" 305 | }, 306 | { 307 | "chainId": 59144, 308 | "address": "0x265b25e22bcd7f10a5bd6e6410f10537cc7567e8", 309 | "name": "Wrapped Matic", 310 | "symbol": "WMATIC", 311 | "decimals": 18, 312 | "logoURI": "https://assets.coingecko.com/coins/images/14073/thumb/matic.png?1696513797" 313 | }, 314 | { 315 | "chainId": 59144, 316 | "address": "0xb79dd08ea68a908a97220c76d19a6aa9cbde4376", 317 | "name": "Overnight fi USD ", 318 | "symbol": "USD+", 319 | "decimals": 6, 320 | "logoURI": "https://assets.coingecko.com/coins/images/25757/thumb/USD__logo.png?1696524843" 321 | }, 322 | { 323 | "chainId": 59144, 324 | "address": "0x47c337bd5b9344a6f3d6f58c474d9d8cd419d8ca", 325 | "name": "DackieSwap", 326 | "symbol": "DACKIE", 327 | "decimals": 18, 328 | "logoURI": "https://assets.coingecko.com/coins/images/30752/thumb/dackieswap_large.png?1707290196" 329 | }, 330 | { 331 | "chainId": 59144, 332 | "address": "0x59debed8d46a0cb823d8be8b957add987ead39aa", 333 | "name": "Quack Token", 334 | "symbol": "QUACK", 335 | "decimals": 18, 336 | "logoURI": "https://assets.coingecko.com/coins/images/31436/thumb/0x639C0D019C257966C4907bD4E68E3F349bB58109.png?1696530251" 337 | }, 338 | { 339 | "chainId": 59144, 340 | "address": "0x3d4b2132ed4ea0aa93903713a4de9f98e625a5c7", 341 | "name": "3A", 342 | "symbol": "A3A", 343 | "decimals": 18, 344 | "logoURI": "https://assets.coingecko.com/coins/images/33135/thumb/A3A.png?1700801023" 345 | }, 346 | { 347 | "chainId": 59144, 348 | "address": "0x8c56017b172226fe024dea197748fc1eaccc82b1", 349 | "name": "XFai", 350 | "symbol": "XFIT", 351 | "decimals": 18, 352 | "logoURI": "https://assets.coingecko.com/coins/images/14904/thumb/XFAI_Logo200x200.jpg?1696514567" 353 | }, 354 | { 355 | "chainId": 59144, 356 | "address": "0x0018d96c579121a94307249d47f053e2d687b5e7", 357 | "name": "Metavault Trade", 358 | "symbol": "MVX", 359 | "decimals": 18, 360 | "logoURI": "https://assets.coingecko.com/coins/images/25402/thumb/mvx.png?1696524534" 361 | }, 362 | { 363 | "chainId": 59144, 364 | "address": "0x3b2f62d42db19b30588648bf1c184865d4c3b1d6", 365 | "name": "Kyber Network Crystal", 366 | "symbol": "KNC", 367 | "decimals": 18, 368 | "logoURI": "https://assets.coingecko.com/coins/images/14899/thumb/RwdVsGcw_400x400.jpg?1696514562" 369 | }, 370 | { 371 | "chainId": 59144, 372 | "address": "0x7a6aa80b49017f3e091574ab5c6977d863ff3865", 373 | "name": "KUMA Protocol US KUMA Interest Bearing ", 374 | "symbol": "USK", 375 | "decimals": 18, 376 | "logoURI": "https://assets.coingecko.com/coins/images/33445/thumb/USK.png?1701888523" 377 | }, 378 | { 379 | "chainId": 59144, 380 | "address": "0x3e5d9d8a63cc8a88748f229999cf59487e90721e", 381 | "name": "MetalSwap", 382 | "symbol": "XMT", 383 | "decimals": 18, 384 | "logoURI": "https://assets.coingecko.com/coins/images/22075/thumb/Logo_COIN_-_Gradiente.png?1696521419" 385 | }, 386 | { 387 | "chainId": 59144, 388 | "address": "0x2b1d36f5b61addaf7da7ebbd11b35fd8cfb0de31", 389 | "name": "Interport Token", 390 | "symbol": "ITP", 391 | "decimals": 18, 392 | "logoURI": "https://assets.coingecko.com/coins/images/28338/thumb/ITP_Logo_200.png?1696527344" 393 | }, 394 | { 395 | "chainId": 59144, 396 | "address": "0x60892e742d91d16be2cb0ffe847e85445989e30b", 397 | "name": "Wefi", 398 | "symbol": "WEFI", 399 | "decimals": 18, 400 | "logoURI": "https://assets.coingecko.com/coins/images/30540/thumb/wefi.png?1696529412" 401 | }, 402 | { 403 | "chainId": 59144, 404 | "address": "0x5a7a183b6b44dc4ec2e3d2ef43f98c5152b1d76d", 405 | "name": "Inception Restaked ETH", 406 | "symbol": "INETH", 407 | "decimals": 18, 408 | "logoURI": "https://assets.coingecko.com/coins/images/34127/thumb/inETH.png?1715036464" 409 | }, 410 | { 411 | "chainId": 59144, 412 | "address": "0xa8ae6365383eb907e6b4b1b7e82a35752cc5ef8c", 413 | "name": "Ankr Network", 414 | "symbol": "ANKR", 415 | "decimals": 18, 416 | "logoURI": "https://assets.coingecko.com/coins/images/4324/thumb/U85xTl2.png?1696504928" 417 | }, 418 | { 419 | "chainId": 59144, 420 | "address": "0x60d01ec2d5e98ac51c8b4cf84dfcce98d527c747", 421 | "name": "iZUMi Finance", 422 | "symbol": "IZI", 423 | "decimals": 18, 424 | "logoURI": "https://assets.coingecko.com/coins/images/21791/thumb/izumi-logo-symbol.png?1696521144" 425 | }, 426 | { 427 | "chainId": 59144, 428 | "address": "0x11d8680c7f8f82f623e840130eb06c33d9f90c89", 429 | "name": "Ankr Staked ETH", 430 | "symbol": "ANKRETH", 431 | "decimals": 18, 432 | "logoURI": "https://assets.coingecko.com/coins/images/13403/thumb/aETHc.png?1696513165" 433 | }, 434 | { 435 | "chainId": 59144, 436 | "address": "0x2f0b4300074afc01726262d4cc9c1d2619d7297a", 437 | "name": "KUMA Protocol Wrapped USK", 438 | "symbol": "WUSK", 439 | "decimals": 18, 440 | "logoURI": "https://assets.coingecko.com/coins/images/33446/thumb/USK.png?1701888542" 441 | }, 442 | { 443 | "chainId": 59144, 444 | "address": "0xd08c3f25862077056cb1b710937576af899a4959", 445 | "name": "Inception stETH", 446 | "symbol": "INSTETH", 447 | "decimals": 18, 448 | "logoURI": "https://assets.coingecko.com/coins/images/34954/thumb/Group_14.png?1706773546" 449 | }, 450 | { 451 | "chainId": 59144, 452 | "address": "0x2416092f143378750bb29b79ed961ab195cceea5", 453 | "name": "Renzo Restaked ETH", 454 | "symbol": "EZETH", 455 | "decimals": 18, 456 | "logoURI": "https://assets.coingecko.com/coins/images/34753/thumb/Ezeth_logo_circle.png?1713496404" 457 | }, 458 | { 459 | "chainId": 59144, 460 | "address": "0xb5bedd42000b71fdde22d3ee8a79bd49a568fc8f", 461 | "name": "Wrapped stETH", 462 | "symbol": "WSTETH", 463 | "decimals": 18, 464 | "logoURI": "https://assets.coingecko.com/coins/images/18834/thumb/wstETH.png?1696518295" 465 | }, 466 | { 467 | "chainId": 59144, 468 | "address": "0x3f817b28da4940f018c6b5c0a11c555ebb1264f9", 469 | "name": "EURO3", 470 | "symbol": "EURO3", 471 | "decimals": 18, 472 | "logoURI": "https://assets.coingecko.com/coins/images/33118/thumb/EURO3.png?1700732918" 473 | }, 474 | { 475 | "chainId": 59144, 476 | "address": "0xa88b54e6b76fb97cdb8ecae868f1458e18a953f4", 477 | "name": "Davos xyz USD", 478 | "symbol": "DUSD", 479 | "decimals": 18, 480 | "logoURI": "https://assets.coingecko.com/coins/images/28775/thumb/dusd_logo_200x200.png?1696527754" 481 | } 482 | ] --------------------------------------------------------------------------------