Skip to content

Commit b9855fe

Browse files
committed
got bree/jobs to work with swap
1 parent 104fa71 commit b9855fe

File tree

7 files changed

+421
-137
lines changed

7 files changed

+421
-137
lines changed

package.json

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
{
2-
"name": "jupiter-core-example",
2+
"name": "jup-dca-bot",
33
"version": "1.0.0",
4-
"description": "",
4+
"description": "dollar cost averaging bot",
55
"source": "src/index.ts",
6-
"main": "dist/main.js",
6+
"main": "src/index.ts",
77
"module": "dist/module.js",
88
"types": "dist/types.d.ts",
99
"scripts": {
10+
"dev": "TS_NODE=true NODE_OPTIONS=\"--loader ts-node/esm --experimental-specifier-resolution=node\" node .",
1011
"start": "ts-node ./src/index.ts"
1112
},
1213
"keywords": [],
@@ -15,7 +16,6 @@
1516
"dependencies": {
1617
"@jup-ag/core": "^1.0.0-beta.16",
1718
"@solana/wallet-adapter-base": "^0.7.1",
18-
"@solana/web3.js": "^1.31.0",
1919
"@types/bs58": "^4.0.1",
2020
"@types/isomorphic-fetch": "^0.0.35",
2121
"bs58": "^4.0.1",
@@ -25,9 +25,12 @@
2525
"ts-node": "^10.4.0"
2626
},
2727
"devDependencies": {
28+
"@solana/web3.js": "^1.36.0",
29+
"bree": "^7.2.0",
2830
"typescript": "^4.5.3"
2931
},
3032
"resolutions": {
3133
"@solana/buffer-layout": "4.0.0"
32-
}
34+
},
35+
"type": "module"
3336
}

src/constants/index.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Cluster } from "@solana/web3.js";
22
import bs58 from "bs58";
33
import { Keypair } from "@solana/web3.js";
44

5-
require("dotenv").config();
5+
import 'dotenv/config'
66

77
// Endpoints, connection
88
export const ENV: Cluster = (process.env.CLUSTER as Cluster) || "mainnet-beta";
@@ -30,8 +30,6 @@ export const OUTPUT_MINT_ADDRESS =
3030
? "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt" // SRM
3131
: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"; // USDT
3232

33-
export const SWAP_INTERVAL_MS = 5000;
34-
3533
// Interface
3634
export interface Token {
3735
chainId: number; // 101,

src/index.ts

Lines changed: 27 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,35 @@
1-
import { Connection, PublicKey } from "@solana/web3.js";
2-
import fetch from "isomorphic-fetch";
3-
4-
import { Jupiter, RouteInfo, TOKEN_LIST_URL } from "@jup-ag/core";
5-
import {
6-
ENV,
7-
INPUT_MINT_ADDRESS,
8-
OUTPUT_MINT_ADDRESS,
9-
SOLANA_RPC_ENDPOINT,
10-
SWAP_INTERVAL_MS,
11-
Token,
12-
USER_KEYPAIR,
13-
} from "./constants";
14-
15-
const getRoutes = async ({
16-
jupiter,
17-
inputToken,
18-
outputToken,
19-
inputAmount,
20-
slippage,
21-
}: {
22-
jupiter: Jupiter;
23-
inputToken?: Token;
24-
outputToken?: Token;
25-
inputAmount: number;
26-
slippage: number;
27-
}) => {
28-
try {
29-
if (!inputToken || !outputToken) {
30-
return null;
31-
}
32-
33-
console.log(
34-
`Getting routes for ${inputAmount} ${inputToken.symbol} -> ${outputToken.symbol}...`
35-
);
36-
const inputAmountInSmallestUnits = inputToken
37-
? Math.round(inputAmount * 10 ** inputToken.decimals)
38-
: 0;
39-
const routes =
40-
inputToken && outputToken
41-
? await jupiter.computeRoutes({
42-
inputMint: new PublicKey(inputToken.address),
43-
outputMint: new PublicKey(outputToken.address),
44-
inputAmount: inputAmountInSmallestUnits, // raw input amount of tokens
45-
slippage,
46-
forceFetch: true,
47-
})
48-
: null;
49-
50-
if (routes && routes.routesInfos) {
51-
console.log("Possible number of routes:", routes.routesInfos.length);
52-
console.log(
53-
"Best quote: ",
54-
routes.routesInfos[0].outAmount / 10 ** outputToken.decimals,
55-
`(${outputToken.symbol})`
56-
);
57-
return routes;
58-
} else {
59-
return null;
60-
}
61-
} catch (error) {
62-
throw error;
63-
}
64-
};
65-
66-
const executeSwap = async ({
67-
jupiter,
68-
route,
69-
}: {
70-
jupiter: Jupiter;
71-
route: RouteInfo;
72-
}) => {
73-
try {
74-
// Prepare execute exchange
75-
const { execute } = await jupiter.exchange({
76-
routeInfo: route,
77-
});
78-
79-
// Execute swap
80-
const swapResult: any = await execute(); // Force any to ignore TS misidentifying SwapResult type
81-
82-
if (swapResult.error) {
83-
console.log(swapResult.error);
84-
} else {
85-
console.log(`https://explorer.solana.com/tx/${swapResult.txid}`);
86-
console.log(
87-
`inputAddress=${swapResult.inputAddress.toString()} outputAddress=${swapResult.outputAddress.toString()}`
88-
);
89-
console.log(
90-
`inputAmount=${swapResult.inputAmount} outputAmount=${swapResult.outputAmount}`
91-
);
92-
}
93-
} catch (error) {
94-
throw error;
95-
}
96-
};
97-
98-
function delay(ms: number) {
99-
return new Promise(resolve => setTimeout(resolve, ms))
100-
}
1+
import * as path from 'node:path';
2+
import * as process from 'node:process';
3+
import { fileURLToPath } from 'node:url';
4+
import Bree from 'bree';
1015

1026
const main = async () => {
1037
try {
104-
const connection = new Connection(SOLANA_RPC_ENDPOINT); // Setup Solana RPC connection
105-
const tokens: Token[] = await (await fetch(TOKEN_LIST_URL[ENV])).json(); // Fetch token list from Jupiter API
106-
107-
// Load Jupiter
108-
const jupiter = await Jupiter.load({
109-
connection,
110-
cluster: ENV,
111-
user: USER_KEYPAIR, // or public key
8+
const bree = new Bree({
9+
/**
10+
* Always set the root option when doing any type of
11+
* compiling with bree. This just makes it clearer where
12+
* bree should resolve the jobs folder from. By default it
13+
* resolves to the jobs folder relative to where the program
14+
* is executed.
15+
*/
16+
root: path.join(path.dirname(fileURLToPath(import.meta.url)), 'jobs'),
17+
/**
18+
* We only need the default extension to be "ts"
19+
* when we are running the app with ts-node - otherwise
20+
* the compiled-to-js code still needs to use JS
21+
*/
22+
defaultExtension: process.env.TS_NODE ? 'ts' : 'js',
23+
jobs: [
24+
{
25+
name: 'job',
26+
interval: 'every 15 seconds'
27+
},
28+
]
11229
});
11330

114-
// If you know which input/output pair you want
115-
const inputToken = tokens.find((t) => t.address == INPUT_MINT_ADDRESS); // USDC Mint Info
116-
const outputToken = tokens.find((t) => t.address == OUTPUT_MINT_ADDRESS); // USDT Mint Info
117-
118-
while (true) {
119-
/* code to wait on goes here (sync or async) */
120-
const routes = await getRoutes({
121-
jupiter,
122-
inputToken,
123-
outputToken,
124-
inputAmount: .01, // 1 unit in UI
125-
slippage: 1, // % slippage
126-
});
127-
// Routes are sorted based on outputAmount, so ideally the first route is the best.
128-
await executeSwap({ jupiter, route: routes!.routesInfos[0] });
129-
await delay(SWAP_INTERVAL_MS);
130-
}
31+
bree.start();
32+
13133
} catch (error) {
13234
console.log({ error });
13335
}

src/jobs/job.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { parentPort } from 'node:worker_threads';
2+
import process from 'node:process';
3+
4+
import { swap, tokens } from '../jup';
5+
import {
6+
INPUT_MINT_ADDRESS,
7+
OUTPUT_MINT_ADDRESS,
8+
} from '../constants';
9+
10+
11+
// If you know which input/output pair you want
12+
const inputToken = tokens.find((t) => t.address == INPUT_MINT_ADDRESS); // USDC Mint Info
13+
const outputToken = tokens.find((t) => t.address == OUTPUT_MINT_ADDRESS); // USDT Mint Info
14+
15+
(async () => {
16+
try {
17+
await swap({
18+
inputToken,
19+
outputToken,
20+
inputAmount: .01,
21+
slippage: 1,
22+
});
23+
// exit properly for the worker
24+
process.exit(0);
25+
} catch (error) {
26+
console.log({ error });
27+
// exit properly for the worker
28+
process.exit(1);
29+
}
30+
})();

src/jup/index.ts

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import fetch from "isomorphic-fetch";
2+
import { Jupiter, RouteInfo, TOKEN_LIST_URL } from "@jup-ag/core";
3+
import { PublicKey, Connection } from "@solana/web3.js";
4+
import {
5+
ENV,
6+
INPUT_MINT_ADDRESS,
7+
OUTPUT_MINT_ADDRESS,
8+
SOLANA_RPC_ENDPOINT,
9+
Token,
10+
USER_KEYPAIR,
11+
} from "../constants";
12+
13+
// Fetch token list from Jupiter API
14+
export const tokens: Token[] = await (await fetch(TOKEN_LIST_URL[ENV])).json();
15+
16+
// Load Jupiter
17+
const jupiterLoaded = async () => {
18+
const connection = new Connection(SOLANA_RPC_ENDPOINT); // Setup Solana
19+
const jupiter = await Jupiter.load({
20+
connection,
21+
cluster: ENV,
22+
user: USER_KEYPAIR, // or public key
23+
});
24+
return jupiter;
25+
};
26+
27+
const getRoutes = async ({
28+
jupiter,
29+
inputToken,
30+
outputToken,
31+
inputAmount,
32+
slippage,
33+
}: {
34+
jupiter: Jupiter;
35+
inputToken?: Token;
36+
outputToken?: Token;
37+
inputAmount: number;
38+
slippage: number;
39+
}) => {
40+
try {
41+
if (!inputToken || !outputToken) {
42+
return null;
43+
}
44+
45+
console.log(
46+
`Getting routes for ${inputAmount} ${inputToken.symbol} -> ${outputToken.symbol}...`
47+
);
48+
const inputAmountInSmallestUnits = inputToken
49+
? Math.round(inputAmount * 10 ** inputToken.decimals)
50+
: 0;
51+
const routes =
52+
inputToken && outputToken
53+
? await jupiter.computeRoutes({
54+
inputMint: new PublicKey(inputToken.address),
55+
outputMint: new PublicKey(outputToken.address),
56+
inputAmount: inputAmountInSmallestUnits, // raw input amount of tokens
57+
slippage,
58+
forceFetch: true,
59+
})
60+
: null;
61+
62+
if (routes && routes.routesInfos) {
63+
console.log("Possible number of routes:", routes.routesInfos.length);
64+
console.log(
65+
"Best quote: ",
66+
routes.routesInfos[0].outAmount / 10 ** outputToken.decimals,
67+
`(${outputToken.symbol})`
68+
);
69+
return routes;
70+
} else {
71+
return null;
72+
}
73+
} catch (error) {
74+
throw error;
75+
}
76+
};
77+
78+
const executeSwap = async ({
79+
jupiter,
80+
route,
81+
}: {
82+
jupiter: Jupiter;
83+
route: RouteInfo;
84+
}) => {
85+
try {
86+
// Prepare execute exchange
87+
const { execute } = await jupiter.exchange({
88+
routeInfo: route,
89+
});
90+
91+
// Execute swap
92+
const swapResult: any = await execute(); // Force any to ignore TS misidentifying SwapResult type
93+
94+
if (swapResult.error) {
95+
console.log(swapResult.error);
96+
} else {
97+
console.log(`https://explorer.solana.com/tx/${swapResult.txid}`);
98+
// console.log(
99+
// `inputAddress=${swapResult.inputAddress.toString()} outputAddress=${swapResult.outputAddress.toString()}`
100+
// );
101+
console.log(
102+
`inputAmount=${swapResult.inputAmount} outputAmount=${swapResult.outputAmount}`
103+
);
104+
}
105+
} catch (error) {
106+
throw error;
107+
}
108+
};
109+
110+
export const swap = async ({
111+
inputToken,
112+
outputToken,
113+
inputAmount,
114+
slippage,
115+
}: {
116+
inputToken?: Token;
117+
outputToken?: Token;
118+
inputAmount: number; // 1 unit in UI
119+
slippage: number; // % slippage
120+
}) => {
121+
try {
122+
console.log('Starting Jupiter swap!');
123+
const routes = await getRoutes({
124+
jupiter: await jupiterLoaded(),
125+
inputToken,
126+
outputToken,
127+
inputAmount,
128+
slippage,
129+
});
130+
// Routes are sorted based on outputAmount, so ideally the first route is the best.
131+
await executeSwap({ jupiter: await jupiterLoaded(), route: routes!.routesInfos[0] });
132+
} catch (error) {
133+
console.log({ error });
134+
}
135+
};

0 commit comments

Comments
 (0)