├── .gitignore ├── pm2.json ├── src ├── Models │ ├── Topics.ts │ ├── GasEstimates.ts │ ├── Symbols.ts │ └── Reserve.ts ├── Services │ ├── Web3Factory.ts │ ├── AbiUtils.ts │ ├── Logger.ts │ ├── LoadConfig.ts │ ├── Pricer.ts │ ├── BscScan.ts │ ├── DumpAll.ts │ ├── WatchNewPairs.ts │ ├── AutoSell.ts │ ├── Ape.ts │ ├── ProfitLossManager.ts │ └── Web3Helper.ts ├── syncModels.ts ├── index.ts ├── dumpAll.ts ├── Entities │ ├── index.ts │ ├── Position.ts │ └── PositionFactory.ts └── ABIs │ ├── IPancakeFactoryV2.json │ ├── IBEP20.json │ ├── IPancakePair.json │ └── IPancakeRouterV2.json ├── tsconfig.json ├── package.json ├── webpack.config.js ├── .env.sample └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | build/* 3 | node_modules 4 | .env -------------------------------------------------------------------------------- /pm2.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "roboape", 3 | "script": "build/app.js" 4 | } -------------------------------------------------------------------------------- /src/Models/Topics.ts: -------------------------------------------------------------------------------- 1 | export const Topics = { 2 | PairCreated: '0x0d3648bd0f6ba80134a33ba9275ac585d9d315f0ad8355cddefde31afa28d0e9', 3 | }; -------------------------------------------------------------------------------- /src/Models/GasEstimates.ts: -------------------------------------------------------------------------------- 1 | export const GasEstimates = { 2 | approve: '50000', // BEP20 approve 3 | swap: '200000', // swapExactTokensForETHSupportingFeeOnTransferTokens on PancakeV2 4 | }; -------------------------------------------------------------------------------- /src/Models/Symbols.ts: -------------------------------------------------------------------------------- 1 | export const Symbols = { 2 | wbnb: '0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c', // mainnet 3 | //wbnb: '0xae13d989dac2f0debff460ac112a837c89baa7cd', // testnet 4 | }; 5 | -------------------------------------------------------------------------------- /src/Services/Web3Factory.ts: -------------------------------------------------------------------------------- 1 | import Web3 from "web3"; 2 | 3 | export default class Web3Factory { 4 | public static make() { 5 | return new Web3(new Web3.providers.WebsocketProvider(process.env.WEB3_WS_PROVIDER)); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/syncModels.ts: -------------------------------------------------------------------------------- 1 | import {Position} from "./Entities"; 2 | import LoadConfig from "./Services/LoadConfig"; 3 | 4 | new LoadConfig(); 5 | 6 | (async () => { 7 | await Position.sync({alter: true}); 8 | 9 | console.log('Done'); 10 | })(); 11 | -------------------------------------------------------------------------------- /src/Services/AbiUtils.ts: -------------------------------------------------------------------------------- 1 | export default class AbiUtils { 2 | public static decodedEventsToArray(log: any) { 3 | const values: any = {}; 4 | for (const event of log.events) { 5 | values[event.name] = event.value; 6 | } 7 | 8 | return values; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Models/Reserve.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from "bignumber.js"; 2 | 3 | export default class Reserve 4 | { 5 | public reserve0: BigNumber; 6 | public reserve1: BigNumber; 7 | 8 | constructor(r0: string, r1: string) { 9 | this.reserve0 = new BigNumber(r0); 10 | this.reserve1 = new BigNumber(r1); 11 | } 12 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./build/", 4 | "noImplicitAny": true, 5 | "lib": [ 6 | "ES6", 7 | "esnext", 8 | "dom" 9 | ], 10 | "module": "es6", 11 | "target": "es6", 12 | "allowJs": false, 13 | "moduleResolution": "node", 14 | "experimentalDecorators": true, 15 | "emitDecoratorMetadata": true 16 | }, 17 | } -------------------------------------------------------------------------------- /src/Services/Logger.ts: -------------------------------------------------------------------------------- 1 | export default class Logger { 2 | constructor(private name: string) { 3 | } 4 | 5 | public log(line: string) { 6 | console.log(new Date(), `[${this.name}] ${line}`); 7 | } 8 | 9 | public error(line: string, exception: any = null) { 10 | console.error(new Date(), `[${this.name}] ${line}`); 11 | if (exception) { 12 | console.error(exception); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Services/LoadConfig.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as dotenv from "dotenv"; 3 | 4 | export default class LoadConfig { 5 | constructor() { 6 | const path = fs.existsSync('.env') ? '.env' : '../.env'; 7 | 8 | if (!fs.existsSync(path)) { 9 | console.error('.env file does not exist'); 10 | process.exit(1); 11 | } 12 | 13 | dotenv.config({ 14 | path, 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import LoadConfig from "./Services/LoadConfig"; 2 | import WatchNewPairs from "./Services/WatchNewPairs"; 3 | import ProfitLossManager from "./Services/ProfitLossManager"; 4 | import Web3Factory from "./Services/Web3Factory"; 5 | import Web3Helper from "./Services/Web3Helper"; 6 | 7 | new LoadConfig(); 8 | 9 | (async () => { 10 | const web3 = Web3Factory.make(); 11 | const web3Helper = new Web3Helper(web3); 12 | await web3Helper.init(); 13 | 14 | new WatchNewPairs(web3, web3Helper); 15 | new ProfitLossManager(web3Helper); 16 | })(); 17 | -------------------------------------------------------------------------------- /src/dumpAll.ts: -------------------------------------------------------------------------------- 1 | import LoadConfig from "./Services/LoadConfig"; 2 | import DumpAll from "./Services/DumpAll"; 3 | import Web3Factory from "./Services/Web3Factory"; 4 | import Web3Helper from "./Services/Web3Helper"; 5 | import * as minimist from "minimist"; 6 | 7 | // parse args 8 | const args = minimist(process.argv, { 9 | string: ['single'], 10 | }); 11 | 12 | new LoadConfig(); 13 | 14 | (async () => { 15 | const web3 = Web3Factory.make(); 16 | const web3Helper = new Web3Helper(web3); 17 | await web3Helper.init(); 18 | 19 | const dumper = new DumpAll(web3Helper); 20 | if (args.single) { 21 | dumper.dumpSingle(args.single); 22 | return; 23 | } 24 | 25 | dumper.dumpAll(); 26 | })(); -------------------------------------------------------------------------------- /src/Entities/index.ts: -------------------------------------------------------------------------------- 1 | import * as sequelize from 'sequelize'; 2 | import {PositionFactory} from "./PositionFactory"; 3 | import LoadConfig from "../Services/LoadConfig"; 4 | 5 | new LoadConfig(); 6 | 7 | export const dbConfig = new sequelize.Sequelize( 8 | (process.env.DB_NAME), 9 | (process.env.DB_USER), 10 | (process.env.DB_PASSWORD), 11 | { 12 | port: Number(process.env.DB_PORT) || 5432, 13 | host: process.env.DB_HOST, 14 | dialect: "postgres", 15 | dialectModule: require('pg'), 16 | pool: { 17 | min: 0, 18 | max: 5, 19 | acquire: 30000, 20 | idle: 10000, 21 | }, 22 | logging: false, 23 | } 24 | ); 25 | 26 | export const Position = PositionFactory(dbConfig); 27 | -------------------------------------------------------------------------------- /src/Entities/Position.ts: -------------------------------------------------------------------------------- 1 | import { BuildOptions, Model } from "sequelize"; 2 | export interface PositionAttributes { 3 | id?: number; 4 | pair: string; 5 | token0: string; 6 | token1: string; 7 | spent?: string; 8 | gotToken?: string; 9 | tokenRemaining?: string; 10 | soldFor?: string; 11 | openedAt?: Date; 12 | closedAt?: Date; 13 | closeReason?: string; 14 | reserveEnter?: string; 15 | profitLoss?: string; 16 | profitLossCheckedAt?: Date; 17 | approved?: boolean; 18 | createdAt?: Date; 19 | updatedAt?: Date; 20 | } 21 | export interface PositionModel extends Model, PositionAttributes {} 22 | export class Position extends Model {} 23 | export type PositionStatic = typeof Model & { 24 | new (values?: object, options?: BuildOptions): PositionModel; 25 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "robo-ape", 3 | "version": "0.1.0", 4 | "description": "Ape of the Future", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "webpack --watch --progress", 9 | "build": "webpack --mode production --no-watch" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@types/minimist": "^1.2.1", 15 | "abi-decoder": "^2.4.0", 16 | "axios": "^0.21.1", 17 | "bignumber.js": "^9.0.1", 18 | "dotenv": "^10.0.0", 19 | "minimist": "^1.2.5", 20 | "pg": "^8.6.0", 21 | "pg-hstore": "^2.3.3", 22 | "pg-native": "^3.0.0", 23 | "sequelize": "^6.6.2", 24 | "ts-loader": "^8.0.14", 25 | "ts-node": "^9.1.1", 26 | "typescript": "^4.3.2", 27 | "web3": "^1.3.6", 28 | "webpack-cli": "^4.7.0" 29 | }, 30 | "devDependencies": { 31 | "@types/node": "^12.20.13", 32 | "null-loader": "^4.0.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | mode: "development", 5 | target: 'node', 6 | entry: { 7 | app: './src/index.ts', 8 | syncModels: './src/syncModels.ts', 9 | dumpAll: './src/dumpAll.ts', 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.tsx?$/, 15 | use: 'ts-loader', 16 | exclude: path.resolve(__dirname, 'node_modules'), 17 | }, 18 | { 19 | test: /README$/, 20 | use: 'null-loader', 21 | }, 22 | { 23 | test: /\.(cs|html)$/, 24 | use: 'null-loader', 25 | } 26 | ], 27 | }, 28 | resolve: { 29 | extensions: ['.tsx', '.ts', '.js', '.json'], 30 | }, 31 | output: { 32 | filename: '[name].js', 33 | path: path.resolve(__dirname, 'build'), 34 | }, 35 | watch: true, 36 | watchOptions: { 37 | aggregateTimeout: 200, 38 | poll: 1000, 39 | ignored: /node_modules/ 40 | }, 41 | externals: { 42 | fsevents: "require('fsevents')", 43 | electron: "require('electron')", 44 | } 45 | }; -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | # Private key of account which will do the buying 2 | ACCOUNT_PK=0x... 3 | 4 | # How much to spend for each position (in BNB) 5 | BUY_IN_AMOUNT=0.002 6 | # Default gas price (in gwei) 7 | GAS_PRICE=5 8 | # starts with either ws:// or wss:// 9 | WEB3_WS_PROVIDER=wss://bsc-ws-node.nariox.org:443 10 | 11 | # Should bot check token contracts for known show-stoppers? 12 | BSSCAN_CHECK=true 13 | # Get your free API key at bscscan.com 14 | BSCSCAN_API_KEY= 15 | # Should bot buy tokens that does not have their contract source code on bscscan.com? 16 | BSSCAN_ALLOW_UNVERIFIED_TOKENS=true 17 | 18 | # PancakeSwap addresses 19 | FACTORY_ADDRESS=0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73 20 | ROUTER_ADDRESS=0x10ED43C718714eb63d5aA57B78B54704E256024E 21 | 22 | # PostgreSQL Config 23 | DB_NAME=postgres 24 | DB_USER=postgres 25 | DB_PASSWORD=mysecretpassword 26 | DB_HOST=localhost 27 | DB_PORT=5432 28 | 29 | # Auto-sell portion of tokens when they become profitable 30 | AUTOSELL_PROFITABLE=true 31 | # Minimum profit per trade to trigger a sell (in BNB) 32 | AUTOSELL_MIN_PROFIT=0.1 33 | 34 | # Auto-sell PERCENTAGE of REMAINING TOKENS if (expected proceeds - txn fees) > AUTOSELL_MIN_PROFIT 35 | # Token deflation (tax) is not taken into account here! 36 | AUTOSELL_PERCENTAGE=20 37 | 38 | # Minimum balance to keep in wallet (BNB) 39 | # Bot will stop buying if balance drops below this threshold 40 | MIN_BALANCE=0.05 41 | 42 | # Minimum BNB reserve to Ape into (in BNB) 43 | # Bot will not buy into LPs that has less than or equal to this amount of BNB 44 | # Set to 0 to allow any 45 | MIN_RESERVE=0.001 -------------------------------------------------------------------------------- /src/Services/Pricer.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from "bignumber.js"; 2 | import Reserve from "../Models/Reserve"; 3 | 4 | export default class Pricer { 5 | public static getOutGivenIn( 6 | currentReserve: Reserve, 7 | token0In: BigNumber = null, 8 | token1In: BigNumber = null, 9 | feeMultiplier: number = 9975, 10 | ): BigNumber { 11 | if (token0In.gt(0)) { 12 | const amountInWithFee = token0In.multipliedBy(feeMultiplier); 13 | const numerator = amountInWithFee.multipliedBy(currentReserve.reserve1); 14 | const denominator = currentReserve.reserve0.multipliedBy(10000).plus(amountInWithFee); 15 | return numerator.dividedBy(denominator).integerValue(); 16 | } else if (token1In.gt(0)) { 17 | const amountInWithFee = token1In.multipliedBy(feeMultiplier); 18 | const numerator = amountInWithFee.multipliedBy(currentReserve.reserve0); 19 | const denominator = currentReserve.reserve1.multipliedBy(10000).plus(amountInWithFee); 20 | return numerator.dividedBy(denominator).integerValue(); 21 | } else { 22 | throw new Error('One of them has to be non-zero'); 23 | } 24 | } 25 | 26 | public static calcNewReserveExactIn( 27 | currentReserve: Reserve, 28 | token0In: BigNumber = null, 29 | token1In: BigNumber = null, 30 | ): Reserve { 31 | if (token0In.gt(0)) { 32 | const outWithFee = Pricer.getOutGivenIn(currentReserve, token0In, token1In); 33 | 34 | return new Reserve( 35 | currentReserve.reserve0.plus(token0In).toFixed(), 36 | currentReserve.reserve1.minus(outWithFee).toFixed(), 37 | ); 38 | } else if (token1In.gt(0)) { 39 | const outWithFee = Pricer.getOutGivenIn(currentReserve, token0In, token1In); 40 | 41 | return new Reserve( 42 | currentReserve.reserve0.minus(outWithFee).toFixed(), 43 | currentReserve.reserve1.plus(token1In).toFixed(), 44 | ); 45 | } else { 46 | throw new Error('One of them has to be non-zero'); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/Entities/PositionFactory.ts: -------------------------------------------------------------------------------- 1 | import {DataTypes, Sequelize} from "sequelize"; 2 | import { PositionStatic } from "./Position"; 3 | 4 | export function PositionFactory(sequelize: Sequelize): PositionStatic 5 | { 6 | return sequelize.define("Positions", { 7 | id: { 8 | allowNull: false, 9 | autoIncrement: true, 10 | primaryKey: true, 11 | type: DataTypes.INTEGER 12 | }, 13 | pair: { 14 | type: DataTypes.STRING, 15 | unique: true, 16 | }, 17 | token0: { 18 | type: DataTypes.STRING, 19 | }, 20 | token1: { 21 | type: DataTypes.STRING, 22 | }, 23 | spent: { 24 | type: DataTypes.DECIMAL(128,0), 25 | allowNull: true, 26 | }, 27 | gotToken: { 28 | type: DataTypes.DECIMAL(128,0), 29 | allowNull: true, 30 | }, 31 | tokenRemaining: { 32 | type: DataTypes.DECIMAL(128,0), 33 | allowNull: true, 34 | }, 35 | soldFor: { 36 | type: DataTypes.DECIMAL(128,0), 37 | allowNull: true, 38 | }, 39 | openedAt: { 40 | allowNull: true, 41 | type: DataTypes.DATE 42 | }, 43 | closedAt: { 44 | allowNull: true, 45 | type: DataTypes.DATE 46 | }, 47 | closeReason: { 48 | type: DataTypes.STRING, 49 | allowNull: true, 50 | }, 51 | reserveEnter: { 52 | type: DataTypes.DECIMAL(128,0), 53 | allowNull: true, 54 | }, 55 | profitLoss: { 56 | type: DataTypes.DECIMAL(128,0), 57 | allowNull: true, 58 | }, 59 | profitLossCheckedAt: { 60 | allowNull: true, 61 | type: DataTypes.DATE 62 | }, 63 | approved: { 64 | type: DataTypes.BOOLEAN, 65 | defaultValue: false, 66 | }, 67 | createdAt: { 68 | allowNull: false, 69 | type: DataTypes.DATE 70 | }, 71 | updatedAt: { 72 | allowNull: false, 73 | type: DataTypes.DATE 74 | } 75 | }); 76 | } -------------------------------------------------------------------------------- /src/Services/BscScan.ts: -------------------------------------------------------------------------------- 1 | import Logger from "./Logger"; 2 | import axios, {AxiosResponse} from "axios"; 3 | 4 | export default class BscScan { 5 | private logger: Logger = new Logger('BscScan'); 6 | 7 | private offendingWords: string[] = [ 8 | 'Handling Request', 9 | 'require(txoo && !bl[msg.sender])', 10 | 'FOR VALUE PROTECTION, YOU CAN ONLY SELL', 11 | 'Syntax Error. Please Re-Submit Order', 12 | 'Error: Can not sell this token', 13 | 'SLAVETAX', 14 | '"please wait"', 15 | '"Not you"', 16 | 'account is freez', 17 | 'Transaction amount exceeds the configured limit', 18 | 'Tokens cannot be transferred', 19 | 'sefhi = 2 weeks', 20 | '"Tokens are here"', 21 | '[account] = 1;', 22 | ]; 23 | 24 | public isGoodToken(token: string) { 25 | return new Promise(async (resolve, reject) => { 26 | if (process.env.BSSCAN_CHECK !== 'true') { 27 | resolve(true); 28 | return; 29 | } 30 | 31 | if (!process.env.BSCSCAN_API_KEY) { 32 | this.logger.error('BSCSCAN_API_KEY not set') 33 | process.exit(0); 34 | } 35 | 36 | let response: AxiosResponse = null; 37 | try { 38 | response = await axios.get(`https://api.bscscan.com/api?module=contract&action=getsourcecode&address=${token.toLowerCase()}&apikey=${process.env.BSCSCAN_API_KEY}`); 39 | } catch (e) { 40 | this.logger.error(`Error while testing ${token}`, e); 41 | reject(false); 42 | return; 43 | } 44 | 45 | if (response.data.message === 'OK') { 46 | for (const sourceObj of response.data.result) { 47 | if (!sourceObj.SourceCode) { 48 | if (process.env.BSSCAN_ALLOW_UNVERIFIED_TOKENS === 'true') { 49 | resolve(true); 50 | return; 51 | } 52 | 53 | this.logger.log(`${token} not verified`); 54 | reject(false); 55 | return; 56 | } 57 | 58 | for (const word of this.offendingWords) { 59 | if (sourceObj.SourceCode.indexOf(word) !== -1) { 60 | this.logger.log(`${token} contains "${word}" - a big no-no!`); 61 | reject(false); 62 | return; 63 | } 64 | } 65 | } 66 | 67 | resolve(true); 68 | } 69 | 70 | if (process.env.BSSCAN_ALLOW_UNVERIFIED_TOKENS === 'true') { 71 | resolve(true); 72 | return; 73 | } 74 | 75 | if (response.data.message === 'NOTOK' && response.data.result === 'Contract source code not verified') { 76 | this.logger.log(`${token} not verified`); 77 | reject(false); 78 | return; 79 | } 80 | 81 | reject(false); 82 | }); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Services/DumpAll.ts: -------------------------------------------------------------------------------- 1 | import Web3Helper from "./Web3Helper"; 2 | import {PositionModel} from "../Entities/Position"; 3 | import Pricer from "./Pricer"; 4 | import {Symbols} from "../Models/Symbols"; 5 | import BigNumber from "bignumber.js"; 6 | import {toWei} from "web3-utils"; 7 | import {GasEstimates} from "../Models/GasEstimates"; 8 | import AutoSell from "./AutoSell"; 9 | import Logger from "./Logger"; 10 | import {Op} from "sequelize"; 11 | import {Position} from "../Entities"; 12 | 13 | export default class DumpAll { 14 | private defaultGas = toWei(process.env.GAS_PRICE, 'gwei'); 15 | private estimatedTxnFees: BigNumber = null; 16 | private autoSell: AutoSell = null; 17 | private logger = new Logger('DumpAll'); 18 | 19 | constructor( 20 | private web3Helper: Web3Helper, 21 | ) { 22 | this.autoSell = new AutoSell(this.web3Helper); 23 | 24 | this.estimatedTxnFees = new BigNumber(GasEstimates.approve).multipliedBy(this.defaultGas).plus( 25 | new BigNumber(GasEstimates.swap).multipliedBy(this.defaultGas), 26 | ); 27 | } 28 | 29 | public dumpAll() { 30 | Position.findAll({ 31 | where: { 32 | tokenRemaining: { [Op.gt]: 0 }, 33 | openedAt: { [Op.ne]: null }, 34 | closedAt: { [Op.eq]: null }, 35 | } 36 | }).then((positions) => { 37 | for (const position of positions) { 38 | this.dump(position); 39 | } 40 | }); 41 | } 42 | 43 | public dumpSingle(pair: string) 44 | { 45 | Position.findOne({ 46 | where: { 47 | pair: pair, 48 | }, 49 | }).then((position) => { 50 | this.dump(position) 51 | .then((dumped) => { 52 | if (!dumped) { 53 | this.logger.error(`Failed to dump ${pair} - not profitable`); 54 | return; 55 | } 56 | }) 57 | .catch((error) => { 58 | this.logger.error(`Error while dumping: ${error.message}`); 59 | }); 60 | }); 61 | } 62 | 63 | private dump(position: PositionModel) { 64 | return new Promise(async (resolve) => { 65 | const reserve = await this.web3Helper.getReserve(position.pair); 66 | 67 | if (reserve.reserve0.eq(0) && reserve.reserve1.eq(0)) { 68 | resolve(false); 69 | return; 70 | } 71 | 72 | const bnbOut = Pricer.getOutGivenIn( 73 | reserve, 74 | position.token0 === Symbols.wbnb ? new BigNumber(0) : new BigNumber(position.tokenRemaining), 75 | position.token0 === Symbols.wbnb ? new BigNumber(position.tokenRemaining) : new BigNumber(0), 76 | ); 77 | 78 | if (bnbOut.minus(this.estimatedTxnFees).lte(0)) { 79 | resolve(false); 80 | return; 81 | } 82 | 83 | this.logger.log(`Dumping ${position.pair}`); 84 | this.autoSell.sellIfProfitable(position, true); 85 | 86 | resolve(true); 87 | }); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Services/WatchNewPairs.ts: -------------------------------------------------------------------------------- 1 | import Web3 from "web3"; 2 | import {Topics} from "../Models/Topics"; 3 | import Logger from "./Logger"; 4 | import {Log} from "web3-core"; 5 | import AbiUtils from "./AbiUtils"; 6 | import {Symbols} from "../Models/Symbols"; 7 | import Ape from "./Ape"; 8 | import Web3Helper from "./Web3Helper"; 9 | import {fromWei, toWei} from "web3-utils"; 10 | 11 | export default class WatchNewPairs { 12 | private logger: Logger = new Logger('WatchNewPairs'); 13 | private abiDecoder = require('abi-decoder'); 14 | 15 | private minBalance = toWei(process.env.MIN_BALANCE); 16 | private minReserve = toWei(process.env.MIN_RESERVE); 17 | private sufficientBalance = false; 18 | 19 | constructor( 20 | private web3: Web3, 21 | private web3Helper: Web3Helper 22 | ) { 23 | this.abiDecoder.addABI(require('../ABIs/IPancakeFactoryV2.json')); 24 | this.abiDecoder.addABI(require('../ABIs/IPancakeRouterV2.json')); 25 | 26 | this.connect(); 27 | this.checkBalance(); 28 | } 29 | 30 | private connect() { 31 | this.web3.eth.subscribe('logs', { 32 | address: process.env.FACTORY_ADDRESS, 33 | topics: [Topics.PairCreated], 34 | }) 35 | .on('data', (log) => { 36 | this.handleLogs(log).catch((e: any) => { 37 | this.logger.error(`Error handling log`, e); 38 | }); 39 | }) 40 | .on('connected', () => { 41 | this.logger.log('Listening to logs'); 42 | }) 43 | .on('error', (error) => { 44 | this.logger.error(`Unexpected error ${error.message}`); 45 | this.connect(); 46 | }); 47 | } 48 | 49 | private async handleLogs(log: Log) { 50 | if (!this.sufficientBalance) { 51 | return; 52 | } 53 | 54 | const decoded = this.abiDecoder.decodeLogs([log]); 55 | const values = AbiUtils.decodedEventsToArray(decoded[0]); 56 | 57 | if (values.token0 !== Symbols.wbnb && values.token1 !== Symbols.wbnb) { 58 | // Non-WBNB pairs are not supported 59 | return; 60 | } 61 | 62 | // verify reserve 63 | const reserve = await this.web3Helper.getReserve(values.pair); 64 | const bnbReserve = values.token0 === Symbols.wbnb ? reserve.reserve0 : reserve.reserve1; 65 | 66 | this.logger.log(`New pair created: ${values.pair}. BNB reserve: ${fromWei(bnbReserve.toFixed())}.`); 67 | 68 | if (bnbReserve.lte(this.minReserve)) { 69 | return; 70 | } 71 | 72 | const ape = new Ape( 73 | this.web3Helper, 74 | values.pair, 75 | values.token0, 76 | values.token1, 77 | reserve, 78 | ); 79 | ape.in(); 80 | } 81 | 82 | private checkBalance() { 83 | this.web3Helper.accountBalance() 84 | .then((balance) => { 85 | this.logger.log(`Current account balance: ${fromWei(balance.toFixed())} BNB`); 86 | 87 | this.sufficientBalance = balance.gt(this.minBalance); 88 | }) 89 | .catch((error) => { 90 | this.logger.error(`Error while checking balance: ${error.message}`); 91 | }); 92 | 93 | setTimeout(() => { 94 | this.checkBalance(); 95 | }, 60*1000); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Services/AutoSell.ts: -------------------------------------------------------------------------------- 1 | import Web3Helper from "./Web3Helper"; 2 | import BigNumber from "bignumber.js"; 3 | import {PositionModel} from "../Entities/Position"; 4 | import Logger from "./Logger"; 5 | import {fromWei, toWei} from "web3-utils"; 6 | import {GasEstimates} from "../Models/GasEstimates"; 7 | import {Symbols} from "../Models/Symbols"; 8 | 9 | export default class AutoSell 10 | { 11 | private enabled = process.env.AUTOSELL_PROFITABLE === 'true'; 12 | private minProfit = new BigNumber(toWei(process.env.AUTOSELL_MIN_PROFIT)); 13 | private sellPercentage: number = Number(process.env.AUTOSELL_PERCENTAGE); 14 | private defaultGas = toWei(process.env.GAS_PRICE, 'gwei'); 15 | private estimatedTxnFees: BigNumber = null; 16 | 17 | private logger = new Logger('AutoSell'); 18 | 19 | constructor( 20 | private web3Helper: Web3Helper 21 | ) { 22 | this.estimatedTxnFees = new BigNumber(GasEstimates.approve).multipliedBy(this.defaultGas).plus( 23 | new BigNumber(GasEstimates.swap).multipliedBy(this.defaultGas), 24 | ); 25 | } 26 | 27 | public sellIfProfitable(position: PositionModel, dumpAll: boolean = false) 28 | { 29 | if (!this.enabled) { 30 | return; 31 | } 32 | 33 | const sellPercentage = dumpAll ? 100 : this.sellPercentage; 34 | 35 | const expectedProceeds = new BigNumber(position.profitLoss) 36 | .multipliedBy(sellPercentage) 37 | .dividedBy(100) 38 | .minus(this.estimatedTxnFees); 39 | 40 | const minProfit = position.soldFor ? new BigNumber(position.soldFor) : this.minProfit; 41 | 42 | if (!dumpAll && expectedProceeds.lt(minProfit)) { 43 | return; 44 | } 45 | 46 | this.sell(position, sellPercentage, dumpAll) 47 | .catch((error) => { 48 | this.logger.log(`Error while selling ${position.pair}: ${error.message}`); 49 | }) 50 | } 51 | 52 | private async sell(position: PositionModel, sellPercentage: number, dumpAll: boolean) { 53 | const token = position.token0 === Symbols.wbnb ? position.token1 : position.token0; 54 | 55 | if (!position.approved) { 56 | await this.web3Helper.approve(token, '-1'); 57 | } 58 | 59 | const sellTokens = new BigNumber(position.tokenRemaining).multipliedBy(sellPercentage).dividedBy(100).integerValue(); 60 | try { 61 | const sold = await this.web3Helper.swapExactTokensForETHSupportingFeeOnTransferTokens(token, sellTokens.toFixed()); 62 | const remainder = await this.web3Helper.balanceOf(token); 63 | const previousSoldFor = new BigNumber(position.soldFor ?? 0); 64 | const totalSoldFor = previousSoldFor.plus(sold); 65 | 66 | if (sellPercentage === 100) { 67 | await position.update({ 68 | tokenRemaining: remainder.toFixed(), 69 | soldFor: totalSoldFor.toFixed(), 70 | approved: true, 71 | closedAt: new Date(), 72 | closeReason: dumpAll ? 'dump-all' : 'sell-all', 73 | }); 74 | } else { 75 | await position.update({ 76 | tokenRemaining: remainder.toFixed(), 77 | soldFor: totalSoldFor.toFixed(), 78 | approved: true, 79 | }); 80 | } 81 | 82 | this.logger.log(`Sold ${sellPercentage}% of ${token} for ${fromWei(sold.toFixed())} BNB (total so far: ${fromWei(totalSoldFor.toFixed())} BNB)`); 83 | } catch (error) { 84 | await position.update({ 85 | closedAt: new Date(), 86 | closeReason: 'error', 87 | }); 88 | 89 | this.logger.log(`Error while selling ${position.pair}: ${error.message}`); 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /src/Services/Ape.ts: -------------------------------------------------------------------------------- 1 | import Logger from "./Logger"; 2 | import BscScan from "./BscScan"; 3 | import {Symbols} from "../Models/Symbols"; 4 | import {Position} from "../Entities"; 5 | import Web3Helper from "./Web3Helper"; 6 | import {toWei} from "web3-utils"; 7 | import Reserve from "../Models/Reserve"; 8 | import BigNumber from "bignumber.js"; 9 | 10 | export default class Ape { 11 | private logger: Logger = new Logger('Ape'); 12 | private defaultBuyIn = toWei(process.env.BUY_IN_AMOUNT); 13 | 14 | constructor( 15 | private web3Helper: Web3Helper, 16 | private pair: string, 17 | private token0: string, 18 | private token1: string, 19 | private initialReserve: Reserve, 20 | ) { 21 | } 22 | 23 | public in() { 24 | const bscScan = new BscScan(); 25 | bscScan.isGoodToken(this.getOtherSideToken()) 26 | .then(() => { 27 | this.buy(); 28 | }) 29 | .catch(() => { 30 | // do nothing 31 | }); 32 | } 33 | 34 | public buy() { 35 | // check if we haven't already bought this pair 36 | Position.findOne({ 37 | where: { 38 | pair: this.pair, 39 | }, 40 | }).then((result) => { 41 | if (result !== null) { 42 | return; 43 | } 44 | 45 | Position.build({ 46 | pair: this.pair, 47 | token0: this.token0, 48 | token1: this.token1, 49 | profitLossCheckedAt: new Date(), 50 | reserveEnter: this.getReserveAmount(this.initialReserve).toFixed(), 51 | }).save(); 52 | 53 | this.logger.log(`Apeing into ${this.pair}`); 54 | 55 | this.web3Helper.swapExactETHForTokens(this.getOtherSideToken(), this.defaultBuyIn) 56 | .then(async (received) => { 57 | const position = await Position.findOne({ 58 | where: { 59 | pair: this.pair, 60 | } 61 | }); 62 | if (!position) { 63 | this.logger.error(`Position not found in DB for ${this.pair}`); 64 | return; 65 | } 66 | 67 | 68 | await position.update({ 69 | spent: this.defaultBuyIn, 70 | gotToken: received.toFixed(), 71 | tokenRemaining: received.toFixed(), 72 | openedAt: new Date(), 73 | }); 74 | 75 | this.logger.log(`Position opened for ${this.pair}`); 76 | }) 77 | .catch(async (error) => { 78 | const position = await Position.findOne({ 79 | where: { 80 | pair: this.pair, 81 | } 82 | }); 83 | if (!position) { 84 | this.logger.error(`Position not found in DB for ${this.pair}`); 85 | return; 86 | } 87 | 88 | await position.update({ 89 | closedAt: new Date(), 90 | closeReason: 'open-error', 91 | }); 92 | 93 | this.logger.log(`Failed to open position for ${this.pair}, marking as closed`); 94 | }); 95 | }); 96 | } 97 | 98 | private getReserveAmount(reserve: Reserve): BigNumber { 99 | return this.token0 === Symbols.wbnb ? reserve.reserve0 : reserve.reserve1; 100 | } 101 | 102 | private getOtherSideToken() { 103 | return this.token0 === Symbols.wbnb ? this.token1 : this.token0; 104 | } 105 | } -------------------------------------------------------------------------------- /src/Services/ProfitLossManager.ts: -------------------------------------------------------------------------------- 1 | import Logger from "./Logger"; 2 | import {Position} from "../Entities"; 3 | import {Op} from "sequelize"; 4 | import Web3Helper from "./Web3Helper"; 5 | import Pricer from "./Pricer"; 6 | import {Symbols} from "../Models/Symbols"; 7 | import BigNumber from "bignumber.js"; 8 | import {fromWei} from "web3-utils"; 9 | import {PositionAttributes} from "../Entities/Position"; 10 | import AutoSell from "./AutoSell"; 11 | 12 | export default class ProfitLossManager { 13 | private logger: Logger = new Logger('ProfitLossManager'); 14 | private autoSell: AutoSell = null; 15 | 16 | constructor( 17 | private web3Helper: Web3Helper, 18 | ) { 19 | this.autoSell = new AutoSell(this.web3Helper); 20 | 21 | setInterval(() => { 22 | this.check(); 23 | }, 60 * 1000); 24 | } 25 | 26 | private check() { 27 | const date = new Date(); 28 | date.setMinutes(date.getMinutes() - 30); 29 | 30 | let bestProfit: BigNumber = null; 31 | 32 | Position.findAll({ 33 | where: { 34 | openedAt: { [Op.ne]: null }, 35 | closedAt: { [Op.eq]: null }, 36 | profitLossCheckedAt: { [Op.lte]: date }, 37 | } 38 | }).then((positions) => { 39 | const toCheck = positions.length; 40 | let checked = 0; 41 | 42 | this.logger.log(`Have ${toCheck} positions to check`); 43 | 44 | positions.forEach(async (position) => { 45 | const tokensRemaining = await this.web3Helper.balanceOf(position.token0 === Symbols.wbnb ? position.token1 : position.token0); 46 | if (tokensRemaining.eq(0)) { 47 | this.logger.error(`0 tokens remaining for ${position.pair}`); 48 | return; 49 | } 50 | 51 | const reserve = await this.web3Helper.getReserve(position.pair); 52 | 53 | const bnbReserve = position.token0 === Symbols.wbnb ? reserve.reserve0 : reserve.reserve1; 54 | const bnbReserveRemaining = bnbReserve.multipliedBy(100).dividedBy(position.reserveEnter); 55 | 56 | const bnbOut = Pricer.getOutGivenIn( 57 | reserve, 58 | position.token0 === Symbols.wbnb ? new BigNumber(0) : tokensRemaining, 59 | position.token0 === Symbols.wbnb ? tokensRemaining : new BigNumber(0), 60 | ); 61 | 62 | const profitLoss = bnbOut.minus(position.spent); 63 | 64 | const updateFields: PositionAttributes = { 65 | pair: position.pair, 66 | token0: position.token0, 67 | token1: position.token1, 68 | 69 | profitLoss: profitLoss.toFixed(), 70 | profitLossCheckedAt: new Date(), 71 | tokenRemaining: tokensRemaining.toFixed(), 72 | }; 73 | 74 | if (bnbReserveRemaining.lte(0.5) && profitLoss.lte(0)) { 75 | // less than 0.5% of initial BNB reserve remaining - calling it a rug pull 76 | updateFields.closedAt = new Date(); 77 | updateFields.closeReason = 'rug'; 78 | 79 | this.logger.log(`Marking ${position.pair} as a rug (remainder of original BNB reserve: ${bnbReserveRemaining.toFixed(2)}%)`) 80 | } 81 | 82 | await position.update(updateFields); 83 | 84 | if (bnbReserveRemaining.gt(0.5)) { 85 | this.autoSell.sellIfProfitable(position); 86 | } 87 | 88 | if (bestProfit === null || profitLoss.gt(bestProfit)) { 89 | bestProfit = profitLoss; 90 | } 91 | 92 | checked++; 93 | if (checked === toCheck && bestProfit !== null) { 94 | this.logger.log(`Best profit of all those: ${fromWei(bestProfit.toFixed())}`); 95 | } 96 | }); 97 | }); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Robo Ape 2 | 3 | A simple bot which will buy into every new BNB pair created on PancakeSwap. Built by a programmer for other programmers. 4 | 5 | ## Motivation 6 | 7 | Every day, every hour, almost every minute there are new LPs (liquidity pool) created on [PancakeSwap](https://bscscan.com/txsInternal?a=0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73&p=1). Almost all of them (probably 99%) are rug pulls (meaning 100% loss), but the remaining 1% is worth it in the long run. 8 | 9 | Open any newly created LP on PancakeSwap and you will see multiple transactions buying 0.002 BNB worth of tokens right away. This bot does the same thing - buys in as early as possible and later on sells in smalls portions (when profitable). 10 | 11 | The idea is that given enough time, bot will build up enough good tokens to generate constant revenue. But to get there you will need to first burn through those rug pulls. 12 | 13 | ## Bot logic 14 | 15 | - Listen for `PairCreated` events on PancakeSwap Factory contract. 16 | - Optionally (`BSSCAN_CHECK`) see if Token's smart contract is verified and if source code contains known "bad words" which we don't want buying into (see `src/Services/BscScan.ts`). 17 | - If pair (LP) is between BNB and any other token and BNB reserve is > 0 - buy in (`BUY_IN_AMOUNT`). 18 | - Every 30 minutes check all pairs that were not already rug pulled and: 19 | - If less than 0.5% of initial BNB reserve is remaining and current position is not profitable - mark possition as `rug`. 20 | - Check if 20% (`AUTOSELL_PERCENTAGE`) of our tokens is more than 0.1 BNB (`AUTOSELL_MIN_PROFIT`) and if so - sell those 20%. 21 | - Rinse & repeat. 22 | 23 | Bot will stop buying into new positions if it's balance drops below 0.05 BNB (`MIN_BALANCE`). 24 | 25 | ## Requirements 26 | 27 | - Node (developed and tested on v15.4.0) 28 | - PostgreSQL (developed and tested ov v13.3) 29 | - NPM 30 | 31 | ## Build & Install 32 | 33 | Copy and edit `.env` file, install `npm` packages, `build`, `syncModels` to initiate DB. 34 | 35 | ```bash 36 | cp .env.sample .env 37 | npm install 38 | npm run build 39 | node build/syncModels.js 40 | ``` 41 | 42 | NOTE: you _might_ also need to install `libpq-dev` or `postgresql-devel` or `libpq-devel` (depending on your OS) and `make` with `g++`: 43 | ```bash 44 | apt install libpq-dev make g++ 45 | ``` 46 | 47 | ## Local development 48 | 49 | If you need PostgreSQL you can spin it up quickly using Docker: 50 | 51 | ```bash 52 | docker run --name roboape-db -e POSTGRES_PASSWORD=mysecretpassword -p 5432:5432 -d postgres 53 | ``` 54 | 55 | Instead of `npm run build` you can use `npm run dev` to watch files for changes and compile on-the-fly. 56 | 57 | ## Usage 58 | 59 | Don't forget to set your private key (`ACCOUNT_PK`), BscScan.com API KEY (`BSCSCAN_API_KEY`) (if `BSSCAN_CHECK=true`) and database credentials in `.env` file! 60 | 61 | To run bot: 62 | ```bash 63 | node build/app.js 64 | ``` 65 | 66 | To dump all profitable tokens: 67 | ```bash 68 | node build/dumpAll.js 69 | ``` 70 | 71 | To dump single profitable token provide LP pair address: 72 | ```bash 73 | node build/dumpAll.js --single 0xLpPairAddressHere 74 | ``` 75 | 76 | If you want to keep bot running in background, a very easy way is to use PM2 (`npm install -g pm2`): 77 | ```bash 78 | pm2 start pm2.json 79 | ``` 80 | 81 | ## Notes 82 | 83 | - Bot supports, but does not account for deflation ("fee on token transfer" inside of token contract) 84 | - Bot is optimised for PancakeSwap v2, but can be very easily adjusted to work with any other PancakeSwap (UniSwap) fork. 85 | - No UI is provided, this is a console application. 86 | 87 | ## Warning 88 | 89 | - This bot comes with no warranty - use at your own risk! 90 | - Most of the trade will not be profitable! (see "Motivation" section) 91 | 92 | ## Examples 93 | 94 | Several examples of profitable trades: 95 | - https://bscscan.com/token/0x5f8ed6eaf2e26a63dddc9baa2bb0a9bf97e6ca13?a=0x3c1acf05c4dcc8ef94cf7ff48f64f5757aafb9db 96 | - https://bscscan.com/token/0x7c0e2f47207ea0619aaf2381ce846dbae978b7ad?a=0x74ab14b94a1b98c8ac116310189e7f121eaea56a 97 | - https://bscscan.com/token/0xac7d00ca9e5809584e78d11b68f6b9e9257c0d05?a=0x7c3270c1a6d91a77dce9354d47a36df1e9c5c258 98 | - https://bscscan.com/token/0xba42fd16cee5e860c2ffc1a3ff1d37e1802bbffa?a=0x3c1acf05c4dcc8ef94cf7ff48f64f5757aafb9db 99 | - https://bscscan.com/token/0xd06f0b5b04f3bc0062c69d73d98c5c9ccfebe9bb?a=0x3c1acf05c4dcc8ef94cf7ff48f64f5757aafb9db 100 | - etc... 101 | -------------------------------------------------------------------------------- /src/ABIs/IPancakeFactoryV2.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "address", 6 | "name": "_feeToSetter", 7 | "type": "address" 8 | } 9 | ], 10 | "payable": false, 11 | "stateMutability": "nonpayable", 12 | "type": "constructor" 13 | }, 14 | { 15 | "anonymous": false, 16 | "inputs": [ 17 | { 18 | "indexed": true, 19 | "internalType": "address", 20 | "name": "token0", 21 | "type": "address" 22 | }, 23 | { 24 | "indexed": true, 25 | "internalType": "address", 26 | "name": "token1", 27 | "type": "address" 28 | }, 29 | { 30 | "indexed": false, 31 | "internalType": "address", 32 | "name": "pair", 33 | "type": "address" 34 | }, 35 | { 36 | "indexed": false, 37 | "internalType": "uint256", 38 | "name": "", 39 | "type": "uint256" 40 | } 41 | ], 42 | "name": "PairCreated", 43 | "type": "event" 44 | }, 45 | { 46 | "constant": true, 47 | "inputs": [], 48 | "name": "INIT_CODE_PAIR_HASH", 49 | "outputs": [ 50 | { 51 | "internalType": "bytes32", 52 | "name": "", 53 | "type": "bytes32" 54 | } 55 | ], 56 | "payable": false, 57 | "stateMutability": "view", 58 | "type": "function" 59 | }, 60 | { 61 | "constant": true, 62 | "inputs": [ 63 | { 64 | "internalType": "uint256", 65 | "name": "", 66 | "type": "uint256" 67 | } 68 | ], 69 | "name": "allPairs", 70 | "outputs": [ 71 | { 72 | "internalType": "address", 73 | "name": "", 74 | "type": "address" 75 | } 76 | ], 77 | "payable": false, 78 | "stateMutability": "view", 79 | "type": "function" 80 | }, 81 | { 82 | "constant": true, 83 | "inputs": [], 84 | "name": "allPairsLength", 85 | "outputs": [ 86 | { 87 | "internalType": "uint256", 88 | "name": "", 89 | "type": "uint256" 90 | } 91 | ], 92 | "payable": false, 93 | "stateMutability": "view", 94 | "type": "function" 95 | }, 96 | { 97 | "constant": false, 98 | "inputs": [ 99 | { 100 | "internalType": "address", 101 | "name": "tokenA", 102 | "type": "address" 103 | }, 104 | { 105 | "internalType": "address", 106 | "name": "tokenB", 107 | "type": "address" 108 | } 109 | ], 110 | "name": "createPair", 111 | "outputs": [ 112 | { 113 | "internalType": "address", 114 | "name": "pair", 115 | "type": "address" 116 | } 117 | ], 118 | "payable": false, 119 | "stateMutability": "nonpayable", 120 | "type": "function" 121 | }, 122 | { 123 | "constant": true, 124 | "inputs": [], 125 | "name": "feeTo", 126 | "outputs": [ 127 | { 128 | "internalType": "address", 129 | "name": "", 130 | "type": "address" 131 | } 132 | ], 133 | "payable": false, 134 | "stateMutability": "view", 135 | "type": "function" 136 | }, 137 | { 138 | "constant": true, 139 | "inputs": [], 140 | "name": "feeToSetter", 141 | "outputs": [ 142 | { 143 | "internalType": "address", 144 | "name": "", 145 | "type": "address" 146 | } 147 | ], 148 | "payable": false, 149 | "stateMutability": "view", 150 | "type": "function" 151 | }, 152 | { 153 | "constant": true, 154 | "inputs": [ 155 | { 156 | "internalType": "address", 157 | "name": "", 158 | "type": "address" 159 | }, 160 | { 161 | "internalType": "address", 162 | "name": "", 163 | "type": "address" 164 | } 165 | ], 166 | "name": "getPair", 167 | "outputs": [ 168 | { 169 | "internalType": "address", 170 | "name": "", 171 | "type": "address" 172 | } 173 | ], 174 | "payable": false, 175 | "stateMutability": "view", 176 | "type": "function" 177 | }, 178 | { 179 | "constant": false, 180 | "inputs": [ 181 | { 182 | "internalType": "address", 183 | "name": "_feeTo", 184 | "type": "address" 185 | } 186 | ], 187 | "name": "setFeeTo", 188 | "outputs": [], 189 | "payable": false, 190 | "stateMutability": "nonpayable", 191 | "type": "function" 192 | }, 193 | { 194 | "constant": false, 195 | "inputs": [ 196 | { 197 | "internalType": "address", 198 | "name": "_feeToSetter", 199 | "type": "address" 200 | } 201 | ], 202 | "name": "setFeeToSetter", 203 | "outputs": [], 204 | "payable": false, 205 | "stateMutability": "nonpayable", 206 | "type": "function" 207 | } 208 | ] 209 | -------------------------------------------------------------------------------- /src/ABIs/IBEP20.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "address", 8 | "name": "owner", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": true, 13 | "internalType": "address", 14 | "name": "spender", 15 | "type": "address" 16 | }, 17 | { 18 | "indexed": false, 19 | "internalType": "uint256", 20 | "name": "value", 21 | "type": "uint256" 22 | } 23 | ], 24 | "name": "Approval", 25 | "type": "event" 26 | }, 27 | { 28 | "anonymous": false, 29 | "inputs": [ 30 | { 31 | "indexed": true, 32 | "internalType": "address", 33 | "name": "from", 34 | "type": "address" 35 | }, 36 | { 37 | "indexed": true, 38 | "internalType": "address", 39 | "name": "to", 40 | "type": "address" 41 | }, 42 | { 43 | "indexed": false, 44 | "internalType": "uint256", 45 | "name": "value", 46 | "type": "uint256" 47 | } 48 | ], 49 | "name": "Transfer", 50 | "type": "event" 51 | }, 52 | { 53 | "constant": true, 54 | "inputs": [ 55 | { 56 | "internalType": "address", 57 | "name": "_owner", 58 | "type": "address" 59 | }, 60 | { 61 | "internalType": "address", 62 | "name": "spender", 63 | "type": "address" 64 | } 65 | ], 66 | "name": "allowance", 67 | "outputs": [ 68 | { 69 | "internalType": "uint256", 70 | "name": "", 71 | "type": "uint256" 72 | } 73 | ], 74 | "payable": false, 75 | "stateMutability": "view", 76 | "type": "function" 77 | }, 78 | { 79 | "constant": false, 80 | "inputs": [ 81 | { 82 | "internalType": "address", 83 | "name": "spender", 84 | "type": "address" 85 | }, 86 | { 87 | "internalType": "uint256", 88 | "name": "amount", 89 | "type": "uint256" 90 | } 91 | ], 92 | "name": "approve", 93 | "outputs": [ 94 | { 95 | "internalType": "bool", 96 | "name": "", 97 | "type": "bool" 98 | } 99 | ], 100 | "payable": false, 101 | "stateMutability": "nonpayable", 102 | "type": "function" 103 | }, 104 | { 105 | "constant": true, 106 | "inputs": [ 107 | { 108 | "internalType": "address", 109 | "name": "account", 110 | "type": "address" 111 | } 112 | ], 113 | "name": "balanceOf", 114 | "outputs": [ 115 | { 116 | "internalType": "uint256", 117 | "name": "", 118 | "type": "uint256" 119 | } 120 | ], 121 | "payable": false, 122 | "stateMutability": "view", 123 | "type": "function" 124 | }, 125 | { 126 | "constant": true, 127 | "inputs": [], 128 | "name": "decimals", 129 | "outputs": [ 130 | { 131 | "internalType": "uint256", 132 | "name": "", 133 | "type": "uint256" 134 | } 135 | ], 136 | "payable": false, 137 | "stateMutability": "view", 138 | "type": "function" 139 | }, 140 | { 141 | "constant": true, 142 | "inputs": [], 143 | "name": "getOwner", 144 | "outputs": [ 145 | { 146 | "internalType": "address", 147 | "name": "", 148 | "type": "address" 149 | } 150 | ], 151 | "payable": false, 152 | "stateMutability": "view", 153 | "type": "function" 154 | }, 155 | { 156 | "constant": true, 157 | "inputs": [], 158 | "name": "name", 159 | "outputs": [ 160 | { 161 | "internalType": "string", 162 | "name": "", 163 | "type": "string" 164 | } 165 | ], 166 | "payable": false, 167 | "stateMutability": "view", 168 | "type": "function" 169 | }, 170 | { 171 | "constant": true, 172 | "inputs": [], 173 | "name": "symbol", 174 | "outputs": [ 175 | { 176 | "internalType": "string", 177 | "name": "", 178 | "type": "string" 179 | } 180 | ], 181 | "payable": false, 182 | "stateMutability": "view", 183 | "type": "function" 184 | }, 185 | { 186 | "constant": true, 187 | "inputs": [], 188 | "name": "totalSupply", 189 | "outputs": [ 190 | { 191 | "internalType": "uint256", 192 | "name": "", 193 | "type": "uint256" 194 | } 195 | ], 196 | "payable": false, 197 | "stateMutability": "view", 198 | "type": "function" 199 | }, 200 | { 201 | "constant": false, 202 | "inputs": [ 203 | { 204 | "internalType": "address", 205 | "name": "recipient", 206 | "type": "address" 207 | }, 208 | { 209 | "internalType": "uint256", 210 | "name": "amount", 211 | "type": "uint256" 212 | } 213 | ], 214 | "name": "transfer", 215 | "outputs": [ 216 | { 217 | "internalType": "bool", 218 | "name": "", 219 | "type": "bool" 220 | } 221 | ], 222 | "payable": false, 223 | "stateMutability": "nonpayable", 224 | "type": "function" 225 | }, 226 | { 227 | "constant": false, 228 | "inputs": [ 229 | { 230 | "internalType": "address", 231 | "name": "sender", 232 | "type": "address" 233 | }, 234 | { 235 | "internalType": "address", 236 | "name": "recipient", 237 | "type": "address" 238 | }, 239 | { 240 | "internalType": "uint256", 241 | "name": "amount", 242 | "type": "uint256" 243 | } 244 | ], 245 | "name": "transferFrom", 246 | "outputs": [ 247 | { 248 | "internalType": "bool", 249 | "name": "", 250 | "type": "bool" 251 | } 252 | ], 253 | "payable": false, 254 | "stateMutability": "nonpayable", 255 | "type": "function" 256 | } 257 | ] -------------------------------------------------------------------------------- /src/Services/Web3Helper.ts: -------------------------------------------------------------------------------- 1 | import Web3 from "web3"; 2 | import {Account, TransactionReceipt, TransactionConfig} from "web3-core"; 3 | import {fromWei, toWei} from "web3-utils"; 4 | import Logger from "./Logger"; 5 | import BigNumber from "bignumber.js"; 6 | import AbiUtils from "./AbiUtils"; 7 | import {Symbols} from "../Models/Symbols"; 8 | import Reserve from "../Models/Reserve"; 9 | 10 | export default class Web3Helper { 11 | private account: Account = null; 12 | private nonce: number = null; 13 | private logger: Logger = new Logger('Web3Helper'); 14 | private routerAddress: string = process.env.ROUTER_ADDRESS; 15 | private abiDecoder = require('abi-decoder'); 16 | private defaultGas = toWei(process.env.GAS_PRICE, 'gwei'); 17 | 18 | constructor( 19 | private web3: Web3 20 | ) { 21 | this.abiDecoder.addABI(require('../ABIs/IPancakePair.json')); 22 | } 23 | 24 | public async init() { 25 | this.account = this.web3.eth.accounts.privateKeyToAccount(process.env.ACCOUNT_PK); 26 | this.nonce = await this.web3.eth.getTransactionCount(this.account.address); 27 | 28 | this.logger.log(`Nonce: ${this.nonce}`); 29 | } 30 | 31 | public swapExactETHForTokens(token: string, amount: string) { 32 | return new Promise((resolve, reject) => { 33 | const contract = this.routerContract(); 34 | const methodCall = contract.methods.swapExactETHForTokens( 35 | '0', 36 | [Symbols.wbnb, token], 37 | this.account.address, 38 | this.deadline(), 39 | ); 40 | 41 | this.sendSigned(this.account, this.routerAddress, '500000', this.defaultGas, methodCall, amount) 42 | .then((receipt) => { 43 | const decodedLogs = this.abiDecoder.decodeLogs(receipt.logs); 44 | const swapped = this.getSwappedAmount(decodedLogs); 45 | if (swapped) { 46 | resolve(swapped); 47 | return; 48 | } 49 | 50 | this.logger.error(`Failed to decode swapped amount for txn ${receipt.transactionHash}`); 51 | }) 52 | .catch((error) => { 53 | reject(error); 54 | }); 55 | }); 56 | } 57 | 58 | public accountBalance() { 59 | return new Promise((resolve, reject) => { 60 | this.web3.eth.getBalance(this.account.address) 61 | .then((balance) => { 62 | resolve(new BigNumber(balance)); 63 | }) 64 | .catch((error) => { 65 | reject(error); 66 | }); 67 | }); 68 | } 69 | 70 | public balanceOf(token: string) { 71 | return new Promise((resolve, reject) => { 72 | const contract = this.tokenContract(token); 73 | contract.methods.balanceOf(this.account.address).call() 74 | .then((result: string) => { 75 | resolve(new BigNumber(result)); 76 | }) 77 | .catch((error: any) => { 78 | reject(error); 79 | }) 80 | }); 81 | } 82 | 83 | public approve(token: string, amount: string) { 84 | if (amount === '-1') { 85 | // MAX_INT 86 | amount = '115792089237316195423570985008687907853269984665640564039457584007913129639935'; 87 | } 88 | 89 | return new Promise((resolve, reject) => { 90 | const contract = this.tokenContract(token); 91 | const methodCall = contract.methods.approve( 92 | this.routerAddress, 93 | amount, 94 | ); 95 | 96 | this.sendSigned(this.account, token, '150000', this.defaultGas, methodCall) 97 | .then((receipt) => { 98 | resolve(receipt); 99 | }) 100 | .catch((error) => { 101 | reject(error); 102 | }) 103 | }); 104 | } 105 | 106 | public swapExactTokensForETHSupportingFeeOnTransferTokens(token: string, amount: string) { 107 | return new Promise((resolve, reject) => { 108 | const contract = this.routerContract(); 109 | const methodCall = contract.methods.swapExactTokensForETHSupportingFeeOnTransferTokens( 110 | amount, 111 | '0', 112 | [token, Symbols.wbnb], 113 | this.account.address, 114 | this.deadline(), 115 | ); 116 | 117 | this.sendSigned(this.account, this.routerAddress, '500000', this.defaultGas, methodCall) 118 | .then((receipt) => { 119 | const decodedLogs = this.abiDecoder.decodeLogs(receipt.logs); 120 | const swapped = this.getSwappedAmount(decodedLogs); 121 | if (swapped) { 122 | resolve(swapped); 123 | return; 124 | } 125 | 126 | this.logger.error(`Failed to decode swapped amount for txn ${receipt.transactionHash}`); 127 | }) 128 | .catch((error) => { 129 | reject(error); 130 | }); 131 | }); 132 | } 133 | 134 | public sendSigned(account: Account, to: string, gas: string, gasPrice: string, methodCall: any, value: string = '0') { 135 | return new Promise(async (resolve, reject) => { 136 | const encodedABI = methodCall.encodeABI(); 137 | const tx: TransactionConfig = { 138 | from: account.address, 139 | to: to, 140 | gas: gas, 141 | data: encodedABI, 142 | value: value, 143 | gasPrice: gasPrice, 144 | }; 145 | if (this.nonce !== null) { 146 | // @ts-ignore 147 | tx.nonce = this.nonce; 148 | this.nonce++; 149 | } 150 | 151 | const signedTx = await account.signTransaction(tx); 152 | 153 | let txnSubmitted = false; 154 | 155 | this.web3.eth.sendSignedTransaction(signedTx.rawTransaction) 156 | .on('transactionHash', (hash: string) => { 157 | txnSubmitted = true; 158 | this.logger.log(`Txn Hash ${hash} (${fromWei(gasPrice, 'gwei')}gwei) (nonce: ${tx.nonce ? Number(tx.nonce) : '-'})`); 159 | }) 160 | .on('receipt', (receipt) => { 161 | resolve(receipt); 162 | }) 163 | .on('error', async (error: any) => { 164 | if (!txnSubmitted && error.message.indexOf('insufficient funds for gas') !== -1) { 165 | this.nonce--; 166 | } 167 | if (!txnSubmitted && error.message.toLowerCase().indexOf('nonce too low') !== -1) { 168 | this.logger.error(`Error: ${error.message}. Retrying...`); 169 | 170 | this.nonce = await this.web3.eth.getTransactionCount(this.account.address); 171 | this.sendSigned(account, to, gas, gasPrice, methodCall, value) 172 | .then((retryResult) => { resolve(retryResult); }) 173 | .catch((retryError) => reject(retryError)); 174 | return; 175 | } 176 | 177 | this.logger.error(`Error: ${error.message}`); 178 | reject(error); 179 | }); 180 | }); 181 | } 182 | 183 | public getReserve(pair: string) { 184 | return new Promise((resolve, reject) => { 185 | const pairContract = new this.web3.eth.Contract(require('../ABIs/IPancakePair.json'), pair); 186 | pairContract.methods.getReserves().call() 187 | .then((result: any) => { 188 | resolve(new Reserve(result[0], result[1])); 189 | }) 190 | .catch((error: any) => { 191 | reject(error); 192 | }); 193 | }); 194 | } 195 | 196 | private routerContract() { 197 | return new this.web3.eth.Contract(require('../ABIs/IPancakeRouterV2.json'), this.routerAddress); 198 | } 199 | 200 | private tokenContract(token: string) { 201 | return new this.web3.eth.Contract(require('../ABIs/IBEP20.json'), token); 202 | } 203 | 204 | private getSwappedAmount(decodedLogs: any): BigNumber { 205 | let swappedAmount: BigNumber = null; 206 | decodedLogs.forEach((log: any) => { 207 | if (log.name !== 'Swap') { 208 | return; 209 | } 210 | 211 | const props = AbiUtils.decodedEventsToArray(log); 212 | swappedAmount = new BigNumber(props.amount0In === '0' ? props.amount0Out : props.amount1Out); 213 | }); 214 | 215 | return swappedAmount; 216 | } 217 | 218 | private deadline() { 219 | return Math.round(new Date().getTime() / 1000) + 30; 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/ABIs/IPancakePair.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [], 4 | "payable": false, 5 | "stateMutability": "nonpayable", 6 | "type": "constructor" 7 | }, 8 | { 9 | "anonymous": false, 10 | "inputs": [ 11 | { 12 | "indexed": true, 13 | "internalType": "address", 14 | "name": "owner", 15 | "type": "address" 16 | }, 17 | { 18 | "indexed": true, 19 | "internalType": "address", 20 | "name": "spender", 21 | "type": "address" 22 | }, 23 | { 24 | "indexed": false, 25 | "internalType": "uint256", 26 | "name": "value", 27 | "type": "uint256" 28 | } 29 | ], 30 | "name": "Approval", 31 | "type": "event" 32 | }, 33 | { 34 | "anonymous": false, 35 | "inputs": [ 36 | { 37 | "indexed": true, 38 | "internalType": "address", 39 | "name": "sender", 40 | "type": "address" 41 | }, 42 | { 43 | "indexed": false, 44 | "internalType": "uint256", 45 | "name": "amount0", 46 | "type": "uint256" 47 | }, 48 | { 49 | "indexed": false, 50 | "internalType": "uint256", 51 | "name": "amount1", 52 | "type": "uint256" 53 | }, 54 | { 55 | "indexed": true, 56 | "internalType": "address", 57 | "name": "to", 58 | "type": "address" 59 | } 60 | ], 61 | "name": "Burn", 62 | "type": "event" 63 | }, 64 | { 65 | "anonymous": false, 66 | "inputs": [ 67 | { 68 | "indexed": true, 69 | "internalType": "address", 70 | "name": "sender", 71 | "type": "address" 72 | }, 73 | { 74 | "indexed": false, 75 | "internalType": "uint256", 76 | "name": "amount0", 77 | "type": "uint256" 78 | }, 79 | { 80 | "indexed": false, 81 | "internalType": "uint256", 82 | "name": "amount1", 83 | "type": "uint256" 84 | } 85 | ], 86 | "name": "Mint", 87 | "type": "event" 88 | }, 89 | { 90 | "anonymous": false, 91 | "inputs": [ 92 | { 93 | "indexed": true, 94 | "internalType": "address", 95 | "name": "sender", 96 | "type": "address" 97 | }, 98 | { 99 | "indexed": false, 100 | "internalType": "uint256", 101 | "name": "amount0In", 102 | "type": "uint256" 103 | }, 104 | { 105 | "indexed": false, 106 | "internalType": "uint256", 107 | "name": "amount1In", 108 | "type": "uint256" 109 | }, 110 | { 111 | "indexed": false, 112 | "internalType": "uint256", 113 | "name": "amount0Out", 114 | "type": "uint256" 115 | }, 116 | { 117 | "indexed": false, 118 | "internalType": "uint256", 119 | "name": "amount1Out", 120 | "type": "uint256" 121 | }, 122 | { 123 | "indexed": true, 124 | "internalType": "address", 125 | "name": "to", 126 | "type": "address" 127 | } 128 | ], 129 | "name": "Swap", 130 | "type": "event" 131 | }, 132 | { 133 | "anonymous": false, 134 | "inputs": [ 135 | { 136 | "indexed": false, 137 | "internalType": "uint112", 138 | "name": "reserve0", 139 | "type": "uint112" 140 | }, 141 | { 142 | "indexed": false, 143 | "internalType": "uint112", 144 | "name": "reserve1", 145 | "type": "uint112" 146 | } 147 | ], 148 | "name": "Sync", 149 | "type": "event" 150 | }, 151 | { 152 | "anonymous": false, 153 | "inputs": [ 154 | { 155 | "indexed": true, 156 | "internalType": "address", 157 | "name": "from", 158 | "type": "address" 159 | }, 160 | { 161 | "indexed": true, 162 | "internalType": "address", 163 | "name": "to", 164 | "type": "address" 165 | }, 166 | { 167 | "indexed": false, 168 | "internalType": "uint256", 169 | "name": "value", 170 | "type": "uint256" 171 | } 172 | ], 173 | "name": "Transfer", 174 | "type": "event" 175 | }, 176 | { 177 | "constant": true, 178 | "inputs": [], 179 | "name": "DOMAIN_SEPARATOR", 180 | "outputs": [ 181 | { 182 | "internalType": "bytes32", 183 | "name": "", 184 | "type": "bytes32" 185 | } 186 | ], 187 | "payable": false, 188 | "stateMutability": "view", 189 | "type": "function" 190 | }, 191 | { 192 | "constant": true, 193 | "inputs": [], 194 | "name": "MINIMUM_LIQUIDITY", 195 | "outputs": [ 196 | { 197 | "internalType": "uint256", 198 | "name": "", 199 | "type": "uint256" 200 | } 201 | ], 202 | "payable": false, 203 | "stateMutability": "view", 204 | "type": "function" 205 | }, 206 | { 207 | "constant": true, 208 | "inputs": [], 209 | "name": "PERMIT_TYPEHASH", 210 | "outputs": [ 211 | { 212 | "internalType": "bytes32", 213 | "name": "", 214 | "type": "bytes32" 215 | } 216 | ], 217 | "payable": false, 218 | "stateMutability": "view", 219 | "type": "function" 220 | }, 221 | { 222 | "constant": true, 223 | "inputs": [ 224 | { 225 | "internalType": "address", 226 | "name": "", 227 | "type": "address" 228 | }, 229 | { 230 | "internalType": "address", 231 | "name": "", 232 | "type": "address" 233 | } 234 | ], 235 | "name": "allowance", 236 | "outputs": [ 237 | { 238 | "internalType": "uint256", 239 | "name": "", 240 | "type": "uint256" 241 | } 242 | ], 243 | "payable": false, 244 | "stateMutability": "view", 245 | "type": "function" 246 | }, 247 | { 248 | "constant": false, 249 | "inputs": [ 250 | { 251 | "internalType": "address", 252 | "name": "spender", 253 | "type": "address" 254 | }, 255 | { 256 | "internalType": "uint256", 257 | "name": "value", 258 | "type": "uint256" 259 | } 260 | ], 261 | "name": "approve", 262 | "outputs": [ 263 | { 264 | "internalType": "bool", 265 | "name": "", 266 | "type": "bool" 267 | } 268 | ], 269 | "payable": false, 270 | "stateMutability": "nonpayable", 271 | "type": "function" 272 | }, 273 | { 274 | "constant": true, 275 | "inputs": [ 276 | { 277 | "internalType": "address", 278 | "name": "", 279 | "type": "address" 280 | } 281 | ], 282 | "name": "balanceOf", 283 | "outputs": [ 284 | { 285 | "internalType": "uint256", 286 | "name": "", 287 | "type": "uint256" 288 | } 289 | ], 290 | "payable": false, 291 | "stateMutability": "view", 292 | "type": "function" 293 | }, 294 | { 295 | "constant": false, 296 | "inputs": [ 297 | { 298 | "internalType": "address", 299 | "name": "to", 300 | "type": "address" 301 | } 302 | ], 303 | "name": "burn", 304 | "outputs": [ 305 | { 306 | "internalType": "uint256", 307 | "name": "amount0", 308 | "type": "uint256" 309 | }, 310 | { 311 | "internalType": "uint256", 312 | "name": "amount1", 313 | "type": "uint256" 314 | } 315 | ], 316 | "payable": false, 317 | "stateMutability": "nonpayable", 318 | "type": "function" 319 | }, 320 | { 321 | "constant": true, 322 | "inputs": [], 323 | "name": "decimals", 324 | "outputs": [ 325 | { 326 | "internalType": "uint8", 327 | "name": "", 328 | "type": "uint8" 329 | } 330 | ], 331 | "payable": false, 332 | "stateMutability": "view", 333 | "type": "function" 334 | }, 335 | { 336 | "constant": true, 337 | "inputs": [], 338 | "name": "factory", 339 | "outputs": [ 340 | { 341 | "internalType": "address", 342 | "name": "", 343 | "type": "address" 344 | } 345 | ], 346 | "payable": false, 347 | "stateMutability": "view", 348 | "type": "function" 349 | }, 350 | { 351 | "constant": true, 352 | "inputs": [], 353 | "name": "getReserves", 354 | "outputs": [ 355 | { 356 | "internalType": "uint112", 357 | "name": "_reserve0", 358 | "type": "uint112" 359 | }, 360 | { 361 | "internalType": "uint112", 362 | "name": "_reserve1", 363 | "type": "uint112" 364 | }, 365 | { 366 | "internalType": "uint32", 367 | "name": "_blockTimestampLast", 368 | "type": "uint32" 369 | } 370 | ], 371 | "payable": false, 372 | "stateMutability": "view", 373 | "type": "function" 374 | }, 375 | { 376 | "constant": false, 377 | "inputs": [ 378 | { 379 | "internalType": "address", 380 | "name": "_token0", 381 | "type": "address" 382 | }, 383 | { 384 | "internalType": "address", 385 | "name": "_token1", 386 | "type": "address" 387 | } 388 | ], 389 | "name": "initialize", 390 | "outputs": [], 391 | "payable": false, 392 | "stateMutability": "nonpayable", 393 | "type": "function" 394 | }, 395 | { 396 | "constant": true, 397 | "inputs": [], 398 | "name": "kLast", 399 | "outputs": [ 400 | { 401 | "internalType": "uint256", 402 | "name": "", 403 | "type": "uint256" 404 | } 405 | ], 406 | "payable": false, 407 | "stateMutability": "view", 408 | "type": "function" 409 | }, 410 | { 411 | "constant": false, 412 | "inputs": [ 413 | { 414 | "internalType": "address", 415 | "name": "to", 416 | "type": "address" 417 | } 418 | ], 419 | "name": "mint", 420 | "outputs": [ 421 | { 422 | "internalType": "uint256", 423 | "name": "liquidity", 424 | "type": "uint256" 425 | } 426 | ], 427 | "payable": false, 428 | "stateMutability": "nonpayable", 429 | "type": "function" 430 | }, 431 | { 432 | "constant": true, 433 | "inputs": [], 434 | "name": "name", 435 | "outputs": [ 436 | { 437 | "internalType": "string", 438 | "name": "", 439 | "type": "string" 440 | } 441 | ], 442 | "payable": false, 443 | "stateMutability": "view", 444 | "type": "function" 445 | }, 446 | { 447 | "constant": true, 448 | "inputs": [ 449 | { 450 | "internalType": "address", 451 | "name": "", 452 | "type": "address" 453 | } 454 | ], 455 | "name": "nonces", 456 | "outputs": [ 457 | { 458 | "internalType": "uint256", 459 | "name": "", 460 | "type": "uint256" 461 | } 462 | ], 463 | "payable": false, 464 | "stateMutability": "view", 465 | "type": "function" 466 | }, 467 | { 468 | "constant": false, 469 | "inputs": [ 470 | { 471 | "internalType": "address", 472 | "name": "owner", 473 | "type": "address" 474 | }, 475 | { 476 | "internalType": "address", 477 | "name": "spender", 478 | "type": "address" 479 | }, 480 | { 481 | "internalType": "uint256", 482 | "name": "value", 483 | "type": "uint256" 484 | }, 485 | { 486 | "internalType": "uint256", 487 | "name": "deadline", 488 | "type": "uint256" 489 | }, 490 | { 491 | "internalType": "uint8", 492 | "name": "v", 493 | "type": "uint8" 494 | }, 495 | { 496 | "internalType": "bytes32", 497 | "name": "r", 498 | "type": "bytes32" 499 | }, 500 | { 501 | "internalType": "bytes32", 502 | "name": "s", 503 | "type": "bytes32" 504 | } 505 | ], 506 | "name": "permit", 507 | "outputs": [], 508 | "payable": false, 509 | "stateMutability": "nonpayable", 510 | "type": "function" 511 | }, 512 | { 513 | "constant": true, 514 | "inputs": [], 515 | "name": "price0CumulativeLast", 516 | "outputs": [ 517 | { 518 | "internalType": "uint256", 519 | "name": "", 520 | "type": "uint256" 521 | } 522 | ], 523 | "payable": false, 524 | "stateMutability": "view", 525 | "type": "function" 526 | }, 527 | { 528 | "constant": true, 529 | "inputs": [], 530 | "name": "price1CumulativeLast", 531 | "outputs": [ 532 | { 533 | "internalType": "uint256", 534 | "name": "", 535 | "type": "uint256" 536 | } 537 | ], 538 | "payable": false, 539 | "stateMutability": "view", 540 | "type": "function" 541 | }, 542 | { 543 | "constant": false, 544 | "inputs": [ 545 | { 546 | "internalType": "address", 547 | "name": "to", 548 | "type": "address" 549 | } 550 | ], 551 | "name": "skim", 552 | "outputs": [], 553 | "payable": false, 554 | "stateMutability": "nonpayable", 555 | "type": "function" 556 | }, 557 | { 558 | "constant": false, 559 | "inputs": [ 560 | { 561 | "internalType": "uint256", 562 | "name": "amount0Out", 563 | "type": "uint256" 564 | }, 565 | { 566 | "internalType": "uint256", 567 | "name": "amount1Out", 568 | "type": "uint256" 569 | }, 570 | { 571 | "internalType": "address", 572 | "name": "to", 573 | "type": "address" 574 | }, 575 | { 576 | "internalType": "bytes", 577 | "name": "data", 578 | "type": "bytes" 579 | } 580 | ], 581 | "name": "swap", 582 | "outputs": [], 583 | "payable": false, 584 | "stateMutability": "nonpayable", 585 | "type": "function" 586 | }, 587 | { 588 | "constant": true, 589 | "inputs": [], 590 | "name": "symbol", 591 | "outputs": [ 592 | { 593 | "internalType": "string", 594 | "name": "", 595 | "type": "string" 596 | } 597 | ], 598 | "payable": false, 599 | "stateMutability": "view", 600 | "type": "function" 601 | }, 602 | { 603 | "constant": false, 604 | "inputs": [], 605 | "name": "sync", 606 | "outputs": [], 607 | "payable": false, 608 | "stateMutability": "nonpayable", 609 | "type": "function" 610 | }, 611 | { 612 | "constant": true, 613 | "inputs": [], 614 | "name": "token0", 615 | "outputs": [ 616 | { 617 | "internalType": "address", 618 | "name": "", 619 | "type": "address" 620 | } 621 | ], 622 | "payable": false, 623 | "stateMutability": "view", 624 | "type": "function" 625 | }, 626 | { 627 | "constant": true, 628 | "inputs": [], 629 | "name": "token1", 630 | "outputs": [ 631 | { 632 | "internalType": "address", 633 | "name": "", 634 | "type": "address" 635 | } 636 | ], 637 | "payable": false, 638 | "stateMutability": "view", 639 | "type": "function" 640 | }, 641 | { 642 | "constant": true, 643 | "inputs": [], 644 | "name": "totalSupply", 645 | "outputs": [ 646 | { 647 | "internalType": "uint256", 648 | "name": "", 649 | "type": "uint256" 650 | } 651 | ], 652 | "payable": false, 653 | "stateMutability": "view", 654 | "type": "function" 655 | }, 656 | { 657 | "constant": false, 658 | "inputs": [ 659 | { 660 | "internalType": "address", 661 | "name": "to", 662 | "type": "address" 663 | }, 664 | { 665 | "internalType": "uint256", 666 | "name": "value", 667 | "type": "uint256" 668 | } 669 | ], 670 | "name": "transfer", 671 | "outputs": [ 672 | { 673 | "internalType": "bool", 674 | "name": "", 675 | "type": "bool" 676 | } 677 | ], 678 | "payable": false, 679 | "stateMutability": "nonpayable", 680 | "type": "function" 681 | }, 682 | { 683 | "constant": false, 684 | "inputs": [ 685 | { 686 | "internalType": "address", 687 | "name": "from", 688 | "type": "address" 689 | }, 690 | { 691 | "internalType": "address", 692 | "name": "to", 693 | "type": "address" 694 | }, 695 | { 696 | "internalType": "uint256", 697 | "name": "value", 698 | "type": "uint256" 699 | } 700 | ], 701 | "name": "transferFrom", 702 | "outputs": [ 703 | { 704 | "internalType": "bool", 705 | "name": "", 706 | "type": "bool" 707 | } 708 | ], 709 | "payable": false, 710 | "stateMutability": "nonpayable", 711 | "type": "function" 712 | } 713 | ] 714 | -------------------------------------------------------------------------------- /src/ABIs/IPancakeRouterV2.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | { 5 | "internalType": "address", 6 | "name": "_factory", 7 | "type": "address" 8 | }, 9 | { 10 | "internalType": "address", 11 | "name": "_WETH", 12 | "type": "address" 13 | } 14 | ], 15 | "stateMutability": "nonpayable", 16 | "type": "constructor" 17 | }, 18 | { 19 | "inputs": [], 20 | "name": "WETH", 21 | "outputs": [ 22 | { 23 | "internalType": "address", 24 | "name": "", 25 | "type": "address" 26 | } 27 | ], 28 | "stateMutability": "view", 29 | "type": "function" 30 | }, 31 | { 32 | "inputs": [ 33 | { 34 | "internalType": "address", 35 | "name": "tokenA", 36 | "type": "address" 37 | }, 38 | { 39 | "internalType": "address", 40 | "name": "tokenB", 41 | "type": "address" 42 | }, 43 | { 44 | "internalType": "uint256", 45 | "name": "amountADesired", 46 | "type": "uint256" 47 | }, 48 | { 49 | "internalType": "uint256", 50 | "name": "amountBDesired", 51 | "type": "uint256" 52 | }, 53 | { 54 | "internalType": "uint256", 55 | "name": "amountAMin", 56 | "type": "uint256" 57 | }, 58 | { 59 | "internalType": "uint256", 60 | "name": "amountBMin", 61 | "type": "uint256" 62 | }, 63 | { 64 | "internalType": "address", 65 | "name": "to", 66 | "type": "address" 67 | }, 68 | { 69 | "internalType": "uint256", 70 | "name": "deadline", 71 | "type": "uint256" 72 | } 73 | ], 74 | "name": "addLiquidity", 75 | "outputs": [ 76 | { 77 | "internalType": "uint256", 78 | "name": "amountA", 79 | "type": "uint256" 80 | }, 81 | { 82 | "internalType": "uint256", 83 | "name": "amountB", 84 | "type": "uint256" 85 | }, 86 | { 87 | "internalType": "uint256", 88 | "name": "liquidity", 89 | "type": "uint256" 90 | } 91 | ], 92 | "stateMutability": "nonpayable", 93 | "type": "function" 94 | }, 95 | { 96 | "inputs": [ 97 | { 98 | "internalType": "address", 99 | "name": "token", 100 | "type": "address" 101 | }, 102 | { 103 | "internalType": "uint256", 104 | "name": "amountTokenDesired", 105 | "type": "uint256" 106 | }, 107 | { 108 | "internalType": "uint256", 109 | "name": "amountTokenMin", 110 | "type": "uint256" 111 | }, 112 | { 113 | "internalType": "uint256", 114 | "name": "amountETHMin", 115 | "type": "uint256" 116 | }, 117 | { 118 | "internalType": "address", 119 | "name": "to", 120 | "type": "address" 121 | }, 122 | { 123 | "internalType": "uint256", 124 | "name": "deadline", 125 | "type": "uint256" 126 | } 127 | ], 128 | "name": "addLiquidityETH", 129 | "outputs": [ 130 | { 131 | "internalType": "uint256", 132 | "name": "amountToken", 133 | "type": "uint256" 134 | }, 135 | { 136 | "internalType": "uint256", 137 | "name": "amountETH", 138 | "type": "uint256" 139 | }, 140 | { 141 | "internalType": "uint256", 142 | "name": "liquidity", 143 | "type": "uint256" 144 | } 145 | ], 146 | "stateMutability": "payable", 147 | "type": "function" 148 | }, 149 | { 150 | "inputs": [], 151 | "name": "factory", 152 | "outputs": [ 153 | { 154 | "internalType": "address", 155 | "name": "", 156 | "type": "address" 157 | } 158 | ], 159 | "stateMutability": "view", 160 | "type": "function" 161 | }, 162 | { 163 | "inputs": [ 164 | { 165 | "internalType": "uint256", 166 | "name": "amountOut", 167 | "type": "uint256" 168 | }, 169 | { 170 | "internalType": "uint256", 171 | "name": "reserveIn", 172 | "type": "uint256" 173 | }, 174 | { 175 | "internalType": "uint256", 176 | "name": "reserveOut", 177 | "type": "uint256" 178 | } 179 | ], 180 | "name": "getAmountIn", 181 | "outputs": [ 182 | { 183 | "internalType": "uint256", 184 | "name": "amountIn", 185 | "type": "uint256" 186 | } 187 | ], 188 | "stateMutability": "pure", 189 | "type": "function" 190 | }, 191 | { 192 | "inputs": [ 193 | { 194 | "internalType": "uint256", 195 | "name": "amountIn", 196 | "type": "uint256" 197 | }, 198 | { 199 | "internalType": "uint256", 200 | "name": "reserveIn", 201 | "type": "uint256" 202 | }, 203 | { 204 | "internalType": "uint256", 205 | "name": "reserveOut", 206 | "type": "uint256" 207 | } 208 | ], 209 | "name": "getAmountOut", 210 | "outputs": [ 211 | { 212 | "internalType": "uint256", 213 | "name": "amountOut", 214 | "type": "uint256" 215 | } 216 | ], 217 | "stateMutability": "pure", 218 | "type": "function" 219 | }, 220 | { 221 | "inputs": [ 222 | { 223 | "internalType": "uint256", 224 | "name": "amountOut", 225 | "type": "uint256" 226 | }, 227 | { 228 | "internalType": "address[]", 229 | "name": "path", 230 | "type": "address[]" 231 | } 232 | ], 233 | "name": "getAmountsIn", 234 | "outputs": [ 235 | { 236 | "internalType": "uint256[]", 237 | "name": "amounts", 238 | "type": "uint256[]" 239 | } 240 | ], 241 | "stateMutability": "view", 242 | "type": "function" 243 | }, 244 | { 245 | "inputs": [ 246 | { 247 | "internalType": "uint256", 248 | "name": "amountIn", 249 | "type": "uint256" 250 | }, 251 | { 252 | "internalType": "address[]", 253 | "name": "path", 254 | "type": "address[]" 255 | } 256 | ], 257 | "name": "getAmountsOut", 258 | "outputs": [ 259 | { 260 | "internalType": "uint256[]", 261 | "name": "amounts", 262 | "type": "uint256[]" 263 | } 264 | ], 265 | "stateMutability": "view", 266 | "type": "function" 267 | }, 268 | { 269 | "inputs": [ 270 | { 271 | "internalType": "uint256", 272 | "name": "amountA", 273 | "type": "uint256" 274 | }, 275 | { 276 | "internalType": "uint256", 277 | "name": "reserveA", 278 | "type": "uint256" 279 | }, 280 | { 281 | "internalType": "uint256", 282 | "name": "reserveB", 283 | "type": "uint256" 284 | } 285 | ], 286 | "name": "quote", 287 | "outputs": [ 288 | { 289 | "internalType": "uint256", 290 | "name": "amountB", 291 | "type": "uint256" 292 | } 293 | ], 294 | "stateMutability": "pure", 295 | "type": "function" 296 | }, 297 | { 298 | "inputs": [ 299 | { 300 | "internalType": "address", 301 | "name": "tokenA", 302 | "type": "address" 303 | }, 304 | { 305 | "internalType": "address", 306 | "name": "tokenB", 307 | "type": "address" 308 | }, 309 | { 310 | "internalType": "uint256", 311 | "name": "liquidity", 312 | "type": "uint256" 313 | }, 314 | { 315 | "internalType": "uint256", 316 | "name": "amountAMin", 317 | "type": "uint256" 318 | }, 319 | { 320 | "internalType": "uint256", 321 | "name": "amountBMin", 322 | "type": "uint256" 323 | }, 324 | { 325 | "internalType": "address", 326 | "name": "to", 327 | "type": "address" 328 | }, 329 | { 330 | "internalType": "uint256", 331 | "name": "deadline", 332 | "type": "uint256" 333 | } 334 | ], 335 | "name": "removeLiquidity", 336 | "outputs": [ 337 | { 338 | "internalType": "uint256", 339 | "name": "amountA", 340 | "type": "uint256" 341 | }, 342 | { 343 | "internalType": "uint256", 344 | "name": "amountB", 345 | "type": "uint256" 346 | } 347 | ], 348 | "stateMutability": "nonpayable", 349 | "type": "function" 350 | }, 351 | { 352 | "inputs": [ 353 | { 354 | "internalType": "address", 355 | "name": "token", 356 | "type": "address" 357 | }, 358 | { 359 | "internalType": "uint256", 360 | "name": "liquidity", 361 | "type": "uint256" 362 | }, 363 | { 364 | "internalType": "uint256", 365 | "name": "amountTokenMin", 366 | "type": "uint256" 367 | }, 368 | { 369 | "internalType": "uint256", 370 | "name": "amountETHMin", 371 | "type": "uint256" 372 | }, 373 | { 374 | "internalType": "address", 375 | "name": "to", 376 | "type": "address" 377 | }, 378 | { 379 | "internalType": "uint256", 380 | "name": "deadline", 381 | "type": "uint256" 382 | } 383 | ], 384 | "name": "removeLiquidityETH", 385 | "outputs": [ 386 | { 387 | "internalType": "uint256", 388 | "name": "amountToken", 389 | "type": "uint256" 390 | }, 391 | { 392 | "internalType": "uint256", 393 | "name": "amountETH", 394 | "type": "uint256" 395 | } 396 | ], 397 | "stateMutability": "nonpayable", 398 | "type": "function" 399 | }, 400 | { 401 | "inputs": [ 402 | { 403 | "internalType": "address", 404 | "name": "token", 405 | "type": "address" 406 | }, 407 | { 408 | "internalType": "uint256", 409 | "name": "liquidity", 410 | "type": "uint256" 411 | }, 412 | { 413 | "internalType": "uint256", 414 | "name": "amountTokenMin", 415 | "type": "uint256" 416 | }, 417 | { 418 | "internalType": "uint256", 419 | "name": "amountETHMin", 420 | "type": "uint256" 421 | }, 422 | { 423 | "internalType": "address", 424 | "name": "to", 425 | "type": "address" 426 | }, 427 | { 428 | "internalType": "uint256", 429 | "name": "deadline", 430 | "type": "uint256" 431 | } 432 | ], 433 | "name": "removeLiquidityETHSupportingFeeOnTransferTokens", 434 | "outputs": [ 435 | { 436 | "internalType": "uint256", 437 | "name": "amountETH", 438 | "type": "uint256" 439 | } 440 | ], 441 | "stateMutability": "nonpayable", 442 | "type": "function" 443 | }, 444 | { 445 | "inputs": [ 446 | { 447 | "internalType": "address", 448 | "name": "token", 449 | "type": "address" 450 | }, 451 | { 452 | "internalType": "uint256", 453 | "name": "liquidity", 454 | "type": "uint256" 455 | }, 456 | { 457 | "internalType": "uint256", 458 | "name": "amountTokenMin", 459 | "type": "uint256" 460 | }, 461 | { 462 | "internalType": "uint256", 463 | "name": "amountETHMin", 464 | "type": "uint256" 465 | }, 466 | { 467 | "internalType": "address", 468 | "name": "to", 469 | "type": "address" 470 | }, 471 | { 472 | "internalType": "uint256", 473 | "name": "deadline", 474 | "type": "uint256" 475 | }, 476 | { 477 | "internalType": "bool", 478 | "name": "approveMax", 479 | "type": "bool" 480 | }, 481 | { 482 | "internalType": "uint8", 483 | "name": "v", 484 | "type": "uint8" 485 | }, 486 | { 487 | "internalType": "bytes32", 488 | "name": "r", 489 | "type": "bytes32" 490 | }, 491 | { 492 | "internalType": "bytes32", 493 | "name": "s", 494 | "type": "bytes32" 495 | } 496 | ], 497 | "name": "removeLiquidityETHWithPermit", 498 | "outputs": [ 499 | { 500 | "internalType": "uint256", 501 | "name": "amountToken", 502 | "type": "uint256" 503 | }, 504 | { 505 | "internalType": "uint256", 506 | "name": "amountETH", 507 | "type": "uint256" 508 | } 509 | ], 510 | "stateMutability": "nonpayable", 511 | "type": "function" 512 | }, 513 | { 514 | "inputs": [ 515 | { 516 | "internalType": "address", 517 | "name": "token", 518 | "type": "address" 519 | }, 520 | { 521 | "internalType": "uint256", 522 | "name": "liquidity", 523 | "type": "uint256" 524 | }, 525 | { 526 | "internalType": "uint256", 527 | "name": "amountTokenMin", 528 | "type": "uint256" 529 | }, 530 | { 531 | "internalType": "uint256", 532 | "name": "amountETHMin", 533 | "type": "uint256" 534 | }, 535 | { 536 | "internalType": "address", 537 | "name": "to", 538 | "type": "address" 539 | }, 540 | { 541 | "internalType": "uint256", 542 | "name": "deadline", 543 | "type": "uint256" 544 | }, 545 | { 546 | "internalType": "bool", 547 | "name": "approveMax", 548 | "type": "bool" 549 | }, 550 | { 551 | "internalType": "uint8", 552 | "name": "v", 553 | "type": "uint8" 554 | }, 555 | { 556 | "internalType": "bytes32", 557 | "name": "r", 558 | "type": "bytes32" 559 | }, 560 | { 561 | "internalType": "bytes32", 562 | "name": "s", 563 | "type": "bytes32" 564 | } 565 | ], 566 | "name": "removeLiquidityETHWithPermitSupportingFeeOnTransferTokens", 567 | "outputs": [ 568 | { 569 | "internalType": "uint256", 570 | "name": "amountETH", 571 | "type": "uint256" 572 | } 573 | ], 574 | "stateMutability": "nonpayable", 575 | "type": "function" 576 | }, 577 | { 578 | "inputs": [ 579 | { 580 | "internalType": "address", 581 | "name": "tokenA", 582 | "type": "address" 583 | }, 584 | { 585 | "internalType": "address", 586 | "name": "tokenB", 587 | "type": "address" 588 | }, 589 | { 590 | "internalType": "uint256", 591 | "name": "liquidity", 592 | "type": "uint256" 593 | }, 594 | { 595 | "internalType": "uint256", 596 | "name": "amountAMin", 597 | "type": "uint256" 598 | }, 599 | { 600 | "internalType": "uint256", 601 | "name": "amountBMin", 602 | "type": "uint256" 603 | }, 604 | { 605 | "internalType": "address", 606 | "name": "to", 607 | "type": "address" 608 | }, 609 | { 610 | "internalType": "uint256", 611 | "name": "deadline", 612 | "type": "uint256" 613 | }, 614 | { 615 | "internalType": "bool", 616 | "name": "approveMax", 617 | "type": "bool" 618 | }, 619 | { 620 | "internalType": "uint8", 621 | "name": "v", 622 | "type": "uint8" 623 | }, 624 | { 625 | "internalType": "bytes32", 626 | "name": "r", 627 | "type": "bytes32" 628 | }, 629 | { 630 | "internalType": "bytes32", 631 | "name": "s", 632 | "type": "bytes32" 633 | } 634 | ], 635 | "name": "removeLiquidityWithPermit", 636 | "outputs": [ 637 | { 638 | "internalType": "uint256", 639 | "name": "amountA", 640 | "type": "uint256" 641 | }, 642 | { 643 | "internalType": "uint256", 644 | "name": "amountB", 645 | "type": "uint256" 646 | } 647 | ], 648 | "stateMutability": "nonpayable", 649 | "type": "function" 650 | }, 651 | { 652 | "inputs": [ 653 | { 654 | "internalType": "uint256", 655 | "name": "amountOut", 656 | "type": "uint256" 657 | }, 658 | { 659 | "internalType": "address[]", 660 | "name": "path", 661 | "type": "address[]" 662 | }, 663 | { 664 | "internalType": "address", 665 | "name": "to", 666 | "type": "address" 667 | }, 668 | { 669 | "internalType": "uint256", 670 | "name": "deadline", 671 | "type": "uint256" 672 | } 673 | ], 674 | "name": "swapETHForExactTokens", 675 | "outputs": [ 676 | { 677 | "internalType": "uint256[]", 678 | "name": "amounts", 679 | "type": "uint256[]" 680 | } 681 | ], 682 | "stateMutability": "payable", 683 | "type": "function" 684 | }, 685 | { 686 | "inputs": [ 687 | { 688 | "internalType": "uint256", 689 | "name": "amountOutMin", 690 | "type": "uint256" 691 | }, 692 | { 693 | "internalType": "address[]", 694 | "name": "path", 695 | "type": "address[]" 696 | }, 697 | { 698 | "internalType": "address", 699 | "name": "to", 700 | "type": "address" 701 | }, 702 | { 703 | "internalType": "uint256", 704 | "name": "deadline", 705 | "type": "uint256" 706 | } 707 | ], 708 | "name": "swapExactETHForTokens", 709 | "outputs": [ 710 | { 711 | "internalType": "uint256[]", 712 | "name": "amounts", 713 | "type": "uint256[]" 714 | } 715 | ], 716 | "stateMutability": "payable", 717 | "type": "function" 718 | }, 719 | { 720 | "inputs": [ 721 | { 722 | "internalType": "uint256", 723 | "name": "amountOutMin", 724 | "type": "uint256" 725 | }, 726 | { 727 | "internalType": "address[]", 728 | "name": "path", 729 | "type": "address[]" 730 | }, 731 | { 732 | "internalType": "address", 733 | "name": "to", 734 | "type": "address" 735 | }, 736 | { 737 | "internalType": "uint256", 738 | "name": "deadline", 739 | "type": "uint256" 740 | } 741 | ], 742 | "name": "swapExactETHForTokensSupportingFeeOnTransferTokens", 743 | "outputs": [], 744 | "stateMutability": "payable", 745 | "type": "function" 746 | }, 747 | { 748 | "inputs": [ 749 | { 750 | "internalType": "uint256", 751 | "name": "amountIn", 752 | "type": "uint256" 753 | }, 754 | { 755 | "internalType": "uint256", 756 | "name": "amountOutMin", 757 | "type": "uint256" 758 | }, 759 | { 760 | "internalType": "address[]", 761 | "name": "path", 762 | "type": "address[]" 763 | }, 764 | { 765 | "internalType": "address", 766 | "name": "to", 767 | "type": "address" 768 | }, 769 | { 770 | "internalType": "uint256", 771 | "name": "deadline", 772 | "type": "uint256" 773 | } 774 | ], 775 | "name": "swapExactTokensForETH", 776 | "outputs": [ 777 | { 778 | "internalType": "uint256[]", 779 | "name": "amounts", 780 | "type": "uint256[]" 781 | } 782 | ], 783 | "stateMutability": "nonpayable", 784 | "type": "function" 785 | }, 786 | { 787 | "inputs": [ 788 | { 789 | "internalType": "uint256", 790 | "name": "amountIn", 791 | "type": "uint256" 792 | }, 793 | { 794 | "internalType": "uint256", 795 | "name": "amountOutMin", 796 | "type": "uint256" 797 | }, 798 | { 799 | "internalType": "address[]", 800 | "name": "path", 801 | "type": "address[]" 802 | }, 803 | { 804 | "internalType": "address", 805 | "name": "to", 806 | "type": "address" 807 | }, 808 | { 809 | "internalType": "uint256", 810 | "name": "deadline", 811 | "type": "uint256" 812 | } 813 | ], 814 | "name": "swapExactTokensForETHSupportingFeeOnTransferTokens", 815 | "outputs": [], 816 | "stateMutability": "nonpayable", 817 | "type": "function" 818 | }, 819 | { 820 | "inputs": [ 821 | { 822 | "internalType": "uint256", 823 | "name": "amountIn", 824 | "type": "uint256" 825 | }, 826 | { 827 | "internalType": "uint256", 828 | "name": "amountOutMin", 829 | "type": "uint256" 830 | }, 831 | { 832 | "internalType": "address[]", 833 | "name": "path", 834 | "type": "address[]" 835 | }, 836 | { 837 | "internalType": "address", 838 | "name": "to", 839 | "type": "address" 840 | }, 841 | { 842 | "internalType": "uint256", 843 | "name": "deadline", 844 | "type": "uint256" 845 | } 846 | ], 847 | "name": "swapExactTokensForTokens", 848 | "outputs": [ 849 | { 850 | "internalType": "uint256[]", 851 | "name": "amounts", 852 | "type": "uint256[]" 853 | } 854 | ], 855 | "stateMutability": "nonpayable", 856 | "type": "function" 857 | }, 858 | { 859 | "inputs": [ 860 | { 861 | "internalType": "uint256", 862 | "name": "amountIn", 863 | "type": "uint256" 864 | }, 865 | { 866 | "internalType": "uint256", 867 | "name": "amountOutMin", 868 | "type": "uint256" 869 | }, 870 | { 871 | "internalType": "address[]", 872 | "name": "path", 873 | "type": "address[]" 874 | }, 875 | { 876 | "internalType": "address", 877 | "name": "to", 878 | "type": "address" 879 | }, 880 | { 881 | "internalType": "uint256", 882 | "name": "deadline", 883 | "type": "uint256" 884 | } 885 | ], 886 | "name": "swapExactTokensForTokensSupportingFeeOnTransferTokens", 887 | "outputs": [], 888 | "stateMutability": "nonpayable", 889 | "type": "function" 890 | }, 891 | { 892 | "inputs": [ 893 | { 894 | "internalType": "uint256", 895 | "name": "amountOut", 896 | "type": "uint256" 897 | }, 898 | { 899 | "internalType": "uint256", 900 | "name": "amountInMax", 901 | "type": "uint256" 902 | }, 903 | { 904 | "internalType": "address[]", 905 | "name": "path", 906 | "type": "address[]" 907 | }, 908 | { 909 | "internalType": "address", 910 | "name": "to", 911 | "type": "address" 912 | }, 913 | { 914 | "internalType": "uint256", 915 | "name": "deadline", 916 | "type": "uint256" 917 | } 918 | ], 919 | "name": "swapTokensForExactETH", 920 | "outputs": [ 921 | { 922 | "internalType": "uint256[]", 923 | "name": "amounts", 924 | "type": "uint256[]" 925 | } 926 | ], 927 | "stateMutability": "nonpayable", 928 | "type": "function" 929 | }, 930 | { 931 | "inputs": [ 932 | { 933 | "internalType": "uint256", 934 | "name": "amountOut", 935 | "type": "uint256" 936 | }, 937 | { 938 | "internalType": "uint256", 939 | "name": "amountInMax", 940 | "type": "uint256" 941 | }, 942 | { 943 | "internalType": "address[]", 944 | "name": "path", 945 | "type": "address[]" 946 | }, 947 | { 948 | "internalType": "address", 949 | "name": "to", 950 | "type": "address" 951 | }, 952 | { 953 | "internalType": "uint256", 954 | "name": "deadline", 955 | "type": "uint256" 956 | } 957 | ], 958 | "name": "swapTokensForExactTokens", 959 | "outputs": [ 960 | { 961 | "internalType": "uint256[]", 962 | "name": "amounts", 963 | "type": "uint256[]" 964 | } 965 | ], 966 | "stateMutability": "nonpayable", 967 | "type": "function" 968 | }, 969 | { 970 | "stateMutability": "payable", 971 | "type": "receive" 972 | } 973 | ] 974 | --------------------------------------------------------------------------------