├── .gitignore ├── README.md ├── package.json ├── src ├── arbitrage-bot.ts ├── example-1.ts └── liquidator-bot.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/osx,node,linux,windows 3 | 4 | ### Linux ### 5 | *~ 6 | 7 | # npm tree 8 | package-lock.json 9 | yarn.lock 10 | 11 | # compiled files 12 | built/ 13 | 14 | # moved files 15 | src/id.json 16 | src/package.json 17 | 18 | # temporary files which can be created if a process still has a handle open of a deleted file 19 | .fuse_hidden* 20 | 21 | # KDE directory preferences 22 | .directory 23 | 24 | # Linux trash folder which might appear on any partition or disk 25 | .Trash-* 26 | 27 | # .nfs files are created when an open file is removed but is still being accessed 28 | .nfs* 29 | 30 | ### Node ### 31 | # Logs 32 | logs 33 | *.log 34 | npm-debug.log* 35 | yarn-debug.log* 36 | yarn-error.log* 37 | 38 | # Runtime data 39 | pids 40 | *.pid 41 | *.seed 42 | *.pid.lock 43 | 44 | # Directory for instrumented libs generated by jscoverage/JSCover 45 | lib-cov 46 | 47 | # Coverage directory used by tools like istanbul 48 | coverage 49 | 50 | # nyc test coverage 51 | .nyc_output 52 | 53 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 54 | .grunt 55 | 56 | # Bower dependency directory (https://bower.io/) 57 | bower_components 58 | 59 | # node-waf configuration 60 | .lock-wscript 61 | 62 | # Compiled binary addons (http://nodejs.org/api/addons.html) 63 | build/Release 64 | 65 | # Dependency directories 66 | node_modules/ 67 | jspm_packages/ 68 | 69 | # Typescript v1 declaration files 70 | typings/ 71 | 72 | # Optional npm cache directory 73 | .npm 74 | 75 | # Optional eslint cache 76 | .eslintcache 77 | 78 | # Optional REPL history 79 | .node_repl_history 80 | 81 | # Output of 'npm pack' 82 | *.tgz 83 | 84 | # Yarn Integrity file 85 | .yarn-integrity 86 | 87 | ### OSX ### 88 | *.DS_Store 89 | .AppleDouble 90 | .LSOverride 91 | 92 | # Icon must end with two \r 93 | Icon 94 | 95 | # Thumbnails 96 | ._* 97 | 98 | # Files that might appear in the root of a volume 99 | .DocumentRevisions-V100 100 | .fseventsd 101 | .Spotlight-V100 102 | .TemporaryItems 103 | .Trashes 104 | .VolumeIcon.icns 105 | .com.apple.timemachine.donotpresent 106 | 107 | # Directories potentially created on remote AFP share 108 | .AppleDB 109 | .AppleDesktop 110 | Network Trash Folder 111 | Temporary Items 112 | .apdisk 113 | 114 | ### Windows ### 115 | # Windows thumbnail cache files 116 | Thumbs.db 117 | ehthumbs.db 118 | ehthumbs_vista.db 119 | 120 | # Folder config file 121 | Desktop.ini 122 | 123 | # Recycle Bin used on file shares 124 | $RECYCLE.BIN/ 125 | 126 | # Windows Installer files 127 | *.cab 128 | *.msi 129 | *.msm 130 | *.msp 131 | 132 | # Windows shortcuts 133 | *.lnk 134 | 135 | 136 | # End of https://www.gitignore.io/api/osx,node,linux,windows 137 | 138 | src/**.js 139 | src/**.js.map 140 | .idea 141 | 142 | .env 143 | 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # example-bots 2 | 3 | *this is experimental software* 4 | 5 | for getting started with solana-cli: https://docs.solana.com/cli 6 | 7 | 8 | Quick Start 9 | ---- 10 | ``` 11 | yarn 12 | echo BOT_PRIVATE_KEY=`cat ~/.config/solana/id.json` >> .env 13 | ts-node src/example-1.ts 14 | ``` 15 | 16 | Disclaimer 17 | ---- 18 | This software is for educational purposes only on a developer testnet. Do not risk money which you are afraid to lose. USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS. 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@drift-labs/sdk": "^0.1.28" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/arbitrage-bot.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from '@project-serum/anchor'; 2 | import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token'; 3 | import { Connection, Keypair, PublicKey } from '@solana/web3.js'; 4 | import { 5 | BN, 6 | calculateMarkPrice, 7 | ClearingHouse, 8 | ClearingHouseUser, 9 | initialize, 10 | Markets, 11 | PositionDirection, 12 | convertToNumber, 13 | calculateTradeSlippage, 14 | calculateTargetPriceTrade, 15 | QUOTE_PRECISION, 16 | MARK_PRICE_PRECISION, 17 | DriftEnv, 18 | Wallet 19 | } from '@drift-labs/sdk'; 20 | 21 | require('dotenv').config(); 22 | 23 | export const getTokenAddress = ( 24 | mintAddress: string, 25 | userPubKey: string 26 | ): Promise => { 27 | return Token.getAssociatedTokenAddress( 28 | new PublicKey(`ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL`), 29 | TOKEN_PROGRAM_ID, 30 | new PublicKey(mintAddress), 31 | new PublicKey(userPubKey) 32 | ); 33 | }; 34 | 35 | 36 | async function arbTrade(clearingHouse, marketIndex) { 37 | const market = clearingHouse.getMarket(marketIndex); 38 | 39 | // arbitrager price gap 40 | const targetPrice = new BN(250.169 * MARK_PRICE_PRECISION.toNumber()); 41 | const [direction, amount, entryPrice, newMarkPrice] = 42 | calculateTargetPriceTrade( 43 | market, 44 | targetPrice, 45 | ); 46 | 47 | // for example: cap trades at $1 48 | const amountCapped = BN.min(amount, QUOTE_PRECISION); 49 | 50 | await clearingHouse.openPosition( 51 | direction, 52 | amountCapped, 53 | marketIndex, 54 | entryPrice 55 | ); 56 | } 57 | 58 | const main = async () => { 59 | // Initialize Drift SDK 60 | const sdkConfig = initialize({ env: 'devnet' as DriftEnv }); 61 | 62 | // Set up the Wallet and Provider 63 | const privateKey = process.env.BOT_PRIVATE_KEY; // stored as an array string 64 | const keypair = Keypair.fromSecretKey( 65 | Uint8Array.from(JSON.parse(privateKey)) 66 | ); 67 | const wallet = new Wallet(keypair); 68 | 69 | // Set up the Connection 70 | const rpcAddress = 'https://api.devnet.solana.com'// for devnet; https://api.mainnet-beta.solana.com for mainnet; 71 | const connection = new Connection(rpcAddress); 72 | 73 | // Set up the Provider 74 | const provider = new Provider(connection, wallet, Provider.defaultOptions()); 75 | 76 | // Check SOL Balance 77 | const lamportsBalance = await connection.getBalance(wallet.publicKey); 78 | console.log('SOL balance:', lamportsBalance / 10 ** 9); 79 | 80 | // Misc. other things to set up 81 | const usdcTokenAddress = await getTokenAddress( 82 | sdkConfig.USDC_MINT_ADDRESS, 83 | wallet.publicKey.toString() 84 | ); 85 | 86 | // Set up the Drift Clearing House 87 | const clearingHousePublicKey = new PublicKey( 88 | sdkConfig.CLEARING_HOUSE_PROGRAM_ID 89 | ); 90 | const clearingHouse = ClearingHouse.from( 91 | connection, 92 | provider.wallet, 93 | clearingHousePublicKey 94 | ); 95 | await clearingHouse.subscribe(); 96 | 97 | // Get current price 98 | const solMarketInfo = Markets.find( 99 | (market) => market.baseAssetSymbol === 'SOL' 100 | ); 101 | 102 | const solMarket = clearingHouse.getMarket(solMarketInfo.marketIndex); 103 | 104 | const currentMarketPrice = calculateMarkPrice(solMarket); 105 | 106 | const formattedPrice = convertToNumber(currentMarketPrice, MARK_PRICE_PRECISION); 107 | 108 | console.log(`Current Market Price is $${formattedPrice}`); 109 | 110 | // Estimate the slippage for a $5000 LONG trade 111 | const solMarketAccount = clearingHouse.getMarket(solMarketInfo.marketIndex); 112 | 113 | const slippage = convertToNumber( 114 | calculateTradeSlippage( 115 | PositionDirection.LONG, 116 | new BN(5000).mul(QUOTE_PRECISION), 117 | solMarketAccount 118 | )[0], 119 | MARK_PRICE_PRECISION 120 | ); 121 | 122 | console.log( 123 | `Slippage for a $5000 LONG on the SOL market would be $${slippage}` 124 | ); 125 | 126 | 127 | // Set up Clearing House user client 128 | const user = ClearingHouseUser.from(clearingHouse, wallet.publicKey); 129 | 130 | //// Check if clearing house account exists for the current wallet 131 | const userAccountExists = await user.exists(); 132 | 133 | if (!userAccountExists) { 134 | //// Create a Clearing House account by Depositing some USDC ($10,000 in this case) 135 | const depositAmount = new BN(10000).mul(QUOTE_PRECISION); 136 | try{ 137 | const tx = await clearingHouse.initializeUserAccountAndDepositCollateral( 138 | depositAmount, 139 | usdcTokenAddress 140 | ); 141 | }catch(e){ 142 | throw Error('ERROR: Unable to initializeUserAccountAndDepositCollateral'); 143 | } 144 | } 145 | 146 | await user.subscribe(); 147 | 148 | while(true){ 149 | function sleep(ms) { 150 | return new Promise((resolve) => setTimeout(resolve, ms)); 151 | } 152 | 153 | await arbTrade(clearingHouse, solMarketInfo.marketIndex); 154 | console.log('sleeping for 10 seconds...'); 155 | sleep(10000); // wait seconds 156 | } 157 | }; 158 | 159 | main(); 160 | -------------------------------------------------------------------------------- /src/example-1.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from '@project-serum/anchor'; 2 | import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token'; 3 | import { Connection, Keypair, PublicKey } from '@solana/web3.js'; 4 | import { 5 | BN, 6 | calculateMarkPrice, 7 | ClearingHouse, 8 | ClearingHouseUser, 9 | initialize, 10 | Markets, 11 | PositionDirection, 12 | convertToNumber, 13 | calculateTradeSlippage, 14 | MARK_PRICE_PRECISION, 15 | QUOTE_PRECISION, 16 | DriftEnv, 17 | Wallet 18 | } from '@drift-labs/sdk'; 19 | 20 | require('dotenv').config(); 21 | 22 | export const getTokenAddress = ( 23 | mintAddress: string, 24 | userPubKey: string 25 | ): Promise => { 26 | return Token.getAssociatedTokenAddress( 27 | new PublicKey(`ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL`), 28 | TOKEN_PROGRAM_ID, 29 | new PublicKey(mintAddress), 30 | new PublicKey(userPubKey) 31 | ); 32 | }; 33 | 34 | const main = async () => { 35 | // Initialize Drift SDK 36 | const sdkConfig = initialize({ env: 'devnet' as DriftEnv }); 37 | 38 | // Set up the Wallet and Provider 39 | const privateKey = process.env.BOT_PRIVATE_KEY; // stored as an array string 40 | const keypair = Keypair.fromSecretKey( 41 | Uint8Array.from(JSON.parse(privateKey)) 42 | ); 43 | const wallet = new Wallet(keypair); 44 | 45 | // Set up the Connection 46 | const rpcAddress = 'https://api.devnet.solana.com'// for devnet; https://api.mainnet-beta.solana.com for mainnet; 47 | const connection = new Connection(rpcAddress); 48 | 49 | // Set up the Provider 50 | const provider = new Provider(connection, wallet, Provider.defaultOptions()); 51 | 52 | // Check SOL Balance 53 | const lamportsBalance = await connection.getBalance(wallet.publicKey); 54 | console.log('SOL balance:', lamportsBalance / 10 ** 9); 55 | 56 | // Misc. other things to set up 57 | const usdcTokenAddress = await getTokenAddress( 58 | sdkConfig.USDC_MINT_ADDRESS, 59 | wallet.publicKey.toString() 60 | ); 61 | 62 | // Set up the Drift Clearing House 63 | const clearingHousePublicKey = new PublicKey( 64 | sdkConfig.CLEARING_HOUSE_PROGRAM_ID 65 | ); 66 | const clearingHouse = ClearingHouse.from( 67 | connection, 68 | provider.wallet, 69 | clearingHousePublicKey 70 | ); 71 | await clearingHouse.subscribe(); 72 | 73 | 74 | 75 | // Get current price 76 | const solMarketInfo = Markets.find( 77 | (market) => market.baseAssetSymbol === 'SOL' 78 | ); 79 | 80 | const currentMarketPrice = calculateMarkPrice( 81 | clearingHouse.getMarket(solMarketInfo.marketIndex) 82 | ); 83 | 84 | const formattedPrice = convertToNumber(currentMarketPrice, MARK_PRICE_PRECISION); 85 | 86 | console.log(`Current Market Price is $${formattedPrice}`); 87 | 88 | // Estimate the slippage for a $5000 LONG trade 89 | const solMarketAccount = clearingHouse.getMarket(solMarketInfo.marketIndex); 90 | 91 | const slippage = convertToNumber( 92 | calculateTradeSlippage( 93 | PositionDirection.LONG, 94 | new BN(5000).mul(QUOTE_PRECISION), 95 | solMarketAccount 96 | )[0], 97 | MARK_PRICE_PRECISION 98 | ); 99 | 100 | console.log( 101 | `Slippage for a $5000 LONG on the SOL market would be $${slippage}` 102 | ); 103 | 104 | 105 | // Set up Clearing House user client 106 | const user = ClearingHouseUser.from(clearingHouse, wallet.publicKey); 107 | 108 | //// Check if clearing house account exists for the current wallet 109 | const userAccountExists = await user.exists(); 110 | 111 | if (!userAccountExists) { 112 | //// Create a Clearing House account by Depositing some USDC ($10,000 in this case) 113 | const depositAmount = new BN(10000).mul(QUOTE_PRECISION); 114 | try{ 115 | const tx = await clearingHouse.initializeUserAccountAndDepositCollateral( 116 | depositAmount, 117 | usdcTokenAddress 118 | ); 119 | }catch(e){ 120 | throw Error('ERROR: Unable to initializeUserAccountAndDepositCollateral'); 121 | } 122 | } 123 | 124 | await user.subscribe(); 125 | 126 | // Make a $5000 LONG trade 127 | await clearingHouse.openPosition( 128 | PositionDirection.LONG, 129 | new BN(5000).mul(QUOTE_PRECISION), 130 | solMarketInfo.marketIndex 131 | ); 132 | console.log(`LONGED $5000 worth of SOL`); 133 | 134 | // Reduce the position by $2000 135 | await clearingHouse.openPosition( 136 | PositionDirection.SHORT, 137 | new BN(2000).mul(QUOTE_PRECISION), 138 | solMarketInfo.marketIndex 139 | ); 140 | 141 | // Close the rest of the position 142 | await clearingHouse.closePosition(solMarketInfo.marketIndex); 143 | }; 144 | 145 | main(); 146 | -------------------------------------------------------------------------------- /src/liquidator-bot.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from '@project-serum/anchor'; 2 | import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token'; 3 | import { Connection, Keypair, PublicKey } from '@solana/web3.js'; 4 | import { 5 | ClearingHouse, 6 | ClearingHouseUser, 7 | initialize, 8 | DriftEnv, 9 | Wallet 10 | } from '@drift-labs/sdk'; 11 | 12 | require('dotenv').config(); 13 | 14 | 15 | export const getTokenAddress = ( 16 | mintAddress: string, 17 | userPubKey: string 18 | ): Promise => { 19 | return Token.getAssociatedTokenAddress( 20 | new PublicKey(`ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL`), 21 | TOKEN_PROGRAM_ID, 22 | new PublicKey(mintAddress), 23 | new PublicKey(userPubKey) 24 | ); 25 | }; 26 | const main = async () => { 27 | // Initialize Drift SDK 28 | const sdkConfig = initialize({ env: 'devnet' as DriftEnv }); 29 | 30 | // Set up the Wallet and Provider 31 | const privateKey = process.env.BOT_PRIVATE_KEY; // stored as an array string 32 | const keypair = Keypair.fromSecretKey( 33 | Uint8Array.from(JSON.parse(privateKey)) 34 | ); 35 | const wallet = new Wallet(keypair); 36 | 37 | // Set up the Connection 38 | const rpcAddress = 'https://api.devnet.solana.com'// for devnet; https://api.mainnet-beta.solana.com for mainnet; 39 | const connection = new Connection(rpcAddress); 40 | 41 | // Set up the Provider 42 | const provider = new Provider(connection, wallet, Provider.defaultOptions()); 43 | 44 | // Check SOL Balance 45 | const lamportsBalance = await connection.getBalance(wallet.publicKey); 46 | console.log('SOL balance:', lamportsBalance / 10 ** 9); 47 | 48 | // Misc. other things to set up 49 | const usdcTokenAddress = await getTokenAddress( 50 | sdkConfig.USDC_MINT_ADDRESS, 51 | wallet.publicKey.toString() 52 | ); 53 | 54 | // Set up the Drift Clearing House 55 | const clearingHousePublicKey = new PublicKey( 56 | sdkConfig.CLEARING_HOUSE_PROGRAM_ID 57 | ); 58 | const clearingHouse = ClearingHouse.from( 59 | connection, 60 | provider.wallet, 61 | clearingHousePublicKey 62 | ); 63 | await clearingHouse.subscribe(); 64 | 65 | 66 | // 1. load users 67 | 68 | const users: ClearingHouseUser[] = []; 69 | 70 | const usersSeen = new Set(); 71 | const updateUserAccounts = async () => { 72 | console.log('Checking if there are new user accounts'); 73 | const programUserAccounts = await clearingHouse.program.account.user.all(); 74 | let numNewUserAccounts = 0; 75 | for (const programUserAccount of programUserAccounts) { 76 | const userAccountPubkey = programUserAccount.publicKey.toString(); 77 | if (usersSeen.has(userAccountPubkey)) { 78 | continue; 79 | } 80 | const user = ClearingHouseUser.from( 81 | clearingHouse, 82 | programUserAccount.account.authority 83 | ); 84 | await user.subscribe(); 85 | users.push(user); 86 | usersSeen.add(userAccountPubkey); 87 | numNewUserAccounts += 1; 88 | } 89 | console.log( 90 | 'num user accounts:', 91 | users.length, 92 | '(', 93 | numNewUserAccounts, 94 | ' new )' 95 | ); 96 | }; 97 | 98 | await updateUserAccounts(); 99 | 100 | // 2. check for liquidatable users 101 | for (const user of users) { 102 | const [canLiquidate] = user.canBeLiquidated(); 103 | if (canLiquidate) { 104 | const liquidateeUserAccountPublicKey = 105 | await user.getUserAccountPublicKey(); 106 | 107 | try { 108 | clearingHouse 109 | .liquidate(liquidateeUserAccountPublicKey) 110 | .then((tx) => { 111 | console.log(`Liquidated user: ${user.authority} Tx: ${tx}`); 112 | }); 113 | } catch (e) { 114 | console.log(e); 115 | } 116 | } 117 | } 118 | 119 | } 120 | 121 | main() 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "esModuleInterop": true, 6 | "declaration": true, 7 | "outDir": "./lib", 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true 10 | }, 11 | "include": ["src"], 12 | "exclude": ["node_modules"] 13 | } --------------------------------------------------------------------------------