Skip to content
This repository was archived by the owner on Nov 14, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,478 changes: 3,478 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"bs58": "^4.0.1",
"dotenv": "^10.0.0",
"isomorphic-fetch": "^3.0.0",
"mathjs": "^10.4.2",
"node-fetch": "^3.1.0",
"ts-node": "^10.4.0"
},
Expand Down
11 changes: 7 additions & 4 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { Keypair } from "@solana/web3.js";

require("dotenv").config();

// How much USD to trade
export const AMOUNT = 10;

// Endpoints, connection
export const ENV: Cluster = (process.env.CLUSTER as Cluster) || "mainnet-beta";

Expand All @@ -12,13 +15,13 @@ export const ENV: Cluster = (process.env.CLUSTER as Cluster) || "mainnet-beta";
export const SOLANA_RPC_ENDPOINT =
ENV === "devnet"
? "https://api.devnet.solana.com"
: "https://ssc-dao.genesysgo.net";
: "https://solana-api.projectserum.com";

// Wallets
export const WALLET_PRIVATE_KEY =
process.env.WALLET_PRIVATE_KEY || "PASTE YOUR WALLET PRIVATE KEY";
export const USER_PRIVATE_KEY = bs58.decode(WALLET_PRIVATE_KEY);
export const USER_KEYPAIR = Keypair.fromSecretKey(USER_PRIVATE_KEY);
//export const USER_PRIVATE_KEY = bs58.decode(WALLET_PRIVATE_KEY);
export const USER_KEYPAIR = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(WALLET_PRIVATE_KEY)));

// Token Mints
export const INPUT_MINT_ADDRESS =
Expand All @@ -28,7 +31,7 @@ export const INPUT_MINT_ADDRESS =
export const OUTPUT_MINT_ADDRESS =
ENV === "devnet"
? "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt" // SRM
: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"; // USDT
: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"; // USDT

// Interface
export interface Token {
Expand Down
151 changes: 151 additions & 0 deletions src/findBestRoute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { Connection, Keypair, Transaction, PublicKey } from '@solana/web3.js'
import fetch from 'cross-fetch'
import { Wallet } from '@project-serum/anchor'
import { Jupiter, RouteInfo, TOKEN_LIST_URL } from "@jup-ag/core";
import {
ENV,
INPUT_MINT_ADDRESS,
OUTPUT_MINT_ADDRESS,
USER_KEYPAIR,
SOLANA_RPC_ENDPOINT,
Token,
AMOUNT,
} from "./constants";

// The GenesysGo RPC endpoint is currently free to use now.
// They may charge in the future. It is recommended that
// you are using your own RPC endpoint.
const connection = new Connection(SOLANA_RPC_ENDPOINT);

const getRoutes = async ({
jupiter,
inputToken,
outputToken,
inputAmount,
slippage,
}: {
jupiter: Jupiter;
inputToken?: Token;
outputToken?: Token;
inputAmount: number;
slippage: number;
}) => {
try {
if (!inputToken || !outputToken) {
return null;
}

console.log(
`Getting routes for ${inputAmount} ${inputToken.symbol} -> ${outputToken.symbol}...`
);
const inputAmountInSmallestUnits = inputToken
? Math.round(inputAmount * 10 ** inputToken.decimals)
: 0;
const routes =
inputToken && outputToken
? await jupiter.computeRoutes({
inputMint: new PublicKey(inputToken.address),
outputMint: new PublicKey(outputToken.address),
inputAmount: inputAmountInSmallestUnits, // raw input amount of tokens
slippage,
forceFetch: true,
})
: null;

if (routes && routes.routesInfos) {
console.log("Possible number of routes:", routes.routesInfos.length);
console.log(
"Best quote: ",
routes.routesInfos[0].outAmount / 10 ** outputToken.decimals,
`(${outputToken.symbol})`
);
return routes;
} else {
return null;
}
} catch (error) {
throw error;
}
}

export const findBestRoute = async (inToken: Token | undefined) => {
// Load Jupiter
const jupiter = await Jupiter.load({
connection,
cluster: ENV,
user: USER_KEYPAIR, // or public key
});
const routeMap = jupiter.getRouteMap();
const tokens: Token[] = await (await fetch(TOKEN_LIST_URL[ENV])).json(); // Fetch token list from Jupiter API


// One route (optimal route provided by jupiter)
const routes = await getRoutes({
jupiter,
inputToken: inToken,
outputToken: inToken,
inputAmount: AMOUNT, // 1 unit in UI
slippage: .1, // .1% slippage
});

const bestRoute = routes!.routesInfos[0];
const oneLegProfit = bestRoute.outAmountWithSlippage - bestRoute.inAmount;

// Calculate best routes to all other pairs that can be swapped for token

interface multiRouteInfo {
profit: number;
routes: RouteInfo[];
}

let maxProfitMultiRoute: multiRouteInfo | undefined;

for (const token of tokens) {
if (new Set((routeMap.get(token.address) || [])).has(inToken!.address)) {
const outputToken = tokens.find((f) => f.address === token.address);

const inRoute = await getRoutes({
jupiter,
inputToken: inToken,
outputToken,
inputAmount: AMOUNT,
slippage: .1,
})

const bestInRoute = inRoute!.routesInfos[0];

const outRoute = await getRoutes({
jupiter,
inputToken: outputToken,
outputToken: inToken,
inputAmount: bestInRoute.outAmountWithSlippage,
slippage: .1,
})

const bestOutRoute = outRoute!.routesInfos[0];

const profit = bestOutRoute.outAmountWithSlippage - bestInRoute.inAmount;
console.log(`Found multi route with profit ${profit}`);

if (maxProfitMultiRoute == undefined || profit > maxProfitMultiRoute.profit) {
maxProfitMultiRoute = {
profit: profit,
routes: [bestInRoute, bestOutRoute]
}
}

}
}

let bestRouteInfo = maxProfitMultiRoute;
/*
if (maxProfitMultiRoute == undefined || maxProfitMultiRoute?.profit < oneLegProfit) {
bestRouteInfo = {
profit: oneLegProfit,
routes: [bestRoute]
}
}
*/

return bestRouteInfo;
}
96 changes: 36 additions & 60 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@ import {
ENV,
INPUT_MINT_ADDRESS,
OUTPUT_MINT_ADDRESS,
USER_KEYPAIR,
SOLANA_RPC_ENDPOINT,
Token,
USER_KEYPAIR,
AMOUNT,
} from "./constants";

import {
findBestRoute
} from "./findBestRoute";

const getPossiblePairsTokenInfo = ({
tokens,
routeMap,
Expand Down Expand Up @@ -42,57 +47,6 @@ const getPossiblePairsTokenInfo = ({
}
};

const getRoutes = async ({
jupiter,
inputToken,
outputToken,
inputAmount,
slippage,
}: {
jupiter: Jupiter;
inputToken?: Token;
outputToken?: Token;
inputAmount: number;
slippage: number;
}) => {
try {
if (!inputToken || !outputToken) {
return null;
}

console.log(
`Getting routes for ${inputAmount} ${inputToken.symbol} -> ${outputToken.symbol}...`
);
const inputAmountInSmallestUnits = inputToken
? Math.round(inputAmount * 10 ** inputToken.decimals)
: 0;
const routes =
inputToken && outputToken
? await jupiter.computeRoutes({
inputMint: new PublicKey(inputToken.address),
outputMint: new PublicKey(outputToken.address),
inputAmount: inputAmountInSmallestUnits, // raw input amount of tokens
slippage,
forceFetch: true,
})
: null;

if (routes && routes.routesInfos) {
console.log("Possible number of routes:", routes.routesInfos.length);
console.log(
"Best quote: ",
routes.routesInfos[0].outAmount / 10 ** outputToken.decimals,
`(${outputToken.symbol})`
);
return routes;
} else {
return null;
}
} catch (error) {
throw error;
}
};

const executeSwap = async ({
jupiter,
route,
Expand Down Expand Up @@ -125,6 +79,7 @@ const executeSwap = async ({
}
};


const main = async () => {
try {
const connection = new Connection(SOLANA_RPC_ENDPOINT); // Setup Solana RPC connection
Expand All @@ -137,33 +92,54 @@ const main = async () => {
user: USER_KEYPAIR, // or public key
});

// Get routeMap, which maps each tokenMint and their respective tokenMints that are swappable
const routeMap = jupiter.getRouteMap();

// If you know which input/output pair you want
const inputToken = tokens.find((t) => t.address == INPUT_MINT_ADDRESS); // USDC Mint Info
const outputToken = tokens.find((t) => t.address == OUTPUT_MINT_ADDRESS); // USDT Mint Info

const maxProfitRoute = await findBestRoute(inputToken);
console.log(`Found ${maxProfitRoute?.routes.length} leg route with max profit ${maxProfitRoute?.profit} going through routes:\
${JSON.stringify(maxProfitRoute?.routes)}.`);
/*
// Alternatively, find all possible outputToken based on your inputToken
const possiblePairsTokenInfo = await getPossiblePairsTokenInfo({
tokens,
routeMap,
inputToken,
});

console.log(possiblePairsTokenInfo)


const routes = await getRoutes({
jupiter,
inputToken,
outputToken,
inputAmount: 1, // 1 unit in UI
slippage: 1, // 1% slippage
inputAmount: AMOUNT, // 1 unit in UI
slippage: .1, // .1% slippage
});

// Routes are sorted based on outputAmount, so ideally the first route is the best.
// await executeSwap({ jupiter, route: routes!.routesInfos[0] });
const bestRoute = routes!.routesInfos[0];
const profit = bestRoute.outAmountWithSlippage - bestRoute.inAmount;
*/
const fee = 500; // Tx fee in USDC = ~.000005 SOL
/*
console.log(bestRoute);
console.log(`Pre tx profit: ${profit}`)

// Percentage
// Routes that are too good to be true usually are
const percentage = profit / bestRoute.inAmount * 100;
*/
if (maxProfitRoute != undefined && maxProfitRoute?.profit > fee) {
console.log(`Making trade`);
for (let route of maxProfitRoute.routes) {
// await executeSwap({ jupiter, route: route });
}
}
main();
} catch (error) {
console.log({ error });
}
};

main();
main();
Loading