diff --git a/src/index.ts b/src/index.ts index a138ba1..c4a1627 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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"; @@ -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") diff --git a/src/janitor.ts b/src/janitor.ts index 73b78ef..305f601 100644 --- a/src/janitor.ts +++ b/src/janitor.ts @@ -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; @@ -31,9 +31,11 @@ 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 ( @@ -41,10 +43,7 @@ export async function createTokenAccounts( ).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()) ); @@ -66,7 +65,7 @@ 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); @@ -74,7 +73,8 @@ export async function createTokenAccounts( ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, mintPk, - userKeypair.publicKey + owner, + allowOwnerOffCurve ); tx.add( ...[ @@ -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); } } @@ -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; diff --git a/yarn.lock b/yarn.lock index b118122..de052c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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" @@ -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" @@ -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" @@ -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" @@ -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" @@ -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" @@ -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==