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
34 changes: 25 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Connection } from "@solana/web3.js";
import { Connection, PublicKey } from "@solana/web3.js";
import { Command } from "commander";
import { RPC_ENDPOINT } from "./constants";
import { createTokenAccounts, createTokenLedger, swapTokens } from "./janitor";
Expand Down Expand Up @@ -27,19 +27,35 @@ program
"Tokens from the top to create an account for",
"10"
)
.option(
"-o, --owner",
"Use another base58 public key than the keypair as the owner, to allow easy setup on behalf of a hardware wallet or a multisig..."
)
.option(
"-a, --allow-owner-off-curve",
"Allow the associated token account owner to be off curve"
)
.option("-d, --dry-run")
.addHelpText(
"beforeAll",
"Create token accounts based on top tokens, to reduce setup when trading or to setup platform fee accounts"
)
.action(async ({ keypair, tokensFromTop, dryRun }) => {
await createTokenAccounts(
CONNECTION,
loadKeypair(keypair),
tokensFromTop,
dryRun
);
});
.action(
async ({ keypair, owner, tokensFromTop, dryRun, allowOwnerOffCurve }) => {
const payerKeypair = loadKeypair(keypair);
const ownerPublicKey = owner
? new PublicKey(owner)
: payerKeypair.publicKey;
await createTokenAccounts(
CONNECTION,
ownerPublicKey,
payerKeypair,
tokensFromTop,
dryRun,
allowOwnerOffCurve
);
}
);

program
.command("swap-tokens")
Expand Down
47 changes: 26 additions & 21 deletions src/janitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js";
import fetch from "isomorphic-fetch";
import { USDC_MINT } from "./constants";
import { deserializeAccount, loadKeypair, sleep } from "./utils";
import JSBI from 'jsbi';
import JSBI from "jsbi";

type Address = string;

Expand All @@ -31,20 +31,19 @@ async function getTokenAccountInfos(

export async function createTokenAccounts(
connection: Connection,
userKeypair: Keypair,
owner: PublicKey,
payerKeypair: Keypair,
tokensFromTop: number,
dryRun: boolean
dryRun: boolean,
allowOwnerOffCurve: boolean
) {
// Top tokens are the most traded tokens, token #60 is about 30k USD of volume a day
const topTokens = (await (
await fetch("https://cache.jup.ag/top-tokens")
).json()) as Address[];

const shortlistedTokens = new Set(topTokens.slice(0, tokensFromTop));
const tokenAccountInfos = await getTokenAccountInfos(
connection,
userKeypair.publicKey
);
const tokenAccountInfos = await getTokenAccountInfos(connection, owner);
const exitingPlatformFeeAccountMints = new Set(
tokenAccountInfos.map(({ mint }) => mint.toBase58())
);
Expand All @@ -66,15 +65,16 @@ export async function createTokenAccounts(
// Create ATAs for missing token accounts
const shortlistedMints = Array.from(shortlistedTokens);
while (shortlistedMints.length > 0) {
let tx = new Transaction({ feePayer: userKeypair.publicKey });
let tx = new Transaction({ feePayer: payerKeypair.publicKey });

for (const mint of shortlistedMints.splice(0, 10)) {
const mintPk = new PublicKey(mint);
const ta = await Token.getAssociatedTokenAddress(
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
mintPk,
userKeypair.publicKey
owner,
allowOwnerOffCurve
);
tx.add(
...[
Expand All @@ -83,14 +83,14 @@ export async function createTokenAccounts(
TOKEN_PROGRAM_ID,
new PublicKey(mintPk),
ta,
userKeypair.publicKey,
userKeypair.publicKey
owner,
payerKeypair.publicKey
),
]
);
}

const signature = await connection.sendTransaction(tx, [userKeypair]);
const signature = await connection.sendTransaction(tx, [payerKeypair]);
console.log("signature:", signature);
}
}
Expand Down Expand Up @@ -154,31 +154,36 @@ export async function swapTokens(
const { routesInfos } = await jupiter.computeRoutes({
inputMint: tokenAccountInfo.mint,
outputMint: USDC_MINT,
amount: JSBI.BigInt((tokenAccountInfo.amount.toNumber())),
amount: JSBI.BigInt(tokenAccountInfo.amount.toNumber()),
slippage: 0.5, // It should be a small amount so slippage can be set wide
forceFetch: true,
});
if (routesInfos.length > 1) {
const bestRouteInfo = routesInfos[0]!;

if (JSBI.BigInt(bestRouteInfo.outAmount) < JSBI.BigInt((50_000))) {
if (JSBI.BigInt(bestRouteInfo.outAmount) < JSBI.BigInt(50_000)) {
// Less than 10 cents so not worth attempting to swap
console.log(
`Skipping swapping ${
JSBI.divide((bestRouteInfo.outAmount), JSBI.BigInt(Math.pow(10, 6)))
} worth of ${
`Skipping swapping ${JSBI.divide(
bestRouteInfo.outAmount,
JSBI.BigInt(Math.pow(10, 6))
)} worth of ${
tokenAccountInfo.mint
} in ${tokenAccountInfo.address.toBase58()}`
);
continue;
}

expectedTotalOutAmount = JSBI.add(expectedTotalOutAmount, bestRouteInfo.outAmount) ;
expectedTotalOutAmount = JSBI.add(
expectedTotalOutAmount,
bestRouteInfo.outAmount
);

console.log(
`Swap ${tokenAccountInfo.mint} for estimated ${
JSBI.divide((bestRouteInfo.outAmount), JSBI.BigInt(Math.pow(10, 6)))
} USDC`
`Swap ${tokenAccountInfo.mint} for estimated ${JSBI.divide(
bestRouteInfo.outAmount,
JSBI.BigInt(Math.pow(10, 6))
)} USDC`
);

if (dryRun) continue;
Expand Down
62 changes: 41 additions & 21 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,19 @@
dependencies:
"@hapi/hoek" "^9.0.0"

"@jup-ag/core@^1.0.0-beta.28":
version "1.0.0-beta.28"
resolved "https://registry.yarnpkg.com/@jup-ag/core/-/core-1.0.0-beta.28.tgz#0c15f940c6049f8b8570d6667d716f1bb1010434"
integrity sha512-96gb77z34jAn0WSQDqdrS8qQdCBRVFFv4UP5/N0d+oyPbK0jpIpAL7TIGN8+RCuutJB3nLLAmiNNVWYf+aJaFg==
"@jup-ag/core@^2.0.0-beta.3":
version "2.0.0-beta.3"
resolved "https://registry.yarnpkg.com/@jup-ag/core/-/core-2.0.0-beta.3.tgz#2e58795aab9bd66f0acb223e3faeb56fa80f29c3"
integrity sha512-/p1axCvvgBUSDa1mRUHP/ezKx0Kxa/LT0qw4OLEThYIUjqpnudrdeB5mZKOYMmG8TnWC/n+I0+mlNJ8H5fks7A==
dependencies:
"@jup-ag/crema-sdk" "2.0.7"
"@jup-ag/cykura-sdk" "0.1.25"
"@jup-ag/cykura-sdk-core" "0.1.8"
"@jup-ag/lifinity-sdk" "0.1.72"
"@jup-ag/math" "1.0.0-beta.28"
"@jup-ag/math" "2.0.0-beta.3"
"@jup-ag/whirlpool-sdk" "0.1.1"
"@mercurial-finance/optimist" "0.1.4"
"@project-serum/anchor" "0.23.0"
"@mercurial-finance/optimist" "0.1.5"
"@project-serum/anchor" "0.24.2"
"@project-serum/serum" "0.13.65"
"@saberhq/stableswap-sdk" "1.13.6"
"@solana/spl-token" "0.1.8"
Expand Down Expand Up @@ -123,10 +123,10 @@
"@solana/web3.js" "1.31.0"
decimal.js "^10.3.1"

"@jup-ag/math@1.0.0-beta.28":
version "1.0.0-beta.28"
resolved "https://registry.yarnpkg.com/@jup-ag/math/-/math-1.0.0-beta.28.tgz#52300d7470eee7c2cdac01b27b2f0c16062ad48a"
integrity sha512-GQkziJQ5kJwYgYNvK8ZFpW+d92R90ihwiQ2n1BunKakeTvPdwt7BQKAOj4KRgIb9/458zMCuYVZvdF1cAWFA/g==
"@jup-ag/math@2.0.0-beta.3":
version "2.0.0-beta.3"
resolved "https://registry.yarnpkg.com/@jup-ag/math/-/math-2.0.0-beta.3.tgz#b1686e1e0fd2a132f44b3d5921c078592d9f15f0"
integrity sha512-2JImGdwuC4hoK9EaIY+31Ynhc+olMWQlP9Djvh/OlGFBSnlQ6BQV9QxtvIlXmmlPrVqKgcCsRvjfOSVveoOXvA==
dependencies:
decimal.js "10.3.1"
jsbi "4.3.0"
Expand All @@ -144,10 +144,10 @@
decimal.js "~10.3.1"
tiny-invariant "~1.2.0"

"@mercurial-finance/optimist@0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@mercurial-finance/optimist/-/optimist-0.1.4.tgz#3ed5155fe94d5df0f86d2f47a3506460109eed4a"
integrity sha512-m8QuyPx9j7fGd2grw0mD5WcYtBb8l7+OQI5aHdeIlxPg3QoPrbSdCHyFOuipYbvB0EY5YDbOmyeFwiTcBkBBSw==
"@mercurial-finance/optimist@0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@mercurial-finance/optimist/-/optimist-0.1.5.tgz#04f43367befe8eca95ac9e286b32841883490db4"
integrity sha512-88FL1r/uakc1wCTL2vkSqFzsCOpAqvv6kmw++xiPNx59FFIR6NQrbfwTE0HNOszUXy6/C9nINywzUkFcyjn5hQ==

"@orca-so/whirlpool-client-sdk@npm:@jup-ag/whirlpool-client-sdk@0.0.8":
version "0.0.8"
Expand All @@ -160,10 +160,10 @@
decimal.js "~10.3.1"
lru-cache "^7.9.0"

"@project-serum/anchor@0.23.0", "@project-serum/anchor@~0.23.0":
version "0.23.0"
resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.23.0.tgz#2b2eb6b51601b073e8db26663aa2d6c2f2841771"
integrity sha512-LV2/ifZOJVFTZ4GbEloXln3iVfCvO1YM8i7BBCrUm4tehP7irMx4nr4/IabHWOzrQcQElsxSP/lb1tBp+2ff8A==
"@project-serum/anchor@0.24.2":
version "0.24.2"
resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.24.2.tgz#a3c52a99605c80735f446ca9b3a4885034731004"
integrity sha512-0/718g8/DnEuwAidUwh5wLYphUYXhUbiClkuRNhvNoa+1Y8a4g2tJyxoae+emV+PG/Gikd/QUBNMkIcimiIRTA==
dependencies:
"@project-serum/borsh" "^0.2.5"
"@solana/web3.js" "^1.36.0"
Expand All @@ -175,7 +175,6 @@
cross-fetch "^3.1.5"
crypto-hash "^1.3.0"
eventemitter3 "^4.0.7"
find "^0.3.0"
js-sha256 "^0.9.0"
pako "^2.0.3"
snake-case "^3.0.4"
Expand Down Expand Up @@ -242,6 +241,27 @@
snake-case "^3.0.4"
toml "^3.0.0"

"@project-serum/anchor@~0.23.0":
version "0.23.0"
resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.23.0.tgz#2b2eb6b51601b073e8db26663aa2d6c2f2841771"
integrity sha512-LV2/ifZOJVFTZ4GbEloXln3iVfCvO1YM8i7BBCrUm4tehP7irMx4nr4/IabHWOzrQcQElsxSP/lb1tBp+2ff8A==
dependencies:
"@project-serum/borsh" "^0.2.5"
"@solana/web3.js" "^1.36.0"
base64-js "^1.5.1"
bn.js "^5.1.2"
bs58 "^4.0.1"
buffer-layout "^1.2.2"
camelcase "^5.3.1"
cross-fetch "^3.1.5"
crypto-hash "^1.3.0"
eventemitter3 "^4.0.7"
find "^0.3.0"
js-sha256 "^0.9.0"
pako "^2.0.3"
snake-case "^3.0.4"
toml "^3.0.0"

"@project-serum/borsh@^0.2.2", "@project-serum/borsh@^0.2.5":
version "0.2.5"
resolved "https://registry.yarnpkg.com/@project-serum/borsh/-/borsh-0.2.5.tgz#6059287aa624ecebbfc0edd35e4c28ff987d8663"
Expand Down Expand Up @@ -1216,7 +1236,7 @@ js-sha3@^0.8.0:
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840"
integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==

jsbi@4.3.0, jsbi@^4.1.0, jsbi@^4.2.0:
jsbi@4.3.0, jsbi@^4.1.0, jsbi@^4.2.0, jsbi@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/jsbi/-/jsbi-4.3.0.tgz#b54ee074fb6fcbc00619559305c8f7e912b04741"
integrity sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==
Expand Down