Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit2d4d332

Browse files
committed
adding buy and sell logic
added support for other blockchains such as bsc, optimism, arbitrum
1 parentd1d4ee4 commit2d4d332

File tree

9 files changed

+224
-21
lines changed

9 files changed

+224
-21
lines changed

‎CHANGELOG.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,20 @@
1212
`v1.0.0`
1313

1414
- Bug fix and optimizations
15-
- Add support for binance smart chain(BSC)
15+
- Add support for binance smart chain(BSC):heavy_check_mark:
1616
- Testing on mainnet
1717

1818
`v1.0.2`
1919

20-
- Add support for Optimism Blockchain
20+
- Add support for Optimism Blockchain:heavy_check_mark:
2121

2222
`v1.0.3`
2323

24-
- Add support for Arbitrum Blockchain
24+
- Add support for Arbitrum Blockchain:heavy_check_mark:
2525

2626
`v1.0.4`
2727

28-
- Add support for Polygon Matic Blockchain
28+
- Add support for Polygon Matic Blockchain:heavy_check_mark:
2929

3030
`v1.0.5`
3131

‎config.ts‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
1-
if(!process.env.PUBLIC_KEY&&!process.env.PRIVATE_KEY&&!process.env.ETH_IN_AMOUNT&&!process.env.DB_URL){
1+
if(!process.env.INFURA_API_KEY&&!process.env.PUBLIC_KEY&&!process.env.PRIVATE_KEY&&!process.env.ETH_IN_AMOUNT&&!process.env.DB_URL){
22

3-
thrownewError("PUBLIC_KEY && PRIVATE_KEY && ETH_IN_AMOUNT && DB_URL, Must be defined in your .env file");
3+
thrownewError("INFURA_API_KEY &&PUBLIC_KEY && PRIVATE_KEY && ETH_IN_AMOUNT && DB_URL, Must be defined in your .env file");
44
}
55
exportconstconfig={
66
WALLET:{
77
PUBLIC_KEY:process.env.PUBLIC_KEY!,
88
PRIVATE_KEY:process.env.PRIVATE_KEY!
99
},
1010
PROVIDERS:{
11-
INFURA_API_KEY:''
11+
INFURA_API_KEY:process.env.INFURA_API_KEY!
1212
},
1313
NETWORK:{
14-
ID:1// 1 eth, 56 is bsc, 137 polygon, 10 optimism, 42161 arbitrum
14+
ID:process.env.NETWORK_ID||1// 1 eth, 56 is bsc, 137 polygon, 10 optimism, 42161 arbitrum
1515
},
16+
PROFIT_THRESHOLD:{// profit % you atleast want
17+
BUY:0.2,
18+
SELL:2
19+
},
20+
SLIPPAGE:0.5,
21+
EXPLORER:'',
1622
PRICE_CHECK_INTERVAL_IN_SECONDS:process.env.PRICE_CHECK_INTERVAL_IN_SECONDS||45,
1723
ETH_IN_AMOUNT:parseFloat(process.env.ETH_IN_AMOUNT!),
1824
DB_URL:process.env.DB_URL!

‎src/index.ts‎

Lines changed: 139 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import chalk from "chalk";
22
import{connect}from"mongoose";
33
import{schedule}from"node-cron";
44
import{config}from"../config";
5-
import{OneInch}from"./lib/1inch.io";
6-
import{Quote,Token}from"./types/1inch";
5+
import{OneInch}from"./lib";
6+
import{Quote,Direction}from"./types";
77
constchalkTable=require('chalk-table');
88
importBigNumberfrom"bignumber.js";
9-
import{flat}from"./utils";
9+
import{buildTradeMsg,flat}from"./utils";
1010
import{MONITORED_TOKENS}from"./data/token";
1111

1212
constMain=async()=>{
@@ -37,7 +37,6 @@ const Main = async () => {
3737
constoptions={
3838
useNewUrlParser:true,
3939
useUnifiedTopology:true,
40-
useCreateIndex:true,
4140
keepAlive:true,
4241
connectTimeoutMS:60000,
4342
socketTimeoutMS:60000,
@@ -47,7 +46,7 @@ const Main = async () => {
4746
console.log("Connected to MongoDb :)");
4847
}).catch(async(err)=>{
4948
leterror=JSON.parse(JSON.stringify(err))
50-
console.log('Mongo Error:',error?.name);
49+
console.log('Mongo Error:',error);
5150
});
5251
console.log(`---`.repeat(10));
5352

@@ -60,6 +59,9 @@ const Main = async () => {
6059
console.log(`---`.repeat(10));
6160

6261
letethInAmount=newBigNumber(config.ETH_IN_AMOUNT).shiftedBy(18).toString()
62+
leton_cooldown=false
63+
letmessage=''
64+
6365
schedule(`*/${config.PRICE_CHECK_INTERVAL_IN_SECONDS} * * * * *`,asyncfunction(){
6466
console.log(`***`.repeat(10));
6567
MONITORED_TOKENS.forEach(async(token:any)=>{
@@ -110,8 +112,140 @@ const Main = async () => {
110112
]);
111113
if(JSON.stringify(best_buy_protocols)!=JSON.stringify(best_sell_protocols)){
112114
console.log(table);
115+
116+
117+
if(profit_pct>=config.PROFIT_THRESHOLD.BUY&&!on_cooldown){
118+
letnonce:number=awaitoneInch.getNonce()
119+
console.log(`Nonce:`,nonce);
120+
121+
on_cooldown=true
122+
/**
123+
* Start of Buy => Approve? => Sell Txs
124+
*/
125+
try{
126+
127+
console.log(`Initiating a buy for token${token.ticker} ...`);
128+
// build buy Tx
129+
lettxData=awaitoneInch.buildTx({
130+
srcToken:'0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
131+
toToken:token.address,
132+
srcAmount:ethInAmount,
133+
slippage:config.SLIPPAGE
134+
})
135+
console.log(`Buy Tx Data:`,txData);
136+
137+
// send a buy Tx
138+
nonce++;
139+
oneInch.sendTx({
140+
data:txData.tx,
141+
nonce
142+
}).then(async(tx:any)=>{
143+
if(tx.hash){
144+
145+
// build Buy Tg Msg
146+
message=awaitbuildTradeMsg({data:tx,profit_pct:profit_pct,side:Direction.BUY})
147+
// send Msg to Tg
148+
sendMessage(users,message);
149+
150+
try{
151+
/**
152+
* Approve Token if it has not been approved before and save it to db
153+
*/
154+
// approve if token has not been approved
155+
consttoken_is_approved=awaitApprove.exists({token:token,by:process.env.PUBLIC_KEY!.toLowerCase()})
156+
if(!token_is_approved){
157+
// approve if not approved
158+
message=`Approving${token.description}...`
159+
sendMessage(users,message)
160+
lettxData=awaitoneInch.approve(token.address)
161+
nonce++;
162+
awaitoneInch.sendTx({
163+
data:txData.tx,
164+
nonce
165+
}).then((tx:any)=>{
166+
console.log(`${token.ticker} has been approved successfully.`)
167+
sendMessage(users,message)
168+
}).catch((err)=>{
169+
console.log(`Error: `,err)
170+
});
171+
}
172+
173+
174+
/**
175+
* Get the balance of the bought token shpuld be atleast be 1/2 of what was expected
176+
*/
177+
178+
lettries=0
179+
lettokenBalance='0'
180+
while(tries<2000){
181+
tokenBalance=awaitoneInch.balanceOf(tx.toToken.address)
182+
if(parseInt(tokenBalance)>parseInt(newBigNumber(token_amount).multipliedBy(0.5).toString())){
183+
break
184+
}
185+
tries++
186+
}
187+
/**
188+
* End of Balance Check
189+
*/
190+
191+
/**
192+
* Sell the bought tokens/assets to the exchange with the best rates
193+
*/
194+
message=`Initiating a sell for token${token.ticker}...`
195+
// build Sell Tx
196+
lettxData=awaitoneInch.buildTx({
197+
srcToken:token.address,
198+
toToken:'0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
199+
srcAmount:tokenBalance,
200+
slippage:config.SLIPPAGE
201+
})
202+
console.log(`Sell Tx Data:`,txData);
203+
204+
// send the sell Tx
205+
nonce++;
206+
oneInch.sendTx({
207+
data:txData.tx,
208+
nonce
209+
}).then(async(tx:any)=>{
210+
if(tx.hash){
211+
212+
// build Sell Tg Msg
213+
message=awaitbuildTradeMsg({data:tx,profit_pct:profit_pct,side:Direction.SELL})
214+
// send Msg to Tg
215+
sendMessage(users,message);
216+
217+
218+
}
219+
}).catch((err)=>{
220+
console.log(`Error:`,err)
221+
})
222+
223+
/**
224+
* End of Sell Tx
225+
*/
226+
227+
}
228+
catch(error){
229+
console.error(`Error:`,error)
230+
}
231+
}
232+
}
233+
).catch((err)=>{
234+
console.log(`Error:`,err);
235+
});
236+
237+
238+
}catch(error){
239+
console.error(`Error:`,error)
240+
}
241+
/**
242+
* End of Buy => Approve? => Sell Txs
243+
*/
244+
245+
}
113246
}
114247

248+
115249
}catch(error:any){
116250
console.error('Error:',JSON.parse(JSON.stringify(error)).code);
117251
}

‎src/lib/1inch.ts‎

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
importaxiosfrom"axios";
22
import{config}from"../../config";
33
import{Quote}from"../types/1inch";
4+
import{toHex}from"../utils";
45
import{Aggr}from"./aggr";
56

67
exportclassOneInchextendsAggr{
@@ -42,13 +43,24 @@ export class OneInch extends Aggr {
4243
*@param slippage - slippage tolerance
4344
*@returns tx data that can be send to the network
4445
*/
45-
buildTx=async(srcToken:string,toToken:string,srcAmount:number,slippage?:number):Promise<string>=>{
46+
buildTx=async(params:{srcToken:string,toToken:string,srcAmount:number|string,slippage?:number,gasLimit?:string})=>{
47+
const{ srcToken, toToken, srcAmount, slippage, gasLimit}=params;
4648
try{
4749
letdefaultSlippage=0.5
48-
const{ data}=awaitaxios({
50+
console.log(defaultSlippage)
51+
const{ data}:any=awaitaxios({
4952
method:"GET",
50-
url:`${this.API_URL}${config.NETWORK.ID}/swap?fromTokenAddress=${srcToken}&toTokenAddress=${toToken}&amount=${srcAmount}&fromAddress=${config.WALLET.PUBLIC_KEY}&disableEstimate=false&slippage=${slippage ?`${slippage}` :defaultSlippage}`
53+
url:`${this.API_URL}${config.NETWORK.ID}/swap?fromTokenAddress=${srcToken}&toTokenAddress=${toToken}&amount=${srcAmount}&fromAddress=${config.WALLET.PUBLIC_KEY}&disableEstimate=true&slippage=${slippage ?`${slippage}` :defaultSlippage}`
5154
})
55+
deletedata.tx.gasPrice;//ethersjs will find the gasPrice needed
56+
deletedata.tx.gas;
57+
58+
if(gasLimit){
59+
data.tx.gasLimit=toHex(parseInt(gasLimit))
60+
}
61+
62+
data.tx["value"]=toHex(parseInt(data.tx["value"]))
63+
5264
returndata
5365
}catch(error:any){
5466
thrownewError(JSON.stringify(error));
@@ -80,10 +92,14 @@ export class OneInch extends Aggr {
8092
*/
8193
approve=async(tokenAddress:string,amount?:string)=>{
8294
try{
83-
const{ data}=awaitaxios({
95+
const{ data}:any=awaitaxios({
8496
method:"GET",
8597
url:`${this.API_URL}${config.NETWORK.ID}/approve/calldata?tokenAddress=${tokenAddress}`
8698
})
99+
deletedata.tx.gasPrice;//ethersjs will find the gasPrice needed
100+
deletedata.tx.gas;
101+
102+
data.tx["value"]=toHex(parseInt(data.tx["value"]))
87103
returndata
88104
}catch(error:any){
89105
thrownewError(JSON.stringify(error));

‎src/lib/aggr.ts‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,18 @@ export abstract class Aggr {
1313
this.account=newWallet(config.WALLET.PRIVATE_KEY,this.provider);
1414
this.API_URL=api_url
1515
}
16-
16+
/**
17+
* Sends a tx to the blockchain
18+
*@param data - Tx data
19+
*@param nonce - wallet current nonce
20+
*@returns Tx hash if successful else error message
21+
*/
1722
sendTx=async(params:{data:any,nonce:number})=>{
1823
const{ data, nonce}=params
1924
try{
2025

2126
if(!isNaN(nonce)){
22-
data.nonce=nonce
27+
data.nonce=nonce+1
2328
}
2429

2530
consttx=awaitthis.account.sendTransaction(data)
@@ -47,9 +52,9 @@ export abstract class Aggr {
4752

4853
/**
4954
* Gets the current nonce of a wallet
50-
*@returns nonce
55+
*@returns wallet's currentnonce
5156
*/
5257
getNonce=async():Promise<number>=>{
53-
returnawaitthis.provider.getTransactionCount(config.WALLET.PUBLIC_KEY)
58+
returnawaitthis.account.getTransactionCount()
5459
}
5560
}

‎src/lib/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export*from'./1inch'

‎src/types/enums.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
exportenumDirection{
2+
SELL='SELL',
3+
BUY='BUY'
4+
}

‎src/types/index.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export*from'./1inch';
2+
export*from'./enums';

‎src/utils/common.ts‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,42 @@
1+
importBigNumberfrom"bignumber.js"
2+
import{config}from"../../config"
3+
import{Direction}from"../types"
4+
15
exportconstflat=async(arr:Array<any>,start:number=0,end:number=3):Promise<Array<any>>=>{
26
if(start<end){
37
start+=1
48
returnflat([].concat(...arr),start)
59
}
610
returnarr
11+
}
12+
13+
exportconsttoHex=(value:number)=>{
14+
return`0x${value.toString(16)}`
15+
}
16+
exportconsthumanizeBalance=async(balance:string|number,decimals:number)=>{
17+
returnnewBigNumber(balance).shiftedBy(-decimals).toString()
18+
}
19+
20+
exportconstbuildTradeMsg=async(params:{data:any,profit_pct:number,side:Direction}):Promise<string>=>{
21+
const{ data, profit_pct, side}=params
22+
23+
letdexes=(awaitflat(data.protocols)).map((quote:any)=>quote.name).join(', ')
24+
letmsg=`* NEW TRADE NOTIFICATION *\n-- - `
25+
msg+=`\n*Direction:*${side}`
26+
if(side==Direction.SELL){
27+
28+
msg+=`\n*Token Amount:*${awaithumanizeBalance(data.fromTokenAmount,data.fromToken.decimals)}`
29+
msg+=`\n*Token:*${data.fromToken.name}`
30+
msg+=`\n*ETH Amount:*${awaithumanizeBalance(data.toTokenAmount,data.toToken.decimals)}`
31+
}else{
32+
msg+=`\n*ETH Amount:*${awaithumanizeBalance(data.fromTokenAmount,data.fromToken.decimals)}`
33+
msg+=`\n*Token Amount:*${awaithumanizeBalance(data.toTokenAmount,data.toToken.decimals}`
34+
msg+=`\n*Token:*${data.fromToken.name}`
35+
}
36+
msg+=`\n*Profit PCT:*${profit_pct.toFixed(6)}%`
37+
msg+=`\n*Dex:*${dexes}`
38+
msg+=`\n*Gas Limit:*${data.gasLimit}`
39+
msg+=`\n*Hash:* [${data.hash.toUpperCase()}](${config.EXPLORER}${data.hash})`
40+
41+
returnmsg
742
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp