├── data └── wallets.txt ├── modules ├── __init__.py └── solana.py ├── requirements.txt ├── README.md └── main.py /data/wallets.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /modules/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asyncio~=3.4.3 2 | base58~=2.1.1 3 | solana~=0.23.0 4 | aiofiles~=0.8.0 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

solana multitool

2 | 3 | `pip install -r requirements.txt` 4 | 5 | **functions:** 6 | 7 | check accounts sol balance 8 | 9 | check accounts token balance by token contract 10 | 11 | collect sol from all accounts 12 | 13 | collect tokens from all accounts 14 | 15 | format private key to public key 16 | 17 | generate new wallets 18 | 19 |

How to use:

20 | 21 | Insert account in `data/wallets.txt` 22 | 23 | Run `main.py` -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import aiofiles 3 | 4 | from base58 import b58encode, b58decode 5 | from solana.keypair import Keypair 6 | 7 | from modules.solana import Solana, MissingTokenProgram 8 | 9 | 10 | async def generate_wallets(queue: asyncio.Queue, fmt: int, save_file: str | None): 11 | new_wallet = Keypair() 12 | while True: 13 | if queue.empty(): 14 | return True 15 | await queue.get() 16 | 17 | if fmt == 1: 18 | if save_file: 19 | async with aiofiles.open("new_wallets.txt", mode="a+") as file: 20 | await file.write(f"{b58encode(new_wallet.secret_key).decode('utf-8')}:{new_wallet.public_key}\n") 21 | else: 22 | print(f"{b58encode(new_wallet.secret_key).decode('utf-8')}:{new_wallet.public_key}") 23 | elif fmt == 2: 24 | if save_file: 25 | async with aiofiles.open("new_wallets.txt", mode="a+") as file: 26 | await file.write(f"{b58encode(new_wallet.secret_key).decode('utf-8')}\n") 27 | else: 28 | print(b58encode(new_wallet.secret_key).decode('utf-8')) 29 | else: 30 | raise ValueError("invalid input") 31 | 32 | 33 | async def private_to_address(queue: asyncio.Queue): 34 | while True: 35 | if queue.empty(): 36 | return True 37 | wallet = await queue.get() 38 | 39 | print(Keypair.from_secret_key(b58decode(wallet)).public_key) 40 | 41 | 42 | async def collect_token(queue: asyncio.Queue, token_contract: str, main_address: str, amount: str): 43 | while True: 44 | try: 45 | if queue.empty(): 46 | return True 47 | if amount: 48 | amount = float(amount) 49 | 50 | wallet = await queue.get() 51 | address = Keypair.from_secret_key(b58decode(wallet)).public_key 52 | 53 | async with Solana(rpc=rpc, skip_confirmation=skip_confirmations) as client: 54 | tx = await client.send_token(key=wallet, token_contract=token_contract, to=main_address, amount=amount) 55 | if address_with_tx: 56 | print(f"{address}: {tx}") 57 | else: 58 | print(tx) 59 | 60 | except Exception as e: 61 | if e: 62 | print(e) 63 | 64 | 65 | async def collect_sol(queue: asyncio.Queue, main_address: str, amount: str): 66 | while True: 67 | try: 68 | if queue.empty(): 69 | return True 70 | 71 | if amount: 72 | amount = float(amount) 73 | wallet = await queue.get() 74 | address = Keypair.from_secret_key(b58decode(wallet)).public_key 75 | 76 | async with Solana(rpc=rpc, skip_confirmation=skip_confirmations) as client: 77 | tx = await client.send_solana(key=wallet, to=main_address, amount=amount) 78 | if address_with_tx: 79 | print(f"{address}: {tx}") 80 | else: 81 | print(tx) 82 | except Exception as e: 83 | print(e) 84 | 85 | 86 | async def check_sol(queue: asyncio.Queue): 87 | total_balance = 0 88 | while True: 89 | try: 90 | data = await queue.get() 91 | wallet = Keypair.from_secret_key(b58decode(data)).public_key 92 | 93 | async with Solana(rpc=rpc) as client: 94 | balance = await client.get_balance(address=wallet, return_sol=True) 95 | print(f"{wallet}: {balance} SOL") 96 | if balance: 97 | total_balance += balance 98 | 99 | if queue.empty(): 100 | return total_balance 101 | except Exception as e: 102 | print(e) 103 | 104 | 105 | async def check_token(queue: asyncio.Queue, token_contract: str): 106 | total_balance = 0 107 | while True: 108 | try: 109 | data = await queue.get() 110 | wallet = Keypair.from_secret_key(b58decode(data)).public_key 111 | 112 | async with Solana(rpc=rpc) as client: 113 | balance = await client.get_token_balance(address=wallet, token_contract=token_contract, return_sol=True) 114 | print(f"{wallet}: {balance}") 115 | if balance: 116 | total_balance += balance 117 | 118 | if queue.empty(): 119 | return total_balance 120 | except MissingTokenProgram as e: 121 | print(e) 122 | 123 | 124 | async def create_task(): 125 | queue = asyncio.Queue() 126 | show_total = False 127 | tasks = [] 128 | 129 | for wallet in wallets: 130 | queue.put_nowait(wallet) 131 | 132 | match choise: 133 | case 0: 134 | exit(69) 135 | case 1: 136 | tasks = [asyncio.create_task(check_sol(queue)) for _ in range(threads)] 137 | show_total = True 138 | case 2: 139 | token_contract = input("enter token contract >>> ") 140 | tasks = [asyncio.create_task(check_token(queue, token_contract)) for _ in range(threads)] 141 | show_total = True 142 | case 3: 143 | main_address = input("enter main wallet >>> ") 144 | amount = input("enter amount to send (skip for all balance) >>> ") 145 | tasks = [asyncio.create_task(collect_sol(queue, main_address, amount)) for _ in range(threads)] 146 | case 4: 147 | main_address = input("enter main wallet >>> ") 148 | token_contract = input("enter token contract >>> ") 149 | amount = input("enter amount to send (skip for all balance) >>> ") 150 | tasks = [asyncio.create_task(collect_token( 151 | queue, token_contract, main_address, amount 152 | )) for _ in range(threads)] 153 | case 5: 154 | tasks = [asyncio.create_task(private_to_address(queue)) for _ in range(threads)] 155 | case 6: 156 | temp_quque = asyncio.Queue() 157 | 158 | for i in range(int(input("how many wallets >>> "))): 159 | temp_quque.put_nowait(i) 160 | 161 | fmt = int(input( 162 | "choose format: \n" 163 | "1 - key:address\n" 164 | "2 - key\n" 165 | ">>> " 166 | )) 167 | save_file = input("save data in file (y/skip) >>> ") 168 | tasks = [asyncio.create_task(generate_wallets(temp_quque, fmt, save_file)) for _ in range(10)] 169 | case _: 170 | raise Exception("invalid input") 171 | 172 | result = await asyncio.gather(*tasks) 173 | if show_total: 174 | print(f"total balance on accounts: {sum(filter(None, result))}") 175 | 176 | 177 | if __name__ == '__main__': 178 | skip_confirmations: bool = False 179 | rpc: str = "https://solana-mainnet.phantom.tech" 180 | address_with_tx: bool = False 181 | 182 | if value := input("enter your rpc (skip for using default rpc) >>> "): 183 | rpc = value 184 | 185 | wallets = list(filter(bool, open("data/wallets.txt").read().strip().split("\n"))) 186 | print(f"\nloaded {len(wallets)} wallets!\n") 187 | 188 | while True: 189 | choise = int(input( 190 | "0 - exit\n" 191 | "1 - check account balances (sol)\n" 192 | "2 - check account balances (token)\n" 193 | "3 - collect sol from accounts\n" 194 | "4 - collect tokens from accounts\n" 195 | "5 - privat key to address\n" 196 | "6 - generate wallets\n" 197 | ">>> " 198 | )) 199 | threads = int(input("threads >>> ")) if len(wallets) > 25 else len(wallets) 200 | asyncio.run(create_task()) 201 | -------------------------------------------------------------------------------- /modules/solana.py: -------------------------------------------------------------------------------- 1 | from base58 import b58decode 2 | from solana.keypair import Keypair 3 | from solana.publickey import PublicKey 4 | from solana.rpc.async_api import AsyncClient, Commitment 5 | from solana.system_program import transfer, TransferParams 6 | from solana.rpc.types import TokenAccountOpts, TxOpts 7 | from solana.transaction import Transaction 8 | 9 | from spl.token.instructions import transfer_checked, TransferCheckedParams 10 | from spl.token.constants import TOKEN_PROGRAM_ID 11 | 12 | 13 | class RPCException(Exception): 14 | def __init__(self, rpc: str): 15 | super().__init__(f"could not connect to rpc: {rpc}") 16 | 17 | 18 | class SolanaLowBalance(Exception): 19 | def __init__(self, address: str | PublicKey): 20 | super().__init__(f"{address} have insuficient balance") 21 | 22 | 23 | class TokenLowBalance(Exception): 24 | def __init__(self, address: str | PublicKey): 25 | super().__init__(f"{address} have insuficient token balance") 26 | 27 | 28 | class MissingTokenProgram(Exception): 29 | def __init__(self, address: str | PublicKey): 30 | super().__init__(f"{address} have no token program") 31 | 32 | 33 | class Solana: 34 | def __init__(self, rpc: str, skip_confirmation: bool = None): 35 | self.rpc = rpc 36 | self.skip_confirmation = skip_confirmation 37 | 38 | async def __aenter__(self): 39 | self.client = AsyncClient(self.rpc) 40 | if await self.client.is_connected(): 41 | return self 42 | raise RPCException(self.rpc) 43 | 44 | async def __aexit__(self, exc_type, exc_val, exc_tb): 45 | await self.client.close() 46 | 47 | async def get_balance( 48 | self, 49 | address: str | PublicKey, 50 | return_sol: bool = False, 51 | commitment: str | Commitment = None 52 | ) -> int: 53 | if isinstance(address, str): 54 | address = PublicKey(address) 55 | if isinstance(commitment, str): 56 | commitment = Commitment(commitment) 57 | 58 | balance = (await self.client.get_balance(address, commitment=commitment))['result']['value'] 59 | 60 | if return_sol: 61 | return balance / 10 ** 9 62 | return balance 63 | 64 | async def get_token_balance( 65 | self, 66 | address: str | PublicKey, 67 | token_contract: str | PublicKey, 68 | return_sol: bool = False, 69 | commitment: str | Commitment = None 70 | ) -> int: 71 | if isinstance(address, str): 72 | address = PublicKey(address) 73 | if isinstance(token_contract, str): 74 | token_contract = PublicKey(token_contract) 75 | if isinstance(commitment, str): 76 | commitment = Commitment(commitment) 77 | 78 | if token_program := await self.get_token_accounts_by_owner(address=address, mint=token_contract): 79 | balance = int((await self.client.get_token_account_balance( 80 | pubkey=token_program[0], 81 | commitment=commitment, 82 | ))['result']['value']['amount']) 83 | 84 | if return_sol: 85 | return balance / 10 ** 9 86 | return balance 87 | raise MissingTokenProgram(address) 88 | 89 | async def get_recent_blockhash(self, commitent: str | Commitment = "confirmed") -> dict: 90 | if isinstance(commitent, str): 91 | commitent = Commitment(commitent) 92 | return await self.client.get_recent_blockhash(commitment=commitent) 93 | 94 | async def send_solana( 95 | self, 96 | key: str | Keypair, 97 | to: str | PublicKey, 98 | amount: float = None, 99 | commitent: str | Commitment = None 100 | ) -> str: 101 | if isinstance(key, str): 102 | key = Keypair.from_secret_key(b58decode(key)) 103 | if isinstance(to, str): 104 | to = PublicKey(to) 105 | 106 | balance = await self.get_balance(key.public_key) 107 | recent_blockhash = (await self.get_recent_blockhash(commitent))["result"]["value"] 108 | lamports = recent_blockhash['feeCalculator']['lamportsPerSignature'] 109 | if amount: 110 | to_send = float(amount) * 10 ** 9 111 | if float(amount) + lamports > balance: 112 | raise SolanaLowBalance(key.public_key) 113 | else: 114 | to_send = balance - lamports 115 | 116 | if to_send < lamports: 117 | raise SolanaLowBalance(key.public_key) 118 | 119 | tx = Transaction( 120 | fee_payer=key.public_key, 121 | recent_blockhash=recent_blockhash["blockhash"] 122 | ) 123 | tx.add( 124 | transfer( 125 | TransferParams( 126 | from_pubkey=key.public_key, 127 | to_pubkey=to, 128 | lamports=int(to_send) 129 | ) 130 | ) 131 | ) 132 | tx.sign(key) 133 | 134 | return (await self.client.send_transaction( 135 | tx, 136 | key, 137 | opts=TxOpts(skip_confirmation=self.skip_confirmation) 138 | ))['result'] 139 | 140 | async def get_transaction(self, tx_hash: str) -> str: 141 | return (await self.client.get_transaction(tx_hash))['result'] 142 | 143 | async def get_token_accounts_by_owner( 144 | self, 145 | address: str | PublicKey, 146 | mint: str | PublicKey, 147 | commitent: str | Commitment = None 148 | ) -> list[PublicKey]: 149 | if isinstance(address, str): 150 | address = PublicKey(address) 151 | if isinstance(mint, str): 152 | mint = PublicKey(mint) 153 | if isinstance(commitent, str): 154 | commitent = Commitment(commitent) 155 | value = (await self.client.get_token_accounts_by_owner( 156 | owner=address, 157 | opts=TokenAccountOpts(mint=mint), 158 | commitment=commitent 159 | )) 160 | 161 | value = value['result']['value'] 162 | return [PublicKey(token['pubkey']) for token in value] 163 | 164 | async def send_token( 165 | self, 166 | key: str | Keypair, 167 | to: str | PublicKey, 168 | token_contract: str | PublicKey, 169 | amount: float = None, 170 | commitent: str | Commitment = None 171 | ) -> str: 172 | if isinstance(key, str): 173 | key = Keypair.from_secret_key(b58decode(key)) 174 | if isinstance(to, str): 175 | to = PublicKey(to) 176 | if isinstance(token_contract, str): 177 | token_contract = PublicKey(token_contract) 178 | 179 | balance = await self.get_balance(key.public_key) 180 | token_balance = await self.get_token_balance(key.public_key, token_contract) 181 | recent_blockhash = (await self.get_recent_blockhash(commitent))["result"]["value"] 182 | lamports = recent_blockhash['feeCalculator']['lamportsPerSignature'] 183 | if token_balance == 0: 184 | raise TokenLowBalance(key.public_key) 185 | if amount: 186 | amount = float(amount) * 10 ** 9 187 | else: 188 | amount = token_balance 189 | 190 | if balance < lamports: 191 | raise SolanaLowBalance(key.public_key) 192 | 193 | if not (dest := await self.get_token_accounts_by_owner(address=to, mint=token_contract)): 194 | raise MissingTokenProgram(to) 195 | 196 | tx = Transaction( 197 | fee_payer=key.public_key, 198 | recent_blockhash=recent_blockhash 199 | ) 200 | 201 | tx.add( 202 | transfer_checked( 203 | TransferCheckedParams( 204 | program_id=TOKEN_PROGRAM_ID, 205 | source=(await self.get_token_accounts_by_owner(address=key.public_key, mint=token_contract))[0], 206 | mint=token_contract, 207 | dest=dest[0], 208 | owner=key.public_key, 209 | amount=int(amount), 210 | decimals=9 211 | ) 212 | ) 213 | ) 214 | return (await self.client.send_transaction( 215 | tx, 216 | key, 217 | opts=TxOpts(skip_confirmation=self.skip_confirmation) 218 | ))['result'] 219 | --------------------------------------------------------------------------------