@@ -5,10 +5,27 @@ import { Jupiter, TOKEN_LIST_URL, SwapMode } from "@jup-ag/core";
5
5
import { PublicKey , Connection } from "@solana/web3.js" ;
6
6
import * as cron from "node-cron" ;
7
7
import cronstrue from "cronstrue" ;
8
- import { Token , MINT_ADDRESSES , USER_KEYPAIR , SOLANA_RPC_ENDPOINT , WRAP_UNWRAP_SOL } from "./constants" ;
8
+ import { Token , MINT_ADDRESSES , USER_KEYPAIR , SOLANA_RPC_ENDPOINT , WRAP_UNWRAP_SOL , tradingEnabled , tradingRetries } from "./constants" ;
9
9
import { dcaconfig } from './dcaconfig'
10
10
import JSBI from 'jsbi' ;
11
11
12
+ // Simple delay function
13
+ function delay ( ms : number ) {
14
+ return new Promise ( resolve => setTimeout ( resolve , ms ) ) ;
15
+ }
16
+
17
+ // Date time logging object
18
+ function ptst ( ) {
19
+ let timestsmp : String = new Date ( ) . toLocaleString ( ) ;
20
+ return timestsmp ;
21
+ }
22
+
23
+ // Add colour to console text
24
+ function setcolour ( ctxt : string , colnum : number ) {
25
+ return ( '\x1b[' + colnum + 'm' + ctxt + '\x1b[0m' ) ;
26
+ }
27
+
28
+ // Jupiter swap code
12
29
const jupiterSwap = async ( {
13
30
jupiter,
14
31
inputToken,
@@ -47,45 +64,95 @@ const jupiterSwap = async ({
47
64
: null ;
48
65
49
66
if ( tradingEnabled ) {
50
- if ( routes && routes . routesInfos ) {
51
- // Prepare execute exchange
52
- const { execute } = await jupiter . exchange ( {
53
- routeInfo : routes ! . routesInfos [ 0 ] ,
54
- } ) ;
55
- // Execute swap
56
- // Force any to ignore TS misidentifying SwapResult type
57
- const swapResult : any = await execute ( ) ;
58
- if ( swapResult . error ) {
59
- console . log ( swapResult . error ) ;
60
- } else {
61
- // trying to keep these on one line
62
- process . stdout . write (
63
- `${ swapResult . inputAmount / ( 10 ** inputToken . decimals ) } `
64
- ) ;
65
- process . stdout . write ( `${ inputToken . symbol } -> ` ) ;
66
- process . stdout . write (
67
- `${ swapResult . outputAmount / ( 10 ** outputToken . decimals ) } `
68
- ) ;
69
- process . stdout . write ( `${ outputToken . symbol } : ` ) ;
70
- console . log ( `https://solscan.io/tx/${ swapResult . txid } ` ) ;
71
- }
72
- } else {
73
- console . log ( "Error during jupiter.computeRoutes()." ) ;
74
- }
67
+
68
+ // handle transaction retries
69
+ let i : number = 0 ;
70
+
71
+ do {
72
+ process . stdout . write ( await ptst ( ) + " - recurring DCA Swap Attempt #" + ( i + 1 ) )
73
+ i ++ ;
74
+
75
+ try {
76
+
77
+ const routes = inputToken && outputToken
78
+ ? await jupiter . computeRoutes ( {
79
+ inputMint : new PublicKey ( inputToken . address ) ,
80
+ outputMint : new PublicKey ( outputToken . address ) ,
81
+ amount : inputAmountInSmallestUnits ,
82
+ slippageBps : slippage ,
83
+ feeBps : 0 ,
84
+ forceFetch : true ,
85
+ onlyDirectRoutes : false ,
86
+ filterTopNResult : 1 ,
87
+ enforceSingleTx : false ,
88
+ swapMode : SwapMode . ExactIn ,
89
+ } )
90
+ : null ;
91
+
92
+ if ( routes && routes . routesInfos ) {
93
+
94
+ console . log ( " - " + routes . routesInfos . length + ' routes found' ) ;
95
+
96
+ const { execute } = await jupiter . exchange ( {
97
+ routeInfo : routes ! . routesInfos [ 0 ] ,
98
+ } ) ;
99
+ // Execute swap
100
+ // Force any to ignore TS misidentifying SwapResult type
101
+ const swapResult : any = await execute ( ) ;
102
+
103
+ if ( swapResult . error ) {
104
+ //console.log(swapResult.error);
105
+ let swaperr = String ( swapResult . error ) ;
106
+ let simpleerror = setcolour ( swaperr . split ( '\n' , 1 ) [ 0 ] , 33 ) ;
107
+ console . log ( await ptst ( ) + " - " + simpleerror ) ;
108
+ } else {
109
+ // trying to keep these on one line
110
+ process . stdout . write ( await ptst ( ) + " - " ) ;
111
+
112
+ process . stdout . write (
113
+ setcolour ( `${ swapResult . inputAmount / ( 10 ** inputToken . decimals ) } ` , 32 )
114
+ ) ;
115
+ process . stdout . write ( `${ inputToken . symbol } -> ` ) ;
116
+ process . stdout . write (
117
+ setcolour ( `${ swapResult . outputAmount / ( 10 ** outputToken . decimals ) } ` , 32 )
118
+ ) ;
119
+ process . stdout . write ( `${ outputToken . symbol } : ` ) ;
120
+ console . log ( `https://solscan.io/tx/${ swapResult . txid } ` ) ;
121
+ break ; // exit retry loop
122
+ }
123
+
124
+ } else {
125
+ console . log ( await ptst ( ) + " - Error during jupiter.computeRoutes()." ) ;
126
+ }
127
+
128
+ } catch ( error ) {
129
+ console . log ( 'Failure in route loop lookup.' ) ;
130
+ throw error ;
131
+ }
132
+
133
+ await delay ( 5000 ) ; // wait for 5 second between attempts
134
+
135
+ } while ( i < tradingRetries )
136
+
137
+
138
+
75
139
} else {
76
140
console . log ( "Trading not enabled. You need to enable it in the .env for swaps to take place." ) ;
77
141
}
78
142
79
-
80
- } catch ( error ) {
143
+
144
+ } catch ( error ) {
145
+ console . log ( 'Throw error check on tokens' ) ;
81
146
throw error ;
82
147
}
83
- } ;
84
-
148
+ } ;
149
+
150
+
85
151
const main = async ( ) => {
86
152
try {
87
- console . log ( "Starting Jupiter DCA Bot" ) ;
88
-
153
+ console . log ( setcolour ( "Starting Jupiter V3 DCA Bot" , 92 ) ) ;
154
+ console . log ( "The bot will retry " + String ( tradingRetries ) + " times if the swap fails for each scheduled period." ) ;
155
+
89
156
const cluster = "mainnet-beta" ; // Force mainnet, as this uses Jupiter which is not deployed on devnet/testnet
90
157
const connection = new Connection ( SOLANA_RPC_ENDPOINT ) ;
91
158
const jupiter = await Jupiter . load ( {
@@ -127,6 +194,10 @@ const main = async () => {
127
194
console . log ( "- invalid cron expression" ) ;
128
195
console . log ( "- inputToken or outputToken does not exist in MINT_ADDRESSES" ) ;
129
196
console . log ( "Validating dcaconfig.ts ..." ) ;
197
+
198
+ // separator
199
+ console . log ( '-----------------------------' ) ;
200
+
130
201
const filteredJobs = dcaconfig . filter ( job => {
131
202
return ( cron . validate ( job . cron )
132
203
&& job . inputToken in MINT_ADDRESSES
@@ -136,8 +207,11 @@ const main = async () => {
136
207
137
208
console . log ( "Scheduling swaps:" ) ;
138
209
filteredJobs . map ( job => {
139
- console . log ( ` ${ job . amount } ${ job . inputToken } for ${ job . outputToken } ${ cronstrue . toString ( job . cron ) } `) ;
210
+ console . log ( setcolour ( String ( job . amount ) , 32 ) + ` ${ job . inputToken } for ${ job . outputToken } ${ cronstrue . toString ( job . cron ) } `) ;
140
211
} ) ;
212
+
213
+ // separator
214
+ console . log ( '-----------------------------' ) ;
141
215
142
216
filteredJobs . forEach ( job => {
143
217
const inputToken = tokens . find ( ( t ) =>
0 commit comments