Skip to content
This repository was archived by the owner on Apr 17, 2023. It is now read-only.

Commit cb11204

Browse files
committed
🚀 initial commit
0 parents  commit cb11204

File tree

17 files changed

+1391
-0
lines changed

17 files changed

+1391
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
.env

config.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
if (!process.env.PUBLIC_KEY && !process.env.PRIVATE_KEY && !process.env.ETH_IN_AMOUNT && !process.env.DB_URL) {
2+
3+
throw new Error("PUBLIC_KEY && PRIVATE_KEY && ETH_IN_AMOUNT && DB_URL, Must be defined in your .env file");
4+
}
5+
export const config = {
6+
WALLET: {
7+
PUBLIC_KEY: process.env.PUBLIC_KEY!,
8+
PRIVATE_KEY: process.env.PRIVATE_KEY!
9+
},
10+
PROVIDERS: {
11+
INFURA_API_KEY: ''
12+
},
13+
NETWORK: {
14+
ID: 1 // 1 eth, 56 is bsc, 137 polygon, 10 optimism, 42161 arbitrum
15+
},
16+
PRICE_CHECK_INTERVAL_IN_SECONDS: process.env.PRICE_CHECK_INTERVAL_IN_SECONDS || 45,
17+
ETH_IN_AMOUNT: parseFloat(process.env.ETH_IN_AMOUNT!),
18+
DB_URL: process.env.DB_URL!
19+
20+
}

example.env

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Wallet
2+
PRIVATE_KEY = ''
3+
PUBLIC_KEY = ''
4+
5+
PRICE_CHECK_INTERVAL_IN_SECONDS=45
6+
7+
# Trading Preference
8+
ETH_IN_AMOUNT = '1'
9+
10+
# Database
11+
DB_URL = ''

package.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "multidexarbbot",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "src/index.ts",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1",
8+
"start": "ts-node-dev -r dotenv/config src/index.ts"
9+
},
10+
"author": {
11+
"name": "Dennoh Peter",
12+
"email": "[email protected]"
13+
},
14+
"keywords": [
15+
"Arbitrage",
16+
"MultiDex",
17+
"Trading"
18+
],
19+
"license": "MIT",
20+
"dependencies": {
21+
"@types/node-cron": "^3.0.0",
22+
"axios": "^0.22.0",
23+
"bignumber.js": "^9.0.1",
24+
"chalk": "^4.1.2",
25+
"chalk-table": "^1.0.2",
26+
"dotenv": "^10.0.0",
27+
"ethers": "^5.4.7",
28+
"mongoose": "^6.0.9",
29+
"node-cron": "^3.0.0",
30+
"telegraf": "^4.4.2"
31+
}
32+
}

src/data/token.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
export const MONITORED_TOKENS = [
2+
3+
// { address: "0xdac17f958d2ee523a2206206994597c13d831ec7", symbol: "USDT", name: "USD Tether" },
4+
{ address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", symbol: "USDC", name: "USD Coin" },
5+
// { address: "0x6b175474e89094c44da98b954eedeac495271d0f", symbol: "DAI", name: "MakerDao StableCoin" },
6+
// // { address: "0x3845badade8e6dff049820680d1f14bd3903a5d0", symbol: "SAND", name: "Sandbox" },
7+
// { address: "0xd46ba6d942050d489dbd938a2c909a5d5039a161", symbol: "AMPL", name: "Ampleforth" },
8+
// // { address: "0x956f47f50a910163d8bf957cf5846d573e7f87ca", symbol: "FEI", name: "Fei Protocol StableCoin" },
9+
// { address: "0xbc396689893d065f41bc2c6ecbee5e0085233447", symbol: "PERP", name: "Defi Trading Platform with AMMs" },
10+
// // { address: "0x1453dbb8a29551ade11d89825ca812e05317eaeb", symbol: "TEND", name: "Tendies" },
11+
// // { address: "0x2b591e99afe9f32eaa6214f7b7629768c40eeb39", symbol: "HEX", name: "Staking" },
12+
// { address: "0xe53ec727dbdeb9e2d5456c3be40cff031ab40a55", symbol: "SUPER", name: "SuperFarm" },
13+
// { address: "0xa47c8bf37f92abed4a126bda807a7b7498661acd", symbol: "UST", name: "Mirror StableCoin" },
14+
// // { address: "0x853d955acef822db058eb8505911ed77f175b99e", symbol: "FRAX", name: "Frax" },
15+
// { address: "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", symbol: "WBTC", name: "Wrapped Bitcoin" },
16+
// // { address: "0x72e364f2abdc788b7e918bc238b21f109cd634d7", symbol: "MVI", name: "Metaverse Virtual Reality" },
17+
// // { address: "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce", symbol: "SHIB", name: "Shiba Inu" },
18+
// { address: "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", symbol: "UNI", name: "Uniswap" },
19+
// // { address: "0x1c9922314ed1415c95b9fd453c3818fd41867d0b", symbol: "TOWER", name: "Crazy Defense Heroes" },
20+
// // { address: "0x990f341946a3fdb507ae7e52d17851b87168017c", symbol: "STRONG", name: "StrongBlock Eth MasterNodes as a service" },
21+
// // { address: "0x07150e919b4de5fd6a63de1f9384828396f25fdc", symbol: "BASE", name: "Base Protocol" },
22+
// // { address: "0xc770eefad204b5180df6a14ee197d99d808ee52d", symbol: "FOX", name: "Fox" },
23+
// // { address: "0xfb7b4564402e5500db5bb6d63ae671302777c75a", symbol: "DEXT", name: "Dex Tools " },
24+
// // { address: "0x514910771af9ca656af840dff83e8264ecf986ca", symbol: "LINK", name: "Chainlink Oracle" },
25+
// { address: "0x557b933a7c2c45672b610f8954a3deb39a51a8ca", symbol: "REVV", name: "REv Motorsport" },
26+
// { address: "0x09a3ecafa817268f77be1283176b946c4ff2e608", symbol: "MIR", name: "Mirror Protocol" },
27+
{ address: "0x8e870d67f660d95d5be530380d0ec0bd388289e1", symbol: "PAX", name: "Paxos Standard" },
28+
{ address: "0x111111111117dc0aa78b770fa6a738034120c302", symbol: "1INCH", name: "1Inch Token" },
29+
{ address: "0x0f5d2fb29fb7d3cfee444a200298f468908cc942", symbol: "MANA", name: "Decentralland" },
30+
{ address: "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0", symbol: "MATIC", name: "Polygon Network" },
31+
{ address: "0xde30da39c46104798bb5aa3fe8b9e0e1f348163f", symbol: "GTC", name: "GitCoin" },
32+
{ address: "0xa0246c9032bc3a600820415ae600c6388619a14d", symbol: "FARM", name: "Harvest Finance Staking" },
33+
{ address: "0xc011a73ee8576fb46f5e1c5751ca3b9fe0af2a6f", symbol: "SNX", name: "Synthetix" },
34+
{ address: "0xd2877702675e6ceb975b4a1dff9fb7baf4c91ea9", symbol: "LUNA", name: "Mirror synthetics protocol token" },
35+
{ address: "0x35a532d376ffd9a705d0bb319532837337a398e7", symbol: "WDOGE", name: "Wrapped DogeCoin" },
36+
]

src/index.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import chalk from "chalk";
2+
import { connect } from "mongoose";
3+
import { schedule } from "node-cron";
4+
import { config } from "../config";
5+
import { OneInch } from "./lib/1inch.io";
6+
import { Quote, Token } from "./types/1inch";
7+
const chalkTable = require('chalk-table');
8+
import BigNumber from "bignumber.js";
9+
import { flat } from "./utils";
10+
import { MONITORED_TOKENS } from "./data/token";
11+
12+
const Main = async () => {
13+
const oneInch = new OneInch()
14+
console.log('Starting...');
15+
console.log(`---`.repeat(10));
16+
17+
// try {
18+
// bot.stop()
19+
// }
20+
// catch (err) {
21+
// }
22+
23+
// console.log('Connecting to telegram bot...\n---');
24+
// await bot.launch().then((result) => {
25+
// console.log('Connected to telegram bot!');
26+
27+
// }).catch(async (err) => {
28+
// let error = JSON.parse(JSON.stringify(err))
29+
// console.log('Telegram Error:', error?.message);
30+
31+
// }).catch((error: any) => {
32+
// console.log('Telegram error:', error);
33+
// })
34+
35+
// console.log(`---`.repeat(10));
36+
console.log('Connecting to MongoDb...\n---');
37+
const options = {
38+
useNewUrlParser: true,
39+
useUnifiedTopology: true,
40+
useCreateIndex: true,
41+
keepAlive: true,
42+
connectTimeoutMS: 60000,
43+
socketTimeoutMS: 60000,
44+
}
45+
46+
await connect(config.DB_URL, options).then((result) => {
47+
console.log("Connected to MongoDb :)");
48+
}).catch(async (err) => {
49+
let error = JSON.parse(JSON.stringify(err))
50+
console.log('Mongo Error:', error?.name);
51+
});
52+
console.log(`---`.repeat(10));
53+
54+
await oneInch.getProtocols()
55+
.then((protocols: string[]) => {
56+
console.log(`Finding the best route for trade on the following exchanges ${protocols.join(', ')}...`);
57+
})
58+
.catch((err: any) => { })
59+
60+
console.log(`---`.repeat(10));
61+
62+
let ethAmount = new BigNumber(config.ETH_IN_AMOUNT).shiftedBy(18).toString()
63+
schedule(`*/${config.PRICE_CHECK_INTERVAL_IN_SECONDS} * * * * *`, async function () {
64+
console.log(`***`.repeat(10));
65+
MONITORED_TOKENS.forEach(async (token: any) => {
66+
try {
67+
const buy_quote: Quote = await oneInch.getQuote({
68+
srcToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
69+
toToken: token.address,
70+
srcAmount: ethAmount
71+
})
72+
let token_amount = buy_quote.toAmount
73+
const sell_quote: Quote = await oneInch.getQuote({
74+
srcToken: token.address,
75+
toToken: buy_quote.srcToken.address,
76+
srcAmount: token_amount
77+
})
78+
79+
const options = {
80+
leftPad: 0,
81+
columns: [
82+
{ field: "eth_in", name: chalk.cyan("ETH IN") },
83+
{ field: "buy_on_dex", name: chalk.green(`BEST BUY ROUTEs`) },
84+
{ field: "sell_on_dex", name: chalk.yellow("BEST SELL ROUTEs") },
85+
{ field: "token_amount", name: chalk.yellow("Token OUT") },
86+
{ field: "eth_out", name: chalk.yellow("ETH OUT") },
87+
{ field: "profit", name: chalk.yellow("PROFIT PCT") },
88+
{ field: "time", name: chalk.magenta("Time 📅") },
89+
{ field: "rate", name: chalk.blue("Fetch Rate 🕠") },
90+
]
91+
};
92+
const timestamp = new Date()
93+
let eth_out = parseFloat(new BigNumber(sell_quote.toAmount).shiftedBy(-sell_quote.toToken.decimals).toFixed(6))
94+
const profit_pct = ((eth_out - config.ETH_IN_AMOUNT) / config.ETH_IN_AMOUNT) * 100
95+
let token_out = parseFloat(new BigNumber(token_amount).shiftedBy(-buy_quote.toToken.decimals).toFixed(6))
96+
let best_buy_protocols = (await flat(buy_quote.protocols)).map((quote: any) => quote.name).join(',')
97+
let best_sell_protocols = (await flat(sell_quote.protocols)).map((quote: any) => quote.name).join(',')
98+
const table = chalkTable(options, [
99+
{
100+
eth_in: config.ETH_IN_AMOUNT,
101+
buy_on_dex: best_buy_protocols,
102+
sell_on_dex: best_sell_protocols,
103+
token_amount: `${token_out} ${buy_quote.toToken.symbol}`,
104+
eth_out: `${eth_out} ${sell_quote.toToken.symbol}`,
105+
profit: `${profit_pct.toFixed(6)}%`,
106+
time: timestamp.toISOString().replace(/T/, ' ').replace(/\..+/, ''),
107+
rate: `${config.PRICE_CHECK_INTERVAL_IN_SECONDS}s`
108+
},
109+
]);
110+
if (!(JSON.stringify(best_buy_protocols) == JSON.stringify(best_sell_protocols))) {
111+
console.log(table);
112+
}
113+
114+
} catch (error: any) {
115+
console.error('Error:', JSON.parse(JSON.stringify(error)).code);
116+
}
117+
118+
});
119+
})
120+
121+
}
122+
123+
124+
Main()

src/lib/1inch.io.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import axios from "axios";
2+
import { config } from "../../config";
3+
import { Quote } from "../types/1inch";
4+
import { Aggr } from "./aggr";
5+
6+
export class OneInch extends Aggr {
7+
constructor() {
8+
super(`1Inch`, `https://api.1inch.exchange/v3.0/`);
9+
}
10+
getQuote = async (params: { srcToken: string, toToken: string, srcAmount: number | string, side?: string }): Promise<Quote> => {
11+
const { srcToken, toToken, srcAmount, side } = params
12+
try {
13+
const { data }: any = await axios({
14+
method: 'GET',
15+
url: `${this.API_URL}${config.NETWORK.ID}/quote?fromTokenAddress=${srcToken}&toTokenAddress=${toToken}&amount=${srcAmount}`
16+
})
17+
return {
18+
srcToken: data.fromToken,
19+
srcAmount: data.fromTokenAmount,
20+
toToken: data.toToken,
21+
toAmount: data.toTokenAmount,
22+
protocols: data.protocols
23+
}
24+
} catch (error: any) {
25+
throw new Error(JSON.stringify(error));
26+
27+
}
28+
}
29+
buildTx = async (srcToken: string, toToken: string, srcAmount: number, slippage?: number): Promise<string> => {
30+
try {
31+
const { data } = await axios({
32+
method: "GET",
33+
url: `${this.API_URL}${config.NETWORK.ID}/swap?fromTokenAddress=${srcToken}&toTokenAddress=${toToken}&amount=${srcAmount}&fromAddress=${config.WALLET.PUBLIC_KEY}&disableEstimate=true${slippage ? `&slippage=${slippage}` : ''}`
34+
})
35+
return data
36+
} catch (error: any) {
37+
throw new Error(JSON.stringify(error));
38+
}
39+
}
40+
41+
getProtocols = async (): Promise<string[]> => {
42+
try {
43+
const { data }: any = await axios({
44+
method: "GET",
45+
url: `${this.API_URL}${config.NETWORK.ID}/protocols`
46+
})
47+
return data.protocols
48+
49+
} catch (error) {
50+
throw new Error(JSON.stringify(error));
51+
}
52+
}
53+
54+
}

src/lib/aggr.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { ethers, Wallet } from "ethers";
2+
import { config } from "../../config";
3+
4+
export abstract class Aggr {
5+
readonly name: string;
6+
readonly provider: ethers.providers.JsonRpcProvider;
7+
readonly account: Wallet
8+
readonly API_URL: string
9+
10+
constructor(name: string, api_url: string) {
11+
this.name = name;
12+
this.provider = new ethers.providers.JsonRpcProvider(config.PROVIDERS.INFURA_API_KEY)
13+
this.account = new Wallet(config.WALLET.PRIVATE_KEY, this.provider);
14+
this.API_URL = api_url
15+
}
16+
17+
sendTx = async (params: { data: any, nonce: number }) => {
18+
const { data, nonce } = params
19+
try {
20+
21+
if (!isNaN(nonce)) {
22+
data.nonce = nonce
23+
}
24+
25+
const tx = await this.account.sendTransaction(data)
26+
return tx
27+
console.log("Tx success");
28+
} catch (e) {
29+
throw new Error(`Tx failure ${e}`);
30+
}
31+
}
32+
33+
/**
34+
* Gets balance of a token in a wallet address
35+
* @param tokenAddress token address to check to check balance
36+
* @returns balance of token in a wallet
37+
*/
38+
balanceOf = async (tokenAddress: string) => {
39+
let contract = new ethers.Contract(
40+
tokenAddress,
41+
['function balanceOf(address account) external view returns (uint256)'],
42+
this.account
43+
)
44+
return await contract.balanceOf(config.WALLET.PUBLIC_KEY);
45+
46+
}
47+
48+
/**
49+
* Gets the current nonce of a wallet
50+
* @returns nonce
51+
*/
52+
getNonce = async (): Promise<number> => {
53+
return await this.provider.getTransactionCount(config.WALLET.PUBLIC_KEY)
54+
}
55+
}

src/lib/dex.ag.ts

Whitespace-only changes.

src/lib/index.ts

Whitespace-only changes.

src/lib/matcha.xyz.ts

Whitespace-only changes.

0 commit comments

Comments
 (0)