├── 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 |
--------------------------------------------------------------------------------