Solana RPC Timeout Fix
Creado el: 30 de noviembre de 2024
Respondido usando O1 Preview por Chat01
Creado el: 30 de noviembre de 2024
Respondido usando O1 Preview por Chat01
Traceback (most recent call last):
File "/usr/local/lib/python3.12/site-packages/solana/exceptions.py", line 60, in argument_decorator
return await func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/solana/rpc/providers/async_http.py", line 59, in make_request
raw = await self.make_request_unparsed(body)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/solana/rpc/providers/async_http.py", line 65, in make_request_unparsed
raw_response = await self.session.post(**request_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/httpx/_client.py", line 1861, in post
return await self.request(
^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/httpx/_client.py", line 1542, in request
return await self.send(request, auth=auth, follow_redirects=follow_redirects)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/httpx/_client.py", line 1631, in send
response = await self._send_handling_auth(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/httpx/_client.py", line 1659, in _send_handling_auth
response = await self._send_handling_redirects(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/httpx/_client.py", line 1696, in _send_handling_redirects
response = await self._send_single_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/httpx/_client.py", line 1732, in _send_single_request
response = await transport.handle_async_request(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/httpx/_transports/default.py", line 393, in handle_async_request
with map_httpcore_exceptions():
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/contextlib.py", line 158, in exit
self.gen.throw(value)
File "/usr/local/lib/python3.12/site-packages/httpx/_transports/default.py", line 118, in map_httpcore_exceptions
raise mapped_exc(message) from exc
httpx.ReadTimeout
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/app/main.py", line 25, in send_notifications
new_links = set((await get_latest_sol_transactions(wallet.wallet_hash)))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/parsers/crypto/solana_check.py", line 56, in get_latest_sol_transactions
res = await client.get_signatures_for_address(Pubkey.from_string(address))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/solana/rpc/async_api.py", line 360, in get_signatures_for_address
return await self._provider.make_request(body, GetSignaturesForAddressResp)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/solana/exceptions.py", line 62, in argument_decorator
raise internal_exception_cls(exc, func, *args, **kwargs) from exc
solana.exceptions.SolanaRpcException
такая ошибка возникает в моем коде при вызове насколько я понимаю вот этого кода:
async def get_latest_sol_transactions(address: str) -> list[str]:
"""
Получить последние n транзакций для указанного адреса в сети Solana.
text:param address: Строка с адресом кошелька Solana. :return: Список подписей последних транзакций. """ count = int(os.getenv('SOLANA_WALLETS_NUM')) async with AsyncClient('https://api.mainnet-beta.solana.com') as client: # Запрос подписей транзакций для указанного адреса res = await client.get_signatures_for_address(Pubkey.from_string(address)) if res.value is not None: # Возвращаем только первые `count` транзакций return [str(tx.signature) for tx in res.value[:count]] else: logging.info("No transactions found for the given address.") return []
через функцию:
async def send_notifications(bot: Bot, timeout: int):
"""
Функция для отправки уведомлений
:param bot: Инстанс бота который будет отправлять сообщения
:param timeout: Время между проверками новых транзакций (в секундах), настраивается в .env
"""
while True:
wallets = await PostgresRequests().get_all_wallets()
for wallet in wallets:
old_links = await get_links(wallet_hash=wallet.wallet_hash)
new_links = set((await get_latest_sol_transactions(wallet.wallet_hash)))
diff = new_links - old_links
for link in diff:
transaction = await get_sol_transaction(link)
text = f"Транзакция: {transaction["transaction"]} \n"
text += f"Отправитель: {transaction["sender"]} \n"
text += f"Получатель: {transaction["receiver"]} \n"
text += f"Сумма: {transaction["amount"]} SOL \n"
await bot.send_message(chat_id=wallet.tg_id, text=text)
textawait store_links(wallet_hash=wallet.wallet_hash, links=new_links) await asyncio.sleep(timeout)
как мне это пофиксить?
The error you're encountering is due to a httpx.ReadTimeout
exception, which is raised when the HTTP request to the Solana RPC endpoint takes longer than the allowed timeout period. This exception is then re-raised as a solana.exceptions.SolanaRpcException
by the Solana client library.
Why This Happens:
https://api.mainnet-beta.solana.com
) might be experiencing high load or network latency, causing requests to take longer than expected.How to Fix It:
Increase the Timeout: You can specify a longer timeout when initializing the AsyncClient
from the solana-py
library. This will give the server more time to respond before a timeout exception is raised.
Implement Retry Logic: Wrap your request in a try-except block and implement retries with exponential backoff in case of transient failures.
Here's how you can modify your code:
pythonimport asyncio import logging import os from solana.rpc.async_api import AsyncClient from solana.publickey import PublicKey async def get_latest_sol_transactions(address: str) -> list[str]: """ Получить последние n транзакций для указанного адреса в сети Solana. :param address: Строка с адресом кошелька Solana. :return: Список подписей последних транзакций. """ count = int(os.getenv('SOLANA_WALLETS_NUM', '10')) # Default to 10 if not set timeout = 30 # Set the timeout to 30 seconds max_retries = 3 # Maximum number of retries retry_delay = 5 # Initial delay between retries for attempt in range(max_retries): try: async with AsyncClient('https://api.mainnet-beta.solana.com', timeout=timeout) as client: # Запрос подписей транзакций для указанного адреса res = await client.get_signatures_for_address(PublicKey(address)) if res.value is not None: # Возвращаем только первые `count` транзакций return [str(tx.signature) for tx in res.value[:count]] else: logging.info("No transactions found for the given address.") return [] except Exception as e: logging.error(f"Attempt {attempt + 1}: Failed to get transactions due to {e}") if attempt < max_retries - 1: await asyncio.sleep(retry_delay) retry_delay *= 2 # Exponential backoff else: raise e # Re-raise the exception if max retries exceeded
Explanation:
Timeout Parameter: We set the timeout
parameter when initializing AsyncClient
. This adjusts the maximum time to wait for a response.
pythonasync with AsyncClient('https://api.mainnet-beta.solana.com', timeout=timeout) as client:
Retry Logic: We implement a retry mechanism with exponential backoff. If a transient error occurs, the code waits and retries the request.
Logging: We added logging to keep track of failed attempts, which can help with debugging.
Additional Recommendations:
Environment Variables: Ensure that the SOLANA_WALLETS_NUM
environment variable is set appropriately or provide a default value.
Error Handling: Be cautious with broad exception handling (except Exception as e
). If you want to handle specific exceptions (like timeouts), you can catch httpx.ReadTimeout
and solana.exceptions.SolanaRpcException
separately.
Thread Safety: If this code runs in a multi-threaded environment, ensure that the AsyncClient
is used correctly within each coroutine.
Update Dependencies: Make sure all your libraries (solana-py
, httpx
, etc.) are up to date, as newer versions might have fixes for related issues.
Example with Specific Exception Handling:
pythonfrom httpx import ReadTimeout from solana.exceptions import SolanaRpcException ... except (ReadTimeout, SolanaRpcException) as e: logging.error(f"Attempt {attempt + 1}: Timeout occurred: {e}") ...
Implementing in Your send_notifications
Function:
Ensure that any exceptions not handled within get_latest_sol_transactions
are appropriately managed in your send_notifications
function to prevent the entire notification loop from crashing due to an unhandled exception.
Final Notes:
INFO:aiogram.dispatcher:Run polling for bot @xdxdssw1_bot id=7678605295 - 'worktest'
INFO:httpx:HTTP Request: POST https://api.mainnet-beta.solana.com "HTTP/1.1 200 OK"
INFO:httpx:HTTP Request: POST https://api.mainnet-beta.solana.com "HTTP/1.1 200 OK"
INFO:httpx:HTTP Request: POST https://api.mainnet-beta.solana.com "HTTP/1.1 429 Too Many Requests"
ERROR:root:Timeout occurred:
ERROR:asyncio:Task exception was never retrieved
future: <Task finished name='Task-3' coro=<send_notifications() done, defined at /app/main.py:15> exception=TypeError("'NoneType' object is not iterable")>
Traceback (most recent call last):
File "/app/main.py", line 25, in send_notifications
new_links = set((await get_latest_sol_transactions(wallet.wallet_hash)))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: 'NoneType' object is not iterable
The new error you're encountering indicates that your get_latest_sol_transactions
function is returning None
, and when you attempt to create a set from it in send_notifications
, Python raises a TypeError
because None
is not iterable.
Understanding the Error:
HTTP 429 Too Many Requests: The log shows that you're receiving a 429 Too Many Requests
response from the Solana RPC endpoint (https://api.mainnet-beta.solana.com
). This means you've exceeded the rate limit for requests to this endpoint.
Function Returns None
: Due to the exception, your function get_latest_sol_transactions
does not return any value, which defaults to None
in Python.
TypeError in send_notifications
: When send_notifications
tries to execute set((await get_latest_sol_transactions(wallet.wallet_hash)))
, it fails because you cannot create a set from None
.
How to Fix It:
Ensure the Function Always Returns a List or Raises an Exception:
Modify your get_latest_sol_transactions
function to ensure that it either returns a list or raises an exception in all code paths. This prevents it from returning None
.
Handle Exceptions in send_notifications
:
In your send_notifications
function, wrap the call to get_latest_sol_transactions
in a try-except block to handle any exceptions that might occur.
Implement Rate Limiting or Use an Alternative RPC Endpoint:
Since you're hitting rate limits, consider using a different Solana RPC endpoint or implement rate limiting in your code to avoid sending too many requests in a short period.
Modified Code Examples:
1. Modify get_latest_sol_transactions
Function:
pythonimport asyncio import logging import os from solana.rpc.async_api import AsyncClient from solana.publickey import PublicKey from solana.exceptions import SolanaRpcException async def get_latest_sol_transactions(address: str) -> list[str]: """ Get the latest n transactions for the specified Solana address. :param address: Solana wallet address as a string. :return: List of signatures of the latest transactions. """ count = int(os.getenv('SOLANA_WALLETS_NUM', '10')) # Default to 10 if not set timeout = 30 # Timeout in seconds max_retries = 3 # Maximum number of retries retry_delay = 5 # Initial delay between retries for attempt in range(1, max_retries + 1): try: async with AsyncClient('https://api.mainnet-beta.solana.com', timeout=timeout) as client: res = await client.get_signatures_for_address(PublicKey(address)) if res.value is not None: return [str(tx.signature) for tx in res.value[:count]] else: logging.info("No transactions found for the given address.") return [] except Exception as e: logging.error(f"Attempt {attempt}: Failed to get transactions due to: {repr(e)}") if attempt < max_retries: await asyncio.sleep(retry_delay) retry_delay *= 2 # Exponential backoff else: logging.error(f"Max retries exceeded for address {address}. Raising exception.") raise # Re-raise the last exception # In case all retries are exhausted without raising an exception return []
2. Modify send_notifications
Function:
pythonasync def send_notifications(bot: Bot, timeout: int): """ Function to send notifications. :param bot: Bot instance that will send messages. :param timeout: Time between checks for new transactions (in seconds), configured in .env. """ while True: wallets = await PostgresRequests().get_all_wallets() for wallet in wallets: old_links = await get_links(wallet_hash=wallet.wallet_hash) try: new_links = set(await get_latest_sol_transactions(wallet.wallet_hash)) except Exception as e: logging.error(f"Failed to get transactions for wallet {wallet.wallet_hash} due to: {repr(e)}") continue # Skip to the next wallet diff = new_links - old_links for link in diff: transaction = await get_sol_transaction(link) text = f"Транзакция: {transaction['transaction']} \n" text += f"Отправитель: {transaction['sender']} \n" text += f"Получатель: {transaction['receiver']} \n" text += f"Сумма: {transaction['amount']} SOL \n" await bot.send_message(chat_id=wallet.tg_id, text=text) await store_links(wallet_hash=wallet.wallet_hash, links=new_links) await asyncio.sleep(timeout)
3. Handle Rate Limiting:
Since you're receiving 429 Too Many Requests
, you should handle this specifically:
pythonfrom httpx import HTTPStatusError async def get_latest_sol_transactions(address: str) -> list[str]: # ... (previous code) ... except SolanaRpcException as e: if '429' in str(e): logging.error(f"Received 429 Too Many Requests for address {address}. Retrying after delay.") if attempt < max_retries: await asyncio.sleep(retry_delay) retry_delay *= 2 else: logging.error(f"Max retries exceeded due to rate limiting. Raising exception.") raise else: logging.error(f"Solana RPC Exception occurred: {repr(e)}") raise except Exception as e: logging.error(f"Attempt {attempt}: Unexpected error: {repr(e)}") raise
4. Consider Using an Alternative Solana RPC Endpoint:
The public Solana RPC endpoint (https://api.mainnet-beta.solana.com
) has strict rate limits. You can use alternative endpoints or services like:
These services often provide higher rate limits or even unlimited requests for free tiers.
5. Implement Client-Side Rate Limiting:
If changing the endpoint is not an option, implement rate limiting in your code to prevent hitting the server's limits:
pythonimport asyncio from asyncio_throttle import Throttler throttler = Throttler(rate_limit=5, period=1) # Allow 5 requests per second async def get_latest_sol_transactions(address: str) -> list[str]: # ... (previous code) ... async with throttler: async with AsyncClient('https://api.mainnet-beta.solana.com', timeout=timeout) as client: # ... (rest of the code) ...
Note: You'll need to install asyncio-throttle
with pip install asyncio-throttle
.
6. Adjust Logging for Clarity:
Make sure your logging statements include repr(e)
to capture the full exception information, especially since some exceptions may not include a message.
Example:
pythonlogging.error(f"Attempt {attempt}: Failed to get transactions due to: {repr(e)}")
Summary:
get_latest_sol_transactions
always returns a list or raises an exception.send_notifications
to prevent the entire loop from crashing.Final Thoughts:
By making these adjustments, your application should become more robust, handle exceptions gracefully, and comply with the rate limits imposed by the Solana RPC endpoint.
File "/usr/local/lib/python3.12/site-packages/aiogram/dispatcher/router.py", line 141, in _wrapped
return await self._propagate_event(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/aiogram/dispatcher/router.py", line 174, in _propagate_event
response = await router.propagate_event(update_type=update_type, event=event, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/aiogram/dispatcher/router.py", line 146, in propagate_event
return await observer.wrap_outer_middleware(_wrapped, event=event, data=kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/aiogram/dispatcher/router.py", line 141, in _wrapped
return await self._propagate_event(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/aiogram/dispatcher/router.py", line 166, in _propagate_event
response = await observer.trigger(event, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/aiogram/dispatcher/event/telegram.py", line 121, in trigger
return await wrapped_inner(event, kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/aiogram/dispatcher/event/handler.py", line 43, in call
return await wrapped()
^^^^^^^^^^^^^^^
File "/app/handlers/wallets.py", line 82, in enable_tracking
links = await get_latest_sol_transactions(address)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/parsers/crypto/solana_check.py", line 63, in get_latest_sol_transactions
res = await client.get_signatures_for_address(Pubkey(address))
^^^^^^^^^^^^^^^
ValueError: expected a sequence of length 32 (got 44)
ERROR:root:Attempt 3: Failed to get transactions due to: ValueError('expected a sequence of length 32 (got 44)')
ERROR:root:Max retries exceeded for address 76a3CxHZYeGoTJLXopkKqE53zMBwgNriDyDgPZ456gmq. Raising exception.
ERROR:root:Failed to get transactions for wallet 76a3CxHZYeGoTJLXopkKqE53zMBwgNriDyDgPZ456gmq due to: ValueError('expected a sequence of length 32 (got 44)')
INFO:sqlalchemy.engine.Engine:BEGIN (implicit)
INFO:sqlalchemy.engine.Engine:SELECT wallets.id, wallets.tg_id, wallets.wallet_hash, wallets.seed
FROM wallets
INFO:sqlalchemy.engine.Engine:[cached since 75.17s ago] ()
INFO:sqlalchemy.engine.Engine:ROLLBACK
ERROR:root:Attempt 1: Failed to get transactions due to: ValueError('expected a sequence of length 32 (got 44)')
The error you're encountering is:
ValueError: expected a sequence of length 32 (got 44)
This error occurs when constructing a PublicKey
object with an input that doesn't meet the expected format or length. Specifically, the PublicKey
constructor expects a sequence of bytes of length 32 (the length of a Solana public key in bytes), but it's receiving an input of length 44.
Understanding the Error:
Pubkey
instead of PublicKey
.Pubkey
class may be expecting a raw byte sequence, not a base58-encoded string.Solution:
Use the Correct Class:
Ensure you're importing and using the PublicKey
class from the solana.publickey
module, not Pubkey
.
pythonfrom solana.publickey import PublicKey
Modify Your Function:
Update your get_latest_sol_transactions
function to use PublicKey
:
pythonasync def get_latest_sol_transactions(address: str) -> list[str]: """ Получить последние n транзакций для указанного адреса в сети Solana. :param address: Строка с адресом кошелька Solana. :return: Список подписей последних транзакций. """ count = int(os.getenv('SOLANA_WALLETS_NUM', '10')) # Default to 10 if not set timeout = 30 # Timeout in seconds max_retries = 3 # Maximum number of retries retry_delay = 5 # Initial delay between retries for attempt in range(1, max_retries + 1): try: async with AsyncClient('https://api.mainnet-beta.solana.com', timeout=timeout) as client: res = await client.get_signatures_for_address(PublicKey(address)) if res.value is not None: return [str(tx.signature) for tx in res.value[:count]] else: logging.info("No transactions found for the given address.") return [] except Exception as e: logging.error(f"Attempt {attempt}: Failed to get transactions due to: {repr(e)}") if attempt < max_retries: await asyncio.sleep(retry_delay) retry_delay *= 2 # Exponential backoff else: logging.error(f"Max retries exceeded for address {address}. Raising exception.") raise # Re-raise the last exception # In case all retries are exhausted without raising an exception return []
Explanation:
PublicKey
vs. Pubkey
: The PublicKey
class is designed to handle base58-encoded strings directly, whereas Pubkey
might not.PublicKey
constructor will internally decode the base58-encoded address to the required 32-byte sequence.PublicKey
from the correct module.Check Your Imports:
Make sure you have the correct import statements:
pythonfrom solana.publickey import PublicKey
Verify the Address:
Ensure that the address you're using is a valid Solana public key in base58 format.
Additional Notes:
Library Version: Ensure you're using a compatible version of the solana-py
library. Updating to the latest version can resolve unexpected issues.
bashpip install --upgrade solana
Error Handling: The exception handling in your function will now catch and log the error, then retry as per your logic.
Consistent Class Usage: Using PublicKey
consistently throughout your code will prevent similar errors.
Alternative Approach (If Needed):
If you still encounter issues, you can manually decode the base58-encoded address:
pythonfrom solana.publickey import PublicKey from base58 import b58decode async def get_latest_sol_transactions(address: str) -> list[str]: # ... previous code ... try: pubkey_bytes = b58decode(address) res = await client.get_signatures_for_address(PublicKey(pubkey_bytes)) # ... rest of your code ...
However, this shouldn't be necessary if PublicKey(address)
works correctly.
Implementing in Your enable_tracking
Function:
Ensure that you update the enable_tracking
function accordingly:
pythonasync def enable_tracking(): # ... previous code ... links = await get_latest_sol_transactions(address) # ... rest of your code ...
Summary:
Pubkey
with PublicKey
.solana.publickey
.By making these changes, the ValueError
should be resolved, and your application should function as expected.
Final Thoughts:
If you have any more questions or encounter further issues, feel free to ask!
ERROR:asyncio:Task exception was never retrieved
future: <Task finished name='Task-3' coro=<send_notifications() done, defined at /app/main.py:15> exception=KeyError('program')>
Traceback (most recent call last):
File "/app/main.py", line 33, in send_notifications
transaction = await get_sol_transaction(link)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/app/parsers/crypto/solana_check.py", line 127, in get_sol_transaction
if instruction['program'] == 'system':
~~~~~~~~~~~^^^^^^^^^^^
KeyError: 'program'
async def get_sol_transaction(tx_signature: str) -> dict | None:
"""
Получение информации о транзакции Solana по ее подписи.
text:param tx_signature: Подпись транзакции Solana в формате строки :return: Словарь с информацией о транзакции (подпись, отправитель, получатель, сумма) / None в случае ошибок """ url = 'https://api.mainnet-beta.solana.com' headers = {'Content-Type': 'application/json'} payload = { "jsonrpc": "2.0", "id": 1, "method": "getTransaction", "params": [ tx_signature, { "encoding": "jsonParsed", "maxSupportedTransactionVersion": 0 } ] } async with aiohttp.ClientSession() as session: async with session.post(url, json=payload, headers=headers) as resp: response = await resp.json() if 'result' not in response or response['result'] is None: logging.error("Транзакция не найдена или произошла ошибка.") return result = response['result'] # Информация о транзакции transaction = result['transaction'] message = transaction['message'] account_keys = message['accountKeys'] instructions = message['instructions'] # Инициализация переменных получателя и суммы receiver = None amount_lamports = None # Отправитель — первый в списке accountKeys (fee payer) sender = account_keys[0]['pubkey'] # Поиск инструкции перевода в списке инструкций for instruction in instructions: if instruction['program'] == 'system': parsed = instruction.get('parsed') if parsed and parsed['type'] == 'transfer': info = parsed['info'] receiver = info['destination'] amount_lamports = int(info['lamports']) break # Предполагаем, что нас интересует первая инструкция перевода if amount_lamports is not None: amount_sol = amount_lamports / 1e9 else: amount_sol = None return { "transaction": tx_signature, "sender": sender, "receiver": receiver, "amount": amount_sol, }
The KeyError: 'program'
you're encountering indicates that in some of the instructions within the instructions
list, the 'program'
key does not exist. This is causing your code to fail when it tries to access instruction['program']
.
Understanding the Error:
'program'
key but instead have a 'programId'
key, especially if they are not parsed or are in a different format.Solution:
You need to adjust your code to handle cases where the 'program'
key might not be present in an instruction. Here's how you can modify your code:
Use .get()
Method:
Replace instruction['program']
with instruction.get('program')
. This will return None
if the key doesn't exist, preventing a KeyError
.
Add a Check for the Key:
Before accessing instruction['program']
, check if the key exists in the instruction.
Handle Instructions with 'programId':
If the 'program'
key is not present, check for the 'programId'
key, which might contain the program ID in base58 format.
Log the Instruction for Debugging:
Logging the instruction when the 'program'
key is missing can help you understand the structure of such instructions.
Modified Code:
Here's the updated get_sol_transaction
function with the necessary adjustments:
pythonimport aiohttp import logging async def get_sol_transaction(tx_signature: str) -> dict | None: """ Получение информации о транзакции Solana по ее подписи. :param tx_signature: Подпись транзакции Solana в формате строки :return: Словарь с информацией о транзакции (подпись, отправитель, получатель, сумма) / None в случае ошибок """ url = 'https://api.mainnet-beta.solana.com' headers = {'Content-Type': 'application/json'} payload = { "jsonrpc": "2.0", "id": 1, "method": "getTransaction", "params": [ tx_signature, { "encoding": "jsonParsed", "maxSupportedTransactionVersion": 0 } ] } async with aiohttp.ClientSession() as session: async with session.post(url, json=payload, headers=headers) as resp: response = await resp.json() if 'result' not in response or response['result'] is None: logging.error("Транзакция не найдена или произошла ошибка.") return result = response['result'] # Информация о транзакции transaction = result['transaction'] message = transaction['message'] account_keys = message['accountKeys'] instructions = message['instructions'] # Инициализация переменных получателя и суммы receiver = None amount_lamports = None # Отправитель — первый в списке accountKeys (fee payer) sender = account_keys[0]['pubkey'] # Поиск инструкции перевода в списке инструкций for instruction in instructions: program = instruction.get('program') if program == 'system': parsed = instruction.get('parsed') if parsed and parsed.get('type') == 'transfer': info = parsed.get('info') if info: receiver = info.get('destination') amount_lamports = int(info.get('lamports', 0)) break # Предполагаем, что нас интересует первая инструкция перевода else: # Optionally, handle other types of instructions or log them logging.debug(f"Skipping instruction without 'program' key or non-system program: {instruction}") if amount_lamports is not None: amount_sol = amount_lamports / 1e9 else: amount_sol = None return { "transaction": tx_signature, "sender": sender, "receiver": receiver, "amount": amount_sol, }
Explanation:
Using .get()
Method:
instruction.get('program')
safely retrieves the value associated with 'program'
or returns None
if the key doesn't exist.KeyError
exception when the key is missing.Additional Checks:
.get()
to ensure they exist.parsed.get('type')
and info.get('destination')
.Logging:
logging.debug
statement to log instructions that are skipped. This can help you understand what other types of instructions are present in the transaction.Handling Amount:
info.get('lamports', 0)
to provide a default value in case 'lamports'
is missing.Additional Recommendations:
Check for 'programId' Key:
Sometimes, the instruction might have a 'programId'
key instead of 'program'
. You can adjust your code to handle this:
pythonprogram = instruction.get('program') or instruction.get('programId') if program == 'system' or program == '11111111111111111111111111111111': # Proceed with parsing
Note: '11111111111111111111111111111111'
is the program ID for the system program in Solana.
Handle Inner Instructions:
Some transactions include inner instructions that might contain the transfer details. You might need to check meta.innerInstructions
.
pythonmeta = result.get('meta', {}) inner_instructions = meta.get('innerInstructions', []) # Process inner instructions similarly
Robust Error Handling:
Add exception handling around sections that might fail to prevent the function from crashing.
pythontry: # Parsing logic except Exception as e: logging.error(f"Error parsing transaction {tx_signature}: {e}") return
Testing:
Test with Different Transactions:
Log Unexpected Instructions:
Example of Enhanced Logging:
pythonfor instruction in instructions: program = instruction.get('program') or instruction.get('programId') if program == 'system' or program == '11111111111111111111111111111111': # Proceed with parsing else: logging.debug(f"Instruction with unhandled program: {program}, instruction: {instruction}")
Handling 'programId' as Base58 String:
If program
is not equal to 'system'
, it might be the program ID in base58 format.
You can compare program
to the system program ID:
pythonSYSTEM_PROGRAM_ID = '11111111111111111111111111111111' program_id = instruction.get('programId') if program_id == SYSTEM_PROGRAM_ID: # Proceed with parsing
Final Code Example:
Combining the suggestions, here's a more robust version:
pythonimport aiohttp import logging SYSTEM_PROGRAM_ID = '11111111111111111111111111111111' async def get_sol_transaction(tx_signature: str) -> dict | None: # ... (previous code remains the same) ... # Инициализация переменных получателя и суммы receiver = None amount_lamports = None # Отправитель — первый в списке accountKeys (fee payer) sender = account_keys[0]['pubkey'] # Поиск инструкции перевода в списке инструкций for instruction in instructions: program = instruction.get('program') program_id = instruction.get('programId') if program == 'system' or program_id == SYSTEM_PROGRAM_ID: parsed = instruction.get('parsed') if parsed and parsed.get('type') == 'transfer': info = parsed.get('info', {}) receiver = info.get('destination') amount_lamports = int(info.get('lamports', 0)) break # Предполагаем, что нас интересует первая инструкция перевода else: logging.debug(f"Skipping instruction: {instruction}") if amount_lamports: amount_sol = amount_lamports / 1e9 else: amount_sol = None return { "transaction": tx_signature, "sender": sender, "receiver": receiver, "amount": amount_sol, }
Summary:
.get()
when accessing dictionary keys.Additional Considerations:
Handle Transactions Without Transfers:
Monitor API Limits:
Final Thoughts:
By making these adjustments, your function should handle transactions more robustly and avoid crashing due to unexpected instruction formats. Always ensure that your code can handle various edge cases, especially when dealing with external data sources like blockchain APIs.
If you have any more questions or need further assistance, feel free to ask!
INFO:httpx:HTTP Request: POST https://api.mainnet-beta.solana.com "HTTP/1.1 200 OK"
ERROR:root:Транзакция не найдена или произошла ошибка.
ERROR:asyncio:Task exception was never retrieved
future: <Task finished name='Task-3' coro=<send_notifications() done, defined at /app/main.py:15> exception=TypeError("'NoneType' object is not subscriptable")>
Traceback (most recent call last):
File "/app/main.py", line 34, in send_notifications
text = f"Транзакция: {transaction['transaction']} \n"
~~~~~~~~~~~^^^^^^^^^^^^^^^
TypeError: 'NoneType' object is not subscriptable
Вот что произошло когда я свапнул Solana в WEN, но сообщение в боте все таки пришло. Их было 2 и выглядели они так:
The error you're encountering is:
TypeError: 'NoneType' object is not subscriptable
This error occurs because your get_sol_transaction
function is returning None
, and your send_notifications
function is trying to access it as if it were a dictionary. Specifically, the line:
pythontext = f"Транзакция: {transaction['transaction']} \n"
fails because transaction
is None
, and you cannot subscript ([]
) a NoneType
object.
Understanding the Error:
get_sol_transaction
Returns None
: Your function returns None
when it cannot find the transaction or when an error occurs.None
: In your send_notifications
function, you proceed to use transaction
without checking if it's None
.transaction['transaction']
when transaction
is None
raises a TypeError
.Why Did This Happen?
When you swapped Solana for WEN, the transaction might not have the expected structure that your get_sol_transaction
function can parse. It could be a more complex transaction involving token swaps, which may not include the standard 'transfer' instruction of the 'system' program.
Solution:
Check for None
in send_notifications
:
Modify your send_notifications
function to handle cases where get_sol_transaction
returns None
.
pythonasync def send_notifications(bot: Bot, timeout: int): """ Function to send notifications. """ while True: wallets = await PostgresRequests().get_all_wallets() for wallet in wallets: old_links = await get_links(wallet_hash=wallet.wallet_hash) try: new_links = set(await get_latest_sol_transactions(wallet.wallet_hash)) except Exception as e: logging.error(f"Failed to get transactions for wallet {wallet.wallet_hash} due to: {repr(e)}") continue # Skip to the next wallet diff = new_links - old_links for link in diff: transaction = await get_sol_transaction(link) if transaction is None: logging.error(f"Failed to retrieve transaction details for {link}. Skipping.") continue # Skip this transaction text = f"Транзакция: {transaction['transaction']} \n" text += f"Отправитель: {transaction['sender']} \n" text += f"Получатель: {transaction['receiver']} \n" text += f"Сумма: {transaction['amount']} SOL \n" await bot.send_message(chat_id=wallet.tg_id, text=text) await store_links(wallet_hash=wallet.wallet_hash, links=new_links) await asyncio.sleep(timeout)
Explanation:
transaction
is None
: Before trying to access its keys, verify that transaction
is not None
.transaction
is None
, log an error message and continue
to the next iteration.Enhance Logging in get_sol_transaction
:
Modify get_sol_transaction
to include the transaction signature in the error message. This will help you identify which transactions are causing issues.
pythonasync def get_sol_transaction(tx_signature: str) -> dict | None: # ... previous code ... async with aiohttp.ClientSession() as session: async with session.post(url, json=payload, headers=headers) as resp: response = await resp.json() if 'result' not in response or response['result'] is None: logging.error(f"Transaction {tx_signature} not found or an error occurred.") return None # ... rest of your code ...
Handle Complex Transactions:
Your get_sol_transaction
function might not be equipped to handle complex transactions like token swaps. These transactions may not include the standard 'transfer' instruction you're looking for. To handle this:
Check for Inner Instructions:
Some transactions include inner instructions that contain the actual transfer details.
pythonmeta = result.get('meta', {}) inner_instructions = meta.get('innerInstructions', []) # Process inner instructions similar to the main instructions
Expand Instruction Parsing:
Modify your function to parse different types of instructions and handle tokens other than SOL.
pythonfor instruction in instructions: program = instruction.get('program') or instruction.get('programId') if program in ['system', 'spl-token']: # Handle system transfers and token transfers
Update get_sol_transaction
to Be More Robust:
Here's an updated version of get_sol_transaction
that attempts to handle token transfers and includes additional error handling:
pythonasync def get_sol_transaction(tx_signature: str) -> dict | None: """ Получение информации о транзакции Solana по ее подписи. """ url = 'https://api.mainnet-beta.solana.com' headers = {'Content-Type': 'application/json'} payload = { "jsonrpc": "2.0", "id": 1, "method": "getTransaction", "params": [ tx_signature, { "encoding": "jsonParsed", "maxSupportedTransactionVersion": 0 } ] } async with aiohttp.ClientSession() as session: async with session.post(url, json=payload, headers=headers) as resp: response = await resp.json() if 'result' not in response or response['result'] is None: logging.error(f"Transaction {tx_signature} not found or an error occurred.") return None result = response['result'] transaction = result['transaction'] message = transaction['message'] account_keys = message['accountKeys'] instructions = message['instructions'] # Sender (fee payer) is the first in accountKeys sender = account_keys[0]['pubkey'] # Initialize variables receiver = None amount_lamports = None token = 'SOL' # Check both main and inner instructions all_instructions = instructions meta = result.get('meta', {}) inner_instructions = meta.get('innerInstructions', []) for inner in inner_instructions: all_instructions.extend(inner.get('instructions', [])) for instruction in all_instructions: parsed = instruction.get('parsed') if not parsed: continue info = parsed.get('info', {}) instruction_type = parsed.get('type') # Handle system transfer if instruction_type == 'transfer': receiver = info.get('destination') amount_lamports = int(info.get('lamports', 0)) token = 'SOL' break # Handle token transfer elif instruction_type == 'transferChecked': receiver = info.get('destination') amount_lamports = int(info.get('tokenAmount', {}).get('amount', 0)) decimals = int(info.get('tokenAmount', {}).get('decimals', 0)) token = info.get('tokenAmount', {}).get('uiAmountString', 'Unknown Token') amount_sol = amount_lamports / (10 ** decimals) break if amount_lamports is not None: if token == 'SOL': amount = amount_lamports / 1e9 else: amount = amount_sol else: amount = None return { "transaction": tx_signature, "sender": sender, "receiver": receiver, "amount": amount, "token": token, }
Explanation:
transferChecked
instructions, which are commonly used for token transfers.Update Message Formatting:
Adjust your message in send_notifications
to include the token name:
pythontext = f"Транзакция: {transaction['transaction']} \n" text += f"Отправитель: {transaction['sender']} \n" text += f"Получатель: {transaction['receiver']} \n" text += f"Сумма: {transaction['amount']} {transaction['token']} \n"
Handle Transactions Without Amount:
If amount
is None
, decide how you want to handle such transactions. You can choose to:
Skip the Transaction:
pythonif transaction['amount'] is None: logging.info(f"No amount found for transaction {transaction['transaction']}. Skipping.") continue
Send a Different Message:
pythontext = f"Транзакция: {transaction['transaction']} \n" text += f"Отправитель: {transaction['sender']} \n" text += f"Получатель: {transaction['receiver']} \n" text += "Сумма: Не удалось определить сумму или тип транзакции.\n"
Final Thoughts:
None
or missing data before accessing dictionary keys.Example of the Updated send_notifications
Function:
pythonasync def send_notifications(bot: Bot, timeout: int): while True: wallets = await PostgresRequests().get_all_wallets() for wallet in wallets: old_links = await get_links(wallet_hash=wallet.wallet_hash) try: new_links = set(await get_latest_sol_transactions(wallet.wallet_hash)) except Exception as e: logging.error(f"Failed to get transactions for wallet {wallet.wallet_hash} due to: {repr(e)}") continue diff = new_links - old_links for link in diff: transaction = await get_sol_transaction(link) if transaction is None: logging.error(f"Failed to retrieve transaction details for {link}. Skipping.") continue text = f"Транзакция: {transaction['transaction']} \n" text += f"Отправитель: {transaction['sender']} \n" text += f"Получатель: {transaction['receiver']} \n" text += f"Сумма: {transaction['amount']} {transaction['token']} \n" await bot.send_message(chat_id=wallet.tg_id, text=text) await store_links(wallet_hash=wallet.wallet_hash, links=new_links) await asyncio.sleep(timeout)
Summary:
None
Values: Always ensure you're not accessing attributes of None
objects.By implementing these changes, your application should handle transactions more gracefully, and you should no longer encounter the TypeError
due to NoneType
objects.
If you need further assistance or have additional questions, feel free to ask!
Теперь аутпут вот такой:
The issue you're encountering stems from the way small floating-point numbers are displayed in Python. When dealing with very small amounts, Python may represent them in scientific notation (e.g., 1e-07
), which can be confusing or misleading in user-facing messages.
Additionally, there may be inaccuracies in how the amount is calculated and displayed for token transfers, especially if the transaction involves tokens other than SOL.
Here's how to fix it:
Format the Amount Appropriately:
To avoid displaying amounts in scientific notation, you can format the amount using string formatting to a fixed number of decimal places.
Ensure Correct Amount Calculation for Token Transfers:
For token transfers, you need to extract the amount and token information correctly. Token transactions often involve different decimals and require proper parsing to display accurate information.
Map Token Mint Addresses to Token Symbols:
To display the correct token symbol (e.g., "WEN" instead of the token mint address), you can use a token list to map mint addresses to their corresponding symbols.
Step-by-Step Solution:
get_sol_transaction
FunctionModify your get_sol_transaction
function to handle token transfers correctly and format the amount properly.
pythonimport aiohttp import logging # Load the token list once at startup token_map = {} def load_token_list(): global token_map import requests token_list_url = "https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/solana.tokenlist.json" response = requests.get(token_list_url) token_list = response.json() tokens = token_list.get('tokens', []) token_map = {token['address']: token['symbol'] for token in tokens} def get_token_symbol(mint_address): return token_map.get(mint_address, mint_address) # Return the symbol or the mint address if not found # Call load_token_list() at startup load_token_list() async def get_sol_transaction(tx_signature: str) -> dict | None: """ Retrieve Solana transaction information by its signature. """ url = 'https://api.mainnet-beta.solana.com' headers = {'Content-Type': 'application/json'} payload = { "jsonrpc": "2.0", "id": 1, "method": "getTransaction", "params": [ tx_signature, { "encoding": "jsonParsed", "maxSupportedTransactionVersion": 0 } ] } async with aiohttp.ClientSession() as session: async with session.post(url, json=payload, headers=headers) as resp: response = await resp.json() if 'result' not in response or response['result'] is None: logging.error(f"Transaction {tx_signature} not found or an error occurred.") return None result = response['result'] transaction = result['transaction'] message = transaction['message'] account_keys = message['accountKeys'] instructions = message['instructions'] # Sender (fee payer) is the first in accountKeys sender = account_keys[0]['pubkey'] # Initialize variables receiver = None amount = None token = 'SOL' token_mint = None # Combine instructions and inner instructions all_instructions = instructions.copy() meta = result.get('meta', {}) inner_instructions = meta.get('innerInstructions', []) for inner in inner_instructions: all_instructions.extend(inner.get('instructions', [])) for instruction in all_instructions: parsed = instruction.get('parsed') if not parsed: continue info = parsed.get('info', {}) instruction_type = parsed.get('type') program = instruction.get('program') or instruction.get('programId') if (program == 'system' or program == '11111111111111111111111111111111') and instruction_type == 'transfer': # Handle SOL transfer receiver = info.get('destination') amount_lamports = int(info.get('lamports', 0)) amount = amount_lamports / 1e9 token = 'SOL' break elif (program == 'spl-token' or program == 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA') and instruction_type in ['transfer', 'transferChecked']: # Handle SPL token transfer receiver = info.get('destination') amount_raw = int(info.get('tokenAmount', {}).get('amount', 0)) decimals = int(info.get('tokenAmount', {}).get('decimals', 0)) amount = amount_raw / (10 ** decimals) token_mint = info.get('mint') token = get_token_symbol(token_mint) break else: continue if amount is not None: return { "transaction": tx_signature, "sender": sender, "receiver": receiver, "amount": amount, "token": token, } else: logging.info(f"No transfer instruction found in transaction {tx_signature}") return None
Explanation:
Token Mapping:
load_token_list()
: Loads the Solana token list once at startup and populates the token_map
dictionary.get_token_symbol(mint_address)
: Retrieves the token symbol from token_map
based on the mint address.Amount Calculation:
amount_lamports / 1e9
).amount_raw / (10 ** decimals)
).Instruction Parsing:
send_notifications
Modify the message formatting to display the amount with a fixed number of decimal places to prevent scientific notation.
pythonasync def send_notifications(bot: Bot, timeout: int): while True: wallets = await PostgresRequests().get_all_wallets() for wallet in wallets: old_links = await get_links(wallet_hash=wallet.wallet_hash) try: new_links = set(await get_latest_sol_transactions(wallet.wallet_hash)) except Exception as e: logging.error(f"Failed to get transactions for wallet {wallet.wallet_hash} due to: {repr(e)}") continue diff = new_links - old_links for link in diff: transaction = await get_sol_transaction(link) if transaction is None: logging.error(f"Failed to retrieve transaction details for {link}. Skipping.") continue # Format the amount to avoid scientific notation amount_formatted = "{:.9f}".format(transaction['amount']).rstrip('0').rstrip('.') text = f"Транзакция: {transaction['transaction']} \n" text += f"Отправитель: {transaction['sender']} \n" text += f"Получатель: {transaction['receiver']} \n" text += f"Сумма: {amount_formatted} {transaction['token']} \n" await bot.send_message(chat_id=wallet.tg_id, text=text) await store_links(wallet_hash=wallet.wallet_hash, links=new_links) await asyncio.sleep(timeout)
Explanation:
"{:.9f}".format(transaction['amount'])
: Formats the amount to 9 decimal places.rstrip('0').rstrip('.')
: Removes trailing zeros and any trailing decimal point if the number is an integer.0.0000001
, it will display as 0.0000001
instead of 1e-07
.1.0000000
, it will display as 1
.After making these changes, your bot messages should display the amount accurately and avoid scientific notation.
Example Messages:
SOL Transfer:
textТранзакция: 3ua66VcYeHMoYyMvP4pRzcw8NXz2irAgSEX6YsjaEQLE7MMLKa4d2jQ5hAAvMHxwe287uS2Fkx6PrC4WztwBiWgG Отправитель: 76a3CxHZYeGoTJLXopkKqE53zMBwgNriDyDgPZ456gmq Получатель: AFFBbhjp6RQtaobLizaRh4Su39GN5uCCRQY7v5LRbYGe Сумма: 0.00015 SOL
Token Transfer:
textТранзакция: 3DtMVrBVetkyq4JxNG7yUoGAbuiVSa4jUXPPbLxNGEPBZba644nUcsG8WvpDDpYRx2sMNoST52cjhk5cCDoS2gRY Отправитель: fLiPgg2yTvmgfhiPkKriAHkDmmXGP6CdeFX9UF5o7Zc Получатель: GJ2RJuf8BFZZHU4EjoVT9Br4gMsmgMQaVLENimxjEYw Сумма: 0.0000001 WEN
Note:
Handling Missing Tokens:
get_token_symbol
will return the mint address.Caching the Token List:
Error Handling:
Final Thoughts:
By implementing these changes, your bot should now accurately display transaction amounts and tokens, providing clear and reliable information to users. Always test your application with various transaction types to ensure it handles different scenarios correctly.
If you need further assistance or have additional questions, feel free to ask!
Traceback (most recent call last):
File "/app/main.py", line 64, in <module>
asyncio.run(load_token_list())
File "/usr/local/lib/python3.12/asyncio/runners.py", line 194, in run
return runner.run(main)
^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/asyncio/runners.py", line 118, in run
return self._loop.run_until_complete(task)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/asyncio/base_events.py", line 687, in run_until_complete
return future.result()
^^^^^^^^^^^^^^^
File "/app/parsers/crypto/solana_check.py", line 86, in load_token_list
token_list = await response.json()
^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/aiohttp/client_reqrep.py", line 1278, in json
raise ContentTypeError(
aiohttp.client_exceptions.ContentTypeError: 200, message='Attempt to decode JSON with unexpected mimetype: text/plain; charset=utf-8', url='https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/solana.tokenlist.json'
async def load_token_list():
global token_map
token_list_url = "https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/solana.tokenlist.json"
async with aiohttp.ClientSession() as session:
async with session.get(token_list_url) as response:
token_list = await response.json()
tokens = token_list.get('tokens', [])
token_map = {token['address']: token['symbol'] for token in tokens}
The error you're encountering is due to a mismatch between the expected Content-Type
header and the actual Content-Type
of the response when using aiohttp
to fetch the JSON file from the URL. Here's the specific error message:
aiohttp.client_exceptions.ContentTypeError: 200, message='Attempt to decode JSON with unexpected mimetype: text/plain; charset=utf-8', url='https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/solana.tokenlist.json'
Understanding the Error:
aiohttp
Behavior: When you use await response.json()
, aiohttp
expects the Content-Type
header of the response to be 'application/json'
by default. If the response has a different Content-Type
, it raises a ContentTypeError
.
Actual Content-Type
: The URL you're accessing returns a Content-Type
of 'text/plain; charset=utf-8'
, not 'application/json'
. This is common for raw files served from GitHub's raw content URLs.
Solution:
You can instruct aiohttp
to ignore the Content-Type
header by passing content_type=None
to response.json()
. This tells aiohttp
to attempt to parse the response body as JSON regardless of the Content-Type
header.
Modified load_token_list
Function:
pythonimport aiohttp import logging token_map = {} async def load_token_list(): global token_map token_list_url = "https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/solana.tokenlist.json" async with aiohttp.ClientSession() as session: async with session.get(token_list_url) as response: # Pass content_type=None to ignore the Content-Type header token_list = await response.json(content_type=None) tokens = token_list.get('tokens', []) token_map = {token['address']: token['symbol'] for token in tokens}
Explanation:
Passing content_type=None
: This parameter overrides the default behavior of aiohttp
and tells it to parse the response as JSON regardless of the Content-Type
header.
pythontoken_list = await response.json(content_type=None)
Why This Works: Since you know the content at the URL is valid JSON, it's safe to bypass the Content-Type
check.
Alternative Solution:
If you prefer not to bypass the Content-Type
check, you can read the response as text and then parse it using the json
module:
pythonimport aiohttp import json token_map = {} async def load_token_list(): global token_map token_list_url = "https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/solana.tokenlist.json" async with aiohttp.ClientSession() as session: async with session.get(token_list_url) as response: text = await response.text() token_list = json.loads(text) tokens = token_list.get('tokens', []) token_map = {token['address']: token['symbol'] for token in tokens}
Explanation:
Reading as Text: await response.text()
reads the response body as a text string.
Parsing JSON Manually: json.loads(text)
parses the text string into a Python dictionary.
Additional Recommendations:
Error Handling:
Add exception handling to catch any potential errors during the network request or JSON parsing.
pythonimport aiohttp import json import logging token_map = {} async def load_token_list(): global token_map token_list_url = "https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/solana.tokenlist.json" try: async with aiohttp.ClientSession() as session: async with session.get(token_list_url) as response: if response.status != 200: logging.error(f"Failed to fetch token list. HTTP status code: {response.status}") return text = await response.text() token_list = json.loads(text) tokens = token_list.get('tokens', []) token_map = {token['address']: token['symbol'] for token in tokens} except Exception as e: logging.error(f"An error occurred while loading the token list: {e}")
Asynchronous Execution:
Ensure that load_token_list()
is called in an asynchronous context since it's an async
function.
pythonimport asyncio # Call this in your main coroutine or setup function asyncio.create_task(load_token_list())
Or, if you're in an asynchronous context:
pythonawait load_token_list()
Update Token List Periodically:
If your application runs continuously, you might want to update the token list periodically to account for new tokens.
pythonasync def update_token_list_periodically(interval_hours=24): while True: await load_token_list() await asyncio.sleep(interval_hours * 3600) # Sleep for specified hours
Start this coroutine in your main event loop:
pythonasyncio.create_task(update_token_list_periodically())
Summary:
Problem: aiohttp
raises ContentTypeError
because the response Content-Type
is 'text/plain'
, not 'application/json'
.
Solution: Override the default behavior by passing content_type=None
to response.json()
, or read the response as text and parse it manually using the json
module.
Implementation: Modify your load_token_list
function accordingly.
Final Thoughts:
By making these changes, your application should successfully fetch and parse the token list from the provided URL without encountering the ContentTypeError
. Always ensure to handle exceptions gracefully to make your application robust.
If you have any further questions or need additional assistance, feel free to ask!
Ты сделал обработку так, чтобы мне не приходили сообщения о токенах НЕ Solana или ты сделал так чтобы мне писалось в каком токене пришла транзакция? просто если первый вариант, то он мне отправил лишь одно сообщение о том что солана отправлена, а о том что что-то пришло мне уведомление не приходило
Здравствуйте,
В предыдущих ответах я предоставил изменения в коде, предназначенные для обработки как SOL, так и токеновых переводов. Цель состояла в том, чтобы вы получали уведомления о транзакциях, связанных с SOL и другими токенами, с указанием токена в сообщениях.
Из вашего описания следует, что вы получаете только уведомления о транзакциях SOL, но не получаете уведомлений о транзакциях с другими токенами. Давайте вместе выясним, почему уведомления о токеновых переводах не приходят.
1. Проверка функции get_sol_transaction
В функции get_sol_transaction
мы пытаемся обработать как SOL, так и токеновые переводы. Возможно, токеновые переводы не обнаруживаются из-за ошибок в логике разбора инструкций.
Добавим дополнительные логи для отладки:
pythonimport logging async def get_sol_transaction(tx_signature: str) -> dict | None: # ... существующий код ... # Инициализация переменных receiver = None amount = None token = 'SOL' token_mint = None # Объединение инструкций и внутренних инструкций all_instructions = instructions.copy() meta = result.get('meta', {}) inner_instructions = meta.get('innerInstructions', []) for inner in inner_instructions: all_instructions.extend(inner.get('instructions', [])) for instruction in all_instructions: parsed = instruction.get('parsed') if not parsed: logging.debug(f"Инструкция без поля 'parsed': {instruction}") continue info = parsed.get('info', {}) instruction_type = parsed.get('type') program = instruction.get('program') or instruction.get('programId') if (program == 'system' or program == '11111111111111111111111111111111') and instruction_type == 'transfer': # Обработка перевода SOL logging.debug(f"SOL перевод обнаружен в транзакции {tx_signature}") receiver = info.get('destination') amount_lamports = int(info.get('lamports', 0)) amount = amount_lamports / 1e9 token = 'SOL' break elif (program == 'spl-token' or program == 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA') and instruction_type in ['transfer', 'transferChecked']: # Обработка токенового перевода logging.debug(f"Токеновый перевод обнаружен в транзакции {tx_signature}") receiver = info.get('destination') sender = info.get('source') # Для токеновых переводов отправитель — 'source' amount_raw = int(info.get('tokenAmount', {}).get('amount', 0)) decimals = int(info.get('tokenAmount', {}).get('decimals', 0)) amount = amount_raw / (10 ** decimals) token_mint = info.get('mint') token = get_token_symbol(token_mint) break else: logging.debug(f"Необработанная инструкция в транзакции {tx_signature}: {instruction}") continue if amount is not None: logging.debug(f"Транзакция {tx_signature} успешно обработана: {amount} {token}") return { "transaction": tx_signature, "sender": sender, "receiver": receiver, "amount": amount, "token": token, } else: logging.info(f"В транзакции {tx_signature} не найдено инструкции перевода") return None
2. Проверка функции send_notifications
Убедитесь, что функция send_notifications
не пропускает токеновые транзакции:
pythonif transaction is None: logging.error(f"Не удалось получить детали транзакции для {link}. Пропуск.") continue # Переходим к следующей транзакции
3. Тестирование с известными токеновыми транзакциями
Попробуйте вручную вызвать get_sol_transaction
с подписью транзакции, которая содержит токеновый перевод, и проверьте вывод:
pythontest_signature = '...подпись транзакции с токеновым переводом...' transaction = await get_sol_transaction(test_signature) print(transaction)
4. Проверка отображения токенов
Убедитесь, что адрес токена (mint address) присутствует в token_map
. Если токен отсутствует в списке, функция get_token_symbol
вернет адрес mint, и это может вызвать недопонимание.
1. Инструкции не обнаруживаются
Проблема: Токеновые переводы могут находиться во внутренних инструкциях (innerInstructions
), и их нужно правильно объединить с основными инструкциями.
Решение: Убедитесь, что вы правильно объединяете все инструкции:
pythonall_instructions = instructions.copy() meta = result.get('meta', {}) inner_instructions = meta.get('innerInstructions', []) for inner in inner_instructions: all_instructions.extend(inner.get('instructions', []))
2. Неправильный парсинг инструкций
Проблема: Возможно, инструкции не парсятся должным образом из-за различий в структуре токеновых переводов.
Решение: Убедитесь, что вы правильно извлекаете sender
, receiver
, amount
и token
из инструкции.
3. Отсутствие токена в token_map
Проблема: Если токен отсутствует в загруженном списке токенов, символ токена не будет найден.
Решение: Добавьте обработку случая, когда символ токена не найден:
pythondef get_token_symbol(mint_address): return token_map.get(mint_address, 'Unknown Token')
4. Пропуск транзакций из-за отсутствия суммы
Проблема: Если amount
не рассчитан, транзакция может быть пропущена.
Решение: Убедитесь, что ваша логика корректно рассчитывает amount
для токеновых переводов.
1. Улучшите обработку ошибок
Добавьте больше логов, чтобы отслеживать процесс и видеть, где возникают проблемы.
2. Проверьте соответствие полей
В токеновых переводах отправитель может быть указан в поле source
, а получатель в destination
.
3. Обновите сообщение в боте
Убедитесь, что вы правильно отображаете информацию о токенах в сообщениях:
pythonamount_formatted = "{:.9f}".format(transaction['amount']).rstrip('0').rstrip('.') text = f"Транзакция: {transaction['transaction']} \n" text += f"Отправитель: {transaction['sender']} \n" text += f"Получатель: {transaction['receiver']} \n" text += f"Сумма: {amount_formatted} {transaction['token']} \n"
Ваш код должен обрабатывать как SOL, так и токеновые переводы, отправляя уведомления с указанием токена. Поскольку вы не получаете уведомления о токеновых переводах, вероятно, есть проблема в логике обработки этих транзакций.
Я рекомендую:
После внесения этих изменений вы должны начать получать уведомления о токеновых переводах с указанием соответствующего токена.
Если после этих шагов проблема не решится, пожалуйста, предоставьте дополнительные логи или информацию, и я с радостью помогу вам дальше.
Если у вас есть дополнительные вопросы или вам нужна помощь, пожалуйста, дайте мне знать!
А что означает это условие?
elif (program == 'spl-token' or program == 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA') and (
instruction_type in ['transfer', 'transferChecked']):
receiver = info.get('destination')
amount_raw = int(info.get('tokenAmount', {}).get('amount', 0))
decimals = int(info.get('tokenAmount', {}).get('decimals', 0))
amount = amount_raw / (10 ** decimals)
token_mint = info.get('mint')
token = get_token_symbol(token_mint)
break
Здравствуйте,
Это условие в вашем коде предназначено для обнаружения и обработки токеновых переводов в транзакциях Solana. Давайте подробно разберём, что оно означает и как работает.
pythonelif (program == 'spl-token' or program == 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA') and ( instruction_type in ['transfer', 'transferChecked']): # Обработка токенового перевода receiver = info.get('destination') amount_raw = int(info.get('tokenAmount', {}).get('amount', 0)) decimals = int(info.get('tokenAmount', {}).get('decimals', 0)) amount = amount_raw / (10 ** decimals) token_mint = info.get('mint') token = get_token_symbol(token_mint) break
Проверка программы:
python(program == 'spl-token' or program == 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')
program
: Это переменная, содержащая идентификатор программы, связанной с данной инструкцией в транзакции.'spl-token'
: Человекочитаемое имя программы SPL Token, используемой для работы с токенами в Solana.'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'
: Это публичный ключ (адрес) программы SPL Token в сети Solana Mainnet.Проверка типа инструкции:
pythoninstruction_type in ['transfer', 'transferChecked']
instruction_type
: Тип инструкции, описывающий, какое действие выполняется.'transfer'
и 'transferChecked'
: Типы инструкций, используемые для перевода токенов.
'transfer'
: Стандартный перевод токенов.'transferChecked'
: Перевод токенов с дополнительными проверками (например, проверка количества десятичных знаков).Комбинированное условие:
and
.'transfer'
или 'transferChecked'
.Получение адреса получателя:
pythonreceiver = info.get('destination')
info
) адрес получателя токенов.Получение количества токенов:
pythonamount_raw = int(info.get('tokenAmount', {}).get('amount', 0))
tokenAmount
: Поле, содержащее информацию о количестве токенов.amount
: Сырое значение количества токенов (в минимальных единицах, без учёта десятичных знаков).Получение количества десятичных знаков:
pythondecimals = int(info.get('tokenAmount', {}).get('decimals', 0))
decimals
: Количество десятичных знаков у токена.Вычисление реального количества токенов:
pythonamount = amount_raw / (10 ** decimals)
Получение адреса mint токена:
pythontoken_mint = info.get('mint')
mint
: Адрес mint токена, уникально идентифицирующий тип токена.Получение символа токена:
pythontoken = get_token_symbol(token_mint)
get_token_symbol
преобразует адрес mint в символ токена (например, 'USDC', 'WEN' и т.д.).Прерывание цикла:
pythonbreak
Общий алгоритм:
Ранее в коде вы уже обработали переводы SOL:
pythonif (program == 'system' or program == '11111111111111111111111111111111') and instruction_type == 'transfer': # Обработка перевода SOL
Теперь вы обрабатываете токеновые переводы с помощью данного условия.
Проверка двух вариантов программы:
'spl-token'
), а другие могут содержать её публичный ключ.Типы инструкций:
'transfer'
и 'transferChecked'
используются для токеновых переводов.Извлечение данных:
get
с указанием пустого словаря {}
по умолчанию предотвращает ошибки, если соответствующие поля отсутствуют.Если ваш бот обнаружит транзакцию с токеновым переводом, например, перевод 100 USDC от одного пользователя другому, этот блок кода позволит:
Данное условие является ключевым для обнаружения и обработки токеновых переводов в транзакциях Solana. Оно позволяет вашему боту отслеживать не только переводы SOL, но и любых других токенов, обеспечивая пользователей полной информацией о движениях средств на их кошельках.
Если у вас возникли дополнительные вопросы или нужна помощь с другим фрагментом кода, пожалуйста, дайте знать, и я с радостью помогу вам!
Я только что свапнул 172 WEN в 0.00001 SOL, но от бота получил такое сообщение:
Транзакция: Drtwa7aDfyDs5jsU6dwx1CEoJzFDJNnBB3YjcvQR7Te6aRJ3YYXMi3gBB5EFuyNpHaA3xs8Mbgabp8f912Ytqff
Отправитель: 76a3CxHZYeGoTJLXopkKqE53zMBwgNriDyDgPZ456gmq
Получатель: 3uXhfzhAZjRdFmr62r7ooKyGHkJqQMMkBZvKZsfCKPVo
Сумма: 0 None
Здравствуйте,
Я понимаю, что вы только что совершили свап 172 WEN в 0.00001 SOL, но получили от бота сообщение:
textТранзакция: Drtwa7aDfyDs5jsU6dwx1CEoJzFDJNnBB3YjcvQR7Te6aRJ3YYXMi3gBB5EFuyNpHaA3xs8Mbgabp8f912Ytqff Отправитель: 76a3CxHZYeGoTJLXopkKqE53zMBwgNriDyDgPZ456gmq Получатель: 3uXhfzhAZjRdFmr62r7ooKyGHkJqQMMkBZvKZsfCKPVo Сумма: 0 None
Причина проблемы:
Ваш бот отображает сумму как 0 None
потому что текущая логика парсинга транзакции не учитывает особенности свап-транзакций. Свап-транзакции сложнее, чем простые переводы, и не содержат явных инструкций transfer
или transferChecked
, которые ваш код ищет для определения суммы и токена.
Как исправить:
Чтобы корректно обрабатывать свап-транзакции и отображать информацию о полученных и отправленных токенах, необходимо использовать поля preTokenBalances
и postTokenBalances
из метаданных транзакции. Эти поля содержат информацию о балансах токенов до и после транзакции, что позволяет вычислить изменения в балансах и определить, какие токены были отправлены или получены.
Обновленный код функции get_sol_transaction
:
pythonimport aiohttp import logging # Предполагается, что функция get_token_symbol инициализирована и доступна # Также token_map должен быть загружен и доступен async def get_sol_transaction(tx_signature: str) -> dict | None: """ Получение информации о транзакции Solana по ее подписи. """ url = 'https://api.mainnet-beta.solana.com' headers = {'Content-Type': 'application/json'} payload = { "jsonrpc": "2.0", "id": 1, "method": "getTransaction", "params": [ tx_signature, { "encoding": "jsonParsed", "maxSupportedTransactionVersion": 0 } ] } async with aiohttp.ClientSession() as session: async with session.post(url, json=payload, headers=headers) as resp: response = await resp.json(content_type=None) if 'result' not in response or response['result'] is None: logging.error(f"Транзакция {tx_signature} не найдена или произошла ошибка.") return None result = response['result'] transaction = result['transaction'] message = transaction['message'] account_keys = message['accountKeys'] sender = account_keys[0]['pubkey'] meta = result.get('meta', {}) pre_token_balances = meta.get('preTokenBalances', []) post_token_balances = meta.get('postTokenBalances', []) # Инициализируем словарь для хранения изменений балансов balance_changes = {} # Обрабатываем балансы токенов for pre_balance, post_balance in zip(pre_token_balances, post_token_balances): mint = pre_balance.get('mint') owner = pre_balance.get('owner') pre_amount = int(pre_balance.get('uiTokenAmount', {}).get('amount', 0)) pre_decimals = int(pre_balance.get('uiTokenAmount', {}).get('decimals', 0)) post_amount = int(post_balance.get('uiTokenAmount', {}).get('amount', 0)) # Предполагаем, что decimals одинаковы в pre и post amount_change = (post_amount - pre_amount) / (10 ** pre_decimals) if amount_change != 0: token_symbol = get_token_symbol(mint) balance_changes[token_symbol] = balance_changes.get(token_symbol, 0) + amount_change # Также обрабатываем изменения SOL баланса pre_balances = meta.get('preBalances', []) post_balances = meta.get('postBalances', []) sol_changes = {} for idx, (pre_balance, post_balance) in enumerate(zip(pre_balances, post_balances)): amount_change = (post_balance - pre_balance) / 1e9 # Преобразуем лампорты в SOL if amount_change != 0: pubkey = account_keys[idx]['pubkey'] if pubkey == sender: sol_changes['SOL'] = sol_changes.get('SOL', 0) + amount_change # Объединяем изменения токенов и SOL all_changes = {**balance_changes, **sol_changes} if all_changes: amount_info = '' for token_symbol, amount in all_changes.items(): # Форматируем сумму amount_formatted = "{:.9f}".format(abs(amount)).rstrip('0').rstrip('.') action = 'Получено' if amount > 0 else 'Отправлено' amount_info += f"{action}: {amount_formatted} {token_symbol}\n" # Получатель может быть неочевиден в свап-транзакциях receiver = 'N/A' return { "transaction": tx_signature, "sender": sender, "receiver": receiver, "amount_info": amount_info, } else: logging.info(f"Не удалось определить изменения баланса в транзакции {tx_signature}") return None
Обновите функцию send_notifications
:
pythonasync def send_notifications(bot: Bot, timeout: int): while True: wallets = await PostgresRequests().get_all_wallets() for wallet in wallets: old_links = await get_links(wallet_hash=wallet.wallet_hash) try: new_links = set(await get_latest_sol_transactions(wallet.wallet_hash)) except Exception as e: logging.error(f"Failed to get transactions for wallet {wallet.wallet_hash} due to: {repr(e)}") continue diff = new_links - old_links for link in diff: transaction = await get_sol_transaction(link) if transaction is None: logging.error(f"Failed to retrieve transaction details for {link}. Skipping.") continue text = f"Транзакция: {transaction['transaction']} \n" text += f"Отправитель: {transaction['sender']} \n" text += f"Получатель: {transaction['receiver']} \n" text += transaction['amount_info'] await bot.send_message(chat_id=wallet.tg_id, text=text) await store_links(wallet_hash=wallet.wallet_hash, links=new_links) await asyncio.sleep(timeout)
Пояснение изменений:
Использование preTokenBalances
и postTokenBalances
: Это позволяет определить изменения в балансах токенов, включая свапы.
Обработка изменений SOL баланса: Мы также учитываем изменения баланса SOL через preBalances
и postBalances
.
Формирование amount_info
: Собираем информацию о всех изменениях балансов токенов и SOL, чтобы отобразить их в сообщении.
Корректное отображение суммы и токена: Теперь бот должен отображать правильные суммы и названия токенов, даже для свап-транзакций.
Пример ожидаемого сообщения от бота:
textТранзакция: Drtwa7aDfyDs5jsU6dwx1CEoJzFDJNnBB3YjcvQR7Te6aRJ3YYXMi3gBB5EFuyNpHaA3xs8Mbgabp8f912Ytqff Отправитель: 76a3CxHZYeGoTJLXopkKqE53zMBwgNriDyDgPZ456gmq Получатель: N/A Отправлено: 172 WEN Получено: 0.00001 SOL
Дополнительные рекомендации:
Улучшение определения получателя: В свап-транзакциях получатель может быть неочевиден. Если это важно, можно попытаться определить получателя на основе инструкций или метаданных.
Обработка отрицательных значений: Убедитесь, что отображаете корректное действие ("Получено" или "Отправлено") в зависимости от знака изменения баланса.
Тестирование: Протестируйте бота с разными типами транзакций, включая свапы и обычные переводы, чтобы убедиться, что информация отображается корректно.
Заключение:
Причина проблемы: Текущая реализация не учитывает особенности свап-транзакций, поэтому бот не может правильно определить сумму и токен.
Решение: Использование балансов до и после транзакции для вычисления изменений и формирования корректной информации.
Если у вас возникнут дополнительные вопросы или потребуется помощь с реализацией этих изменений, пожалуйста, дайте знать. Я буду рад помочь вам настроить бота для корректного отображения информации о всех транзакциях.
Я свапнул 25.68584 WEN на 0.000015 SOL, но во входящем сообщении мало того что получил очень странный ответ, так еще и ни одна из сумм по итогу не правильная
Транзакция: 5iYVXEQ7bFQcveU36uwbXkXtMTMqHas3iG2PD8B6LR4dk54sy36Y3twZAVErYFJN5QHcdno1YajTf4yd9GmYjwRy
Отправитель: 76a3CxHZYeGoTJLXopkKqE53zMBwgNriDyDgPZ456gmq
Получатель: N/A
Отправлено: 0 Unknown Token
Отправлено: 0.00039 SOL
Здравствуйте,
Я понимаю вашу проблему: после свапа 25.68584 WEN на 0.000015 SOL ваш бот прислал сообщение с некорректными суммами:
textТранзакция: 5iYVXEQ7bFQcveU36uwbXkXtMTMqHas3iG2PD8B6LR4dk54sy36Y3twZAVErYFJN5QHcdno1YajTf4yd9GmYjwRy Отправитель: 76a3CxHZYeGoTJLXopkKqE53zMBwgNriDyDgPZ456gmq Получатель: N/A Отправлено: 0 Unknown Token Отправлено: 0.00039 SOL
Проблема:
Чтобы корректно вычислить изменения баланса для вашего кошелька, нам нужно:
Фильтровать изменения баланса по вашему кошельку. Убедиться, что мы учитываем только те изменения, которые относятся к вашему адресу.
Исправить логику расчёта балансов. Убедиться, что мы правильно сопоставляем preTokenBalances
и postTokenBalances
и учитываем accountIndex
для сопоставления с вашим кошельком.
Обработать все балансы. Включая SOL и токены, а также корректно рассчитывать изменения, учитывая возможные комиссии.
get_sol_transaction
:pythonasync def get_sol_transaction(tx_signature: str, user_wallet: str) -> dict | None: """ Получение информации о транзакции Solana по ее подписи. """ url = 'https://api.mainnet-beta.solana.com' headers = {'Content-Type': 'application/json'} payload = { "jsonrpc": "2.0", "id": 1, "method": "getTransaction", "params": [ tx_signature, { "encoding": "jsonParsed", "maxSupportedTransactionVersion": 0 } ] } async with aiohttp.ClientSession() as session: async with session.post(url, json=payload, headers=headers) as resp: response = await resp.json(content_type=None) if 'result' not in response or response['result'] is None: logging.error(f"Транзакция {tx_signature} не найдена или произошла ошибка.") return None result = response['result'] transaction = result['transaction'] message = transaction['message'] account_keys = message['accountKeys'] account_pubkeys = [account['pubkey'] for account in account_keys] meta = result.get('meta', {}) pre_token_balances = meta.get('preTokenBalances', []) post_token_balances = meta.get('postTokenBalances', []) balance_changes = {} # Обрабатываем токеновые балансы token_balances = {} for pre_balance in pre_token_balances: account_index = pre_balance.get('accountIndex') pubkey = account_pubkeys[account_index] if pubkey != user_wallet: continue # Пропускаем чужие кошельки mint = pre_balance.get('mint') decimals = int(pre_balance.get('uiTokenAmount', {}).get('decimals', 0)) pre_amount = int(pre_balance.get('uiTokenAmount', {}).get('amount', 0)) token_balances[mint] = {'pre_amount': pre_amount, 'decimals': decimals} for post_balance in post_token_balances: account_index = post_balance.get('accountIndex') pubkey = account_pubkeys[account_index] if pubkey != user_wallet: continue mint = post_balance.get('mint') decimals = int(post_balance.get('uiTokenAmount', {}).get('decimals', 0)) post_amount = int(post_balance.get('uiTokenAmount', {}).get('amount', 0)) pre_amount = token_balances.get(mint, {}).get('pre_amount', 0) amount_change = (post_amount - pre_amount) / (10 ** decimals) if amount_change != 0: token_symbol = get_token_symbol(mint) balance_changes[token_symbol] = balance_changes.get(token_symbol, 0) + amount_change # Обрабатываем изменения SOL баланса pre_balances = meta.get('preBalances', []) post_balances = meta.get('postBalances', []) for idx, (pre_balance, post_balance) in enumerate(zip(pre_balances, post_balances)): pubkey = account_pubkeys[idx] if pubkey != user_wallet: continue amount_change = (post_balance - pre_balance) / 1e9 # Лампорты в SOL if amount_change != 0: balance_changes['SOL'] = balance_changes.get('SOL', 0) + amount_change if balance_changes: amount_info = '' for token_symbol, amount in balance_changes.items(): # Форматируем сумму amount_formatted = "{:.9f}".format(abs(amount)).rstrip('0').rstrip('.') action = 'Получено' if amount > 0 else 'Отправлено' amount_info += f"{action}: {amount_formatted} {token_symbol}\n" return { "transaction": tx_signature, "sender": user_wallet, "receiver": 'N/A', "amount_info": amount_info, } else: logging.info(f"Не удалось определить изменения баланса в транзакции {tx_signature}") return None
Объяснение изменений:
Передача адреса пользователя: Теперь функция принимает параметр user_wallet
, чтобы фильтровать изменения балансов по вашему кошельку.
Фильтрация по accountIndex
: Используем accountIndex
из preTokenBalances
и postTokenBalances
для получения соответствующего публичного ключа и сравниваем с user_wallet
.
Расчёт изменений балансов: Вычисляем изменения балансов только для вашего кошелька, что предотвращает включение данных других участников транзакции.
Корректное отображение сумм и токенов: Теперь бот должен правильно отображать полученные и отправленные суммы, соответствующие вашему кошельку.
send_notifications
:Необходимо передать wallet.wallet_hash
в функцию get_sol_transaction
:
pythontransaction = await get_sol_transaction(link, wallet.wallet_hash)
Тестирование с вашей транзакцией:
Используя обновлённый код и вашу транзакцию 5iYVXEQ7bFQcveU36uwbXkXtMTMqHas3iG2PD8B6LR4dk54sy36Y3twZAVErYFJN5QHcdno1YajTf4yd9GmYjwRy
, бот должен отобразить:
textТранзакция: 5iYVXEQ7bFQcveU36uwbXkXtMTMqHas3iG2PD8B6LR4dk54sy36Y3twZAVErYFJN5QHcdno1YajTf4yd9GmYjwRy Отправитель: 76a3CxHZYeGoTJLXopkKqE53zMBwgNriDyDgPZ456gmq Получатель: N/A Отправлено: 25.68584 WEN Получено: 0.000015 SOL
Обратите внимание: Суммы могут немного отличаться из-за комиссий.
Проверка обработки комиссий:
Обработка токенов с неизвестными символами:
get_token_symbol
не находит символ токена, убедитесь, что функция возвращает осмысленное значение, например, "Unknown Token"
или адрес mint.Улучшите функцию get_token_symbol
:
pythondef get_token_symbol(mint_address): symbol = token_map.get(mint_address) if symbol: return symbol else: # Логирование неизвестного токена logging.warning(f"Неизвестный токен с mint адресом: {mint_address}") return mint_address # Или 'Unknown Token'
Обработка небольших изменений балансов:
Добавьте информацию о комиссии:
Вы можете вывести информацию о комиссионных расходах, используя поле fee
из метаданных транзакции:
pythonfee = meta.get('fee', 0) / 1e9 # Преобразуем из лампортов в SOL if fee > 0: amount_info += f"Комиссия: {fee} SOL\n"
Причина проблемы: Код не учитывал ваш конкретный кошелёк при расчёте изменений балансов, из-за чего суммы отображались некорректно.
Решение: Обновить функцию get_sol_transaction
, чтобы она фильтровала изменения балансов по вашему кошельку и корректно вычисляла суммы.
Результат: Бот теперь будет правильно отображать суммы отправленных и полученных токенов и SOL, соответствующих вашим транзакциям.
Если после внесения этих изменений у вас всё ещё возникают проблемы или есть дополнительные вопросы, пожалуйста, дайте мне знать, и я с радостью помогу вам дальше!
Какие конкретно изменения мне нужно внести в send_notifications? сейчас она выглядит вот так:
async def send_notifications(bot: Bot, timeout: int):
while True:
wallets = await PostgresRequests().get_all_wallets()
for wallet in wallets:
old_links = await get_links(wallet_hash=wallet.wallet_hash)
try:
new_links = set(await get_latest_sol_transactions(wallet.wallet_hash))
except Exception as e:
logging.error(f"Failed to get transactions for wallet {wallet.wallet_hash} due to: {repr(e)}")
continue
diff = new_links - old_links
for link in diff:
transaction = await get_sol_transaction(link)
if transaction is None:
logging.error(f"Failed to retrieve transaction details for {link}. Skipping.")
continue
text = f"Транзакция: {transaction['transaction']} \n"
text += f"Отправитель: {transaction['sender']} \n"
text += f"Получатель: {transaction['receiver']} \n"
text += transaction['amount_info']
await bot.send_message(chat_id=wallet.tg_id, text=text)
await store_links(wallet_hash=wallet.wallet_hash, links=new_links)
await asyncio.sleep(timeout)
Здравствуйте,
Вам нужно внести небольшое изменение в функцию send_notifications
, чтобы она передавала адрес кошелька пользователя в функцию get_sol_transaction
, которая теперь требует два аргумента: подпись транзакции (tx_signature
) и адрес кошелька пользователя (user_wallet
).
Вот конкретные изменения, которые вам нужно внести:
Изменить вызов get_sol_transaction
внутри цикла по транзакциям:
Было:
pythontransaction = await get_sol_transaction(link)
Стало:
pythontransaction = await get_sol_transaction(link, wallet.wallet_hash)
Полный обновлённый код функции send_notifications
:
pythonasync def send_notifications(bot: Bot, timeout: int): while True: wallets = await PostgresRequests().get_all_wallets() for wallet in wallets: old_links = await get_links(wallet_hash=wallet.wallet_hash) try: new_links = set(await get_latest_sol_transactions(wallet.wallet_hash)) except Exception as e: logging.error(f"Failed to get transactions for wallet {wallet.wallet_hash} due to: {repr(e)}") continue diff = new_links - old_links for link in diff: # Передаём wallet.wallet_hash в функцию get_sol_transaction transaction = await get_sol_transaction(link, wallet.wallet_hash) if transaction is None: logging.error(f"Failed to retrieve transaction details for {link}. Skipping.") continue text = f"Транзакция: {transaction['transaction']} \n" text += f"Отправитель: {transaction['sender']} \n" text += f"Получатель: {transaction['receiver']} \n" text += transaction['amount_info'] await bot.send_message(chat_id=wallet.tg_id, text=text) await store_links(wallet_hash=wallet.wallet_hash, links=new_links) await asyncio.sleep(timeout)
Пояснение изменений:
Передача адреса кошелька:
get_sol_transaction
ожидает два аргумента:
tx_signature
: подпись транзакции (в вашем коде это link
).user_wallet
: адрес кошелька пользователя (в вашем коде это wallet.wallet_hash
).wallet.wallet_hash
как второй аргумент.Почему это важно:
get_sol_transaction
для корректного вычисления изменений балансов, относящихся только к конкретному кошельку пользователя.wallet.wallet_hash
, функция может фильтровать изменения балансов и отображать только релевантную информацию для каждого пользователя.Убедитесь, что ваша функция get_sol_transaction
обновлена и выглядит следующим образом:
pythonasync def get_sol_transaction(tx_signature: str, user_wallet: str) -> dict | None: # Ваш обновлённый код функции здесь...
Дополнительные рекомендации:
Проверьте, что все необходимые импорты и переменные определены:
wallet.wallet_hash
содержит правильный адрес кошелька в виде строки.wallet.tg_id
содержит идентификатор чата Telegram для отправки сообщений.Тестирование:
Логирование:
Пример ожидаемого результата:
После внесения изменений и выполнения транзакции бот должен отправить сообщение, которое точно отражает изменения баланса для конкретного пользователя.
Пример сообщения:
textТранзакция: Drtwa7aDfyDs5jsU6dwx1CEoJzFDJNnBB3YjcvQR7Te6aRJ3YYXMi3gBB5EFuyNpHaA3xs8Mbgabp8f912Ytqff Отправитель: 76a3CxHZYeGoTJLXopkKqE53zMBwgNriDyDgPZ456gmq Получатель: N/A Отправлено: 25.68584 WEN Получено: 0.000015 SOL
Если у вас возникают проблемы после внесения изменений, убедитесь, что:
get_sol_transaction
корректно фильтрует изменения балансов по адресу пользователя.get_sol_transaction
в send_notifications
передаёт правильные аргументы.async def
) вызываются с использованием await
.Заключение:
wallet.wallet_hash
в вызов get_sol_transaction
внутри функции send_notifications
.get_sol_transaction
получала адрес кошелька пользователя и могла корректно вычислять изменения балансов, относящиеся к этому кошельку.Если у вас есть дополнительные вопросы или нужна помощь с другими частями кода, пожалуйста, дайте знать, и я с радостью помогу вам!
Перепиши метод парсинга транзакций так, чтобы он парсил все те же данные через API Solscan, используя подписи транзакций. Также распиши какие изменения надо сделать в других функциях по типу send_notifications
Здравствуйте,
Вы хотите переписать метод парсинга транзакций так, чтобы он использовал API Solscan для получения деталей транзакций по их подписи. Также вы просите объяснить, какие изменения нужно внести в другие функции, такие как send_notifications
.
Solscan предоставляет публичный API, который позволяет получать детали транзакций по их подписи. Нам нужно переписать функцию get_sol_transaction
, чтобы она использовала этот API.
Шаги:
Изучить документацию API Solscan
API для получения деталей транзакции:
GET https://public-api.solscan.io/transaction/{txHash}
Где {txHash}
— это подпись транзакции.
Обновить функцию get_sol_transaction
Перепишем функцию так, чтобы она делала GET-запрос к API Solscan и парсила необходимые данные.
get_sol_transaction
pythonimport aiohttp import logging # Предполагается, что функция get_token_symbol и token_map уже определены async def get_sol_transaction(tx_signature: str, user_wallet: str) -> dict | None: """ Получение информации о транзакции через API Solscan по ее подписи. :param tx_signature: Подпись транзакции в виде строки. :param user_wallet: Адрес кошелька пользователя. :return: Словарь с информацией о транзакции или None в случае ошибки. """ url = f'https://public-api.solscan.io/transaction/{tx_signature}' headers = { 'accept': 'application/json' } try: async with aiohttp.ClientSession() as session: async with session.get(url, headers=headers) as resp: if resp.status != 200: logging.error(f"Не удалось получить данные транзакции {tx_signature}. Код ответа: {resp.status}") return None response = await resp.json() # Проверяем наличие необходимых данных if not response: logging.error(f"Пустой ответ для транзакции {tx_signature}") return None # Инициализация переменных sender = user_wallet receiver = 'N/A' amount_info = '' # Получаем список инструкций instructions = response.get('parsedInstruction', []) if not instructions: logging.info(f"Нет инструкций в транзакции {tx_signature}") return None # Обрабатываем инструкции for instruction in instructions: instruction_type = instruction.get('type') info = instruction.get('data', {}) if instruction_type == 'transfer': # Обработка перевода SOL source = info.get('source') destination = info.get('destination') lamports = int(info.get('lamports', 0)) amount = lamports / 1e9 # Преобразуем лампорты в SOL token = 'SOL' if source == user_wallet or destination == user_wallet: amount_formatted = "{:.9f}".format(abs(amount)).rstrip('0').rstrip('.') action = 'Получено' if destination == user_wallet else 'Отправлено' amount_info += f"{action}: {amount_formatted} {token}\n" elif instruction_type in ['transferChecked', 'transfer']: # Обработка перевода токенов source = info.get('source') destination = info.get('destination') mint = info.get('mint') decimals = int(info.get('tokenAmount', {}).get('decimals', 0)) amount_raw = int(info.get('tokenAmount', {}).get('amount', 0)) amount = amount_raw / (10 ** decimals) token = get_token_symbol(mint) if source == user_wallet or destination == user_wallet: amount_formatted = "{:.9f}".format(abs(amount)).rstrip('0').rstrip('.') action = 'Получено' if destination == user_wallet else 'Отправлено' amount_info += f"{action}: {amount_formatted} {token}\n" # Если нет релевантных изменений баланса if not amount_info: logging.info(f"Нет изменений баланса для транзакции {tx_signature} и кошелька {user_wallet}") return None return { "transaction": tx_signature, "sender": sender, "receiver": receiver, "amount_info": amount_info, } except Exception as e: logging.error(f"Произошла ошибка при получении транзакции {tx_signature}: {e}") return None
Пояснения:
Запрос к API Solscan:
aiohttp
для выполнения асинхронного GET-запроса к API Solscan.Парсинг ответа:
parsedInstruction
.source
или destination
).Обработка ошибок:
send_notifications
Поскольку мы изменили реализацию функции get_sol_transaction
, но её сигнатура осталась прежней, изменения в send_notifications
минимальны.
Обновлённая функция send_notifications
:
pythonasync def send_notifications(bot: Bot, timeout: int): while True: wallets = await PostgresRequests().get_all_wallets() for wallet in wallets: old_links = await get_links(wallet_hash=wallet.wallet_hash) try: new_links = set(await get_latest_sol_transactions(wallet.wallet_hash)) except Exception as e: logging.error(f"Не удалось получить транзакции для кошелька {wallet.wallet_hash}: {repr(e)}") continue diff = new_links - old_links for link in diff: # Передаём wallet.wallet_hash в функцию get_sol_transaction transaction = await get_sol_transaction(link, wallet.wallet_hash) if transaction is None: logging.error(f"Не удалось получить детали транзакции для {link}. Пропуск.") continue text = f"Транзакция: {transaction['transaction']} \n" text += f"Отправитель: {transaction['sender']} \n" text += f"Получатель: {transaction['receiver']} \n" text += transaction['amount_info'] await bot.send_message(chat_id=wallet.tg_id, text=text) await store_links(wallet_hash=wallet.wallet_hash, links=new_links) await asyncio.sleep(timeout)
Пояснения:
Передача адреса кошелька:
get_sol_transaction
остался без изменений, так как сигнатура функции такая же.wallet.wallet_hash
содержит корректный адрес кошелька пользователя.Обработка результата:
send_notifications
остаётся прежней.1. Обновить функцию get_token_symbol
, если необходимо:
Убедитесь, что функция get_token_symbol
инициализирована и может правильно преобразовывать адреса токенов в их символы.
pythondef get_token_symbol(mint_address): symbol = token_map.get(mint_address) if symbol: return symbol else: logging.warning(f"Неизвестный токен с mint адресом: {mint_address}") return mint_address # Или 'Unknown Token'
2. Загрузить список токенов (token_map):
Убедитесь, что token_map
загружен и доступен для функции get_token_symbol
.
3. Тестирование:
Проверка с различными транзакциями:
get_sol_transaction
с разными типами транзакций: переводы SOL, токеновые переводы, свапы и т.д.Логирование:
4. Обработка специальных случаев:
Свапы и сложные транзакции:
5. Обработка комиссий:
Переписали функцию get_sol_transaction
:
Изменения в других функциях минимальны:
send_notifications
остаётся практически без изменений.Дополнительные шаги:
Пример использования функции get_sol_transaction
:
python# Пример вызова функции с тестовой транзакцией и кошельком пользователя transaction = await get_sol_transaction('TxSignatureHere', 'UserWalletAddressHere') if transaction: print(transaction) else: print("Не удалось получить детали транзакции.")
Пример ожидаемого результата:
text{ "transaction": "TxSignatureHere", "sender": "UserWalletAddressHere", "receiver": "N/A", "amount_info": "Получено: 0.000015 SOL\nОтправлено: 25.68584 WEN\n" }
Если у вас возникнут вопросы или потребуется дополнительная помощь с реализацией или отладкой кода, пожалуйста, дайте знать, и я с радостью помогу вам дальше!
INFO:sqlalchemy.engine.Engine:ROLLBACK
INFO:httpx:HTTP Request: POST https://api.mainnet-beta.solana.com "HTTP/1.1 200 OK"
ERROR:root:Не удалось получить данные транзакции 5RYzuta7rDCyAF8a4gwHbG1bSS1e9ZYp9o9HU4uHG3v4Ck8AiCnpzYz45Buf2mUn5KimNm8HpqSJSEye6FbVedrS. Код ответа: 404
ERROR:root:Не удалось получить детали транзакции для 5RYzuta7rDCyAF8a4gwHbG1bSS1e9ZYp9o9HU4uHG3v4Ck8AiCnpzYz45Buf2mUn5KimNm8HpqSJSEye6FbVedrS. Пропуск.
INFO:httpx:HTTP Request: POST https://api.mainnet-beta.solana.com "HTTP/1.1 200 OK"
Здравствуйте,
Я вижу, что вы столкнулись со следующей ошибкой:
textERROR:root:Не удалось получить данные транзакции 5RYzuta7rDCyAF8a4gwHbG1bSS1e9ZYp9o9HU4uHG3v4Ck8AiCnpzYz45Buf2mUn5KimNm8HpqSJSEye6FbVedrS. Код ответа: 404 ERROR:root:Не удалось получить детали транзакции для 5RYzuta7rDCyAF8a4gwHbG1bSS1e9ZYp9o9HU4uHG3v4Ck8AiCnpzYz45Buf2mUn5KimNm8HpqSJSEye6FbVedrS. Пропуск.
Понимание проблемы:
https://api.mainnet-beta.solana.com
указывает на то, что часть вашего кода всё ещё использует Solana RPC API.Возможные причины:
Транзакция ещё не проиндексирована Solscan:
Неправильный хеш транзакции:
API Solscan требует API-ключ:
Ограничения по количеству запросов:
Изменения в API или устаревший эндпоинт:
1. Получение API-ключа для Solscan:
2. Обновление функции get_sol_transaction
с использованием API-ключа:
pythonimport aiohttp import logging async def get_sol_transaction(tx_signature: str, user_wallet: str) -> dict | None: """ Получение информации о транзакции через API Solscan по ее подписи. """ url = f'https://public-api.solscan.io/transaction/{tx_signature}' headers = { 'accept': 'application/json', 'token': 'ВАШ_API_КЛЮЧ' # Замените на ваш реальный API-ключ } try: async with aiohttp.ClientSession() as session: async with session.get(url, headers=headers) as resp: if resp.status != 200: logging.error(f"Не удалось получить данные транзакции {tx_signature}. Код ответа: {resp.status}") return None response = await resp.json() if not response: logging.error(f"Пустой ответ для транзакции {tx_signature}") return None # Далее обрабатывайте response как раньше # ... except Exception as e: logging.error(f"Произошла ошибка при получении транзакции {tx_signature}: {e}") return None
3. Обновление других функций, использующих Solscan API:
Если у вас есть другие функции, обращающиеся к API Solscan (например, get_latest_sol_transactions
), убедитесь, что они также используют API-ключ.
Пример обновленной функции get_latest_sol_transactions
:
pythonasync def get_latest_sol_transactions(wallet_address: str) -> List[str]: """ Получение последних транзакций кошелька через API Solscan. """ url = f'https://public-api.solscan.io/account/transactions?address={wallet_address}&limit=10' headers = { 'accept': 'application/json', 'token': 'ВАШ_API_КЛЮЧ' # Замените на ваш реальный API-ключ } try: async with aiohttp.ClientSession() as session: async with session.get(url, headers=headers) as resp: if resp.status != 200: logging.error(f"Не удалось получить транзакции для кошелька {wallet_address}. Код ответа: {resp.status}") return [] response = await resp.json() tx_hashes = [tx.get('txHash') for tx in response] return tx_hashes except Exception as e: logging.error(f"Произошла ошибка при получении транзакций для кошелька {wallet_address}: {e}") return []
4. Обработка ошибок и задержек:
5. Проверка хеша транзакции:
https://solscan.io/tx/5RYzuta7...
и проверьте, отображается ли транзакция.6. Обновление логики парсинга:
7. Обновление зависимостей и документации:
aiohttp
, asyncio
и др.) до последних версий, если необходимо.https://api.mainnet-beta.solana.com
:Причина:
get_latest_sol_transactions
может использовать Solana RPC API.Решение:
Пример использования Solana RPC API в get_latest_sol_transactions
:
Если вы хотите оставить эту функцию без изменений, можете оставить запросы к https://api.mainnet-beta.solana.com
.
1. Ограничения и лимиты API Solscan:
2. Логирование и отладка:
3. Обработка других кодов ответа:
4. Альтернативные решения:
Если у вас есть дополнительные вопросы или вам нужна помощь с другими аспектами вашего кода, пожалуйста, дайте знать, и я с радостью помогу вам!
Верни все так, чтобы он использовал mainnet соланы и в случае обнаружения транзакции вне токена соланы писал об этом в уведомлении, при этом выдавая транзакции только касающиеся соланы. Например, чтобы при свапе токена, он просто писал сколько SOL ушло на свап, при этом не затрагивая получение этих же денег в другом токене. Также и наоборот, чтобы когда пользователь трейдил любой другой токен в солану, ему приходило уведомление только о пополнении в SOL, не затрагивая начальный токен, с которого он менял. Естественно, сумма в SOL должна отображаться корректно
Здравствуйте,
Понимаю, что вы хотите вернуть код к использованию Solana Mainnet API и настроить его так, чтобы бот отправлял уведомления только о транзакциях с SOL. При этом, когда происходит свап токена, бот должен сообщать только о том, сколько SOL было отправлено или получено, не затрагивая другие токены.
Вернуть использование Solana Mainnet API: Переписать функцию get_sol_transaction
для использования getTransaction
метода Solana RPC API.
Обновить логику парсинга: Изменить функцию так, чтобы она учитывала только транзакции, связанные с SOL.
Обрабатывать только SOL-транзакции: В случае свапа токенов, бот будет сообщать только о том, сколько SOL было потрачено или получено.
Обновить другие функции, если необходимо: Убедиться, что send_notifications
и другие функции корректно работают с обновлённой логикой.
Мы будем использовать метод getTransaction
из Solana RPC API.
Функция get_sol_transaction
:
pythonimport aiohttp import logging async def get_sol_transaction(tx_signature: str, user_wallet: str) -> dict | None: """ Получение информации о транзакции Solana по ее подписи. """ url = 'https://api.mainnet-beta.solana.com' headers = {'Content-Type': 'application/json'} payload = { "jsonrpc": "2.0", "id": 1, "method": "getTransaction", "params": [ tx_signature, { "encoding": "jsonParsed", "maxSupportedTransactionVersion": 0, "commitment": "confirmed" } ] } try: async with aiohttp.ClientSession() as session: async with session.post(url, json=payload, headers=headers) as resp: response = await resp.json() if 'result' not in response or response['result'] is None: logging.error(f"Транзакция {tx_signature} не найдена или произошла ошибка.") return None result = response['result'] transaction = result['transaction'] message = transaction['message'] account_keys = message['accountKeys'] account_pubkeys = [account['pubkey'] for account in account_keys] meta = result.get('meta', {}) pre_balances = meta.get('preBalances', []) post_balances = meta.get('postBalances', []) # Ищем изменения баланса SOL для кошелька пользователя sol_change = 0 for idx, (pre_balance, post_balance) in enumerate(zip(pre_balances, post_balances)): pubkey = account_pubkeys[idx] if pubkey != user_wallet: continue amount_change = (post_balance - pre_balance) / 1e9 # Преобразуем лампорты в SOL sol_change += amount_change if sol_change == 0: logging.info(f"Нет изменений баланса SOL для транзакции {tx_signature} и кошелька {user_wallet}") return None # Определяем действие (получено или отправлено) action = 'Получено' if sol_change > 0 else 'Отправлено' amount_formatted = "{:.9f}".format(abs(sol_change)).rstrip('0').rstrip('.') sender = user_wallet receiver = 'N/A' amount_info = f"{action}: {amount_formatted} SOL\n" return { "transaction": tx_signature, "sender": sender, "receiver": receiver, "amount_info": amount_info, } except Exception as e: logging.error(f"Произошла ошибка при получении транзакции {tx_signature}: {e}") return None
Пояснения:
Использование Solana Mainnet API:
https://api.mainnet-beta.solana.com
с методом getTransaction
.encoding: jsonParsed
для удобного парсинга.Парсинг SOL-транзакций:
preBalances
и postBalances
, которые содержат балансы в лампортах (1 SOL = 1e9 лампортов).user_wallet
.Игнорирование токеновых транзакций:
preTokenBalances
и postTokenBalances
.Формирование уведомления:
send_notifications
Функция send_notifications
остаётся практически без изменений, но убедитесь, что она корректно передаёт wallet.wallet_hash
в функцию get_sol_transaction
.
pythonasync def send_notifications(bot: Bot, timeout: int): while True: wallets = await PostgresRequests().get_all_wallets() for wallet in wallets: old_links = await get_links(wallet_hash=wallet.wallet_hash) try: new_links = set(await get_latest_sol_transactions(wallet.wallet_hash)) except Exception as e: logging.error(f"Не удалось получить транзакции для кошелька {wallet.wallet_hash}: {repr(e)}") continue diff = new_links - old_links for link in diff: # Передаём wallet.wallet_hash в функцию get_sol_transaction transaction = await get_sol_transaction(link, wallet.wallet_hash) if transaction is None: logging.info(f"Нет изменений SOL для транзакции {link}. Пропуск.") continue text = f"Транзакция: {transaction['transaction']} \n" text += f"Отправитель: {transaction['sender']} \n" text += f"Получатель: {transaction['receiver']} \n" text += transaction['amount_info'] await bot.send_message(chat_id=wallet.tg_id, text=text) await store_links(wallet_hash=wallet.wallet_hash, links=new_links) await asyncio.sleep(timeout)
Пояснения:
Логирование:
ERROR
на INFO
, так как это не является ошибкой.Уведомления:
get_latest_sol_transactions
Функция get_latest_sol_transactions
может остаться без изменений, если она корректно возвращает последние транзакции пользователя.
Пример:
pythonasync def get_latest_sol_transactions(wallet_address: str) -> List[str]: url = 'https://api.mainnet-beta.solana.com' headers = {'Content-Type': 'application/json'} payload = { "jsonrpc": "2.0", "id": 1, "method": "getSignaturesForAddress", "params": [ wallet_address, { "limit": 10, "commitment": "confirmed" } ] } try: async with aiohttp.ClientSession() as session: async with session.post(url, json=payload, headers=headers) as resp: response = await resp.json() if 'result' not in response or response['result'] is None: logging.error(f"Не удалось получить транзакции для кошелька {wallet_address}.") return [] signatures = [entry['signature'] for entry in response['result']] return signatures except Exception as e: logging.error(f"Произошла ошибка при получении транзакций для кошелька {wallet_address}: {e}") return []
Свап SOL на токен:
Свап токена на SOL:
Пример сообщения от бота:
textТранзакция: Drtwa7aDfyDs5jsU6dwx1CEoJzFDJNnBB3YjcvQR7Te6aRJ3YYXMi3gBB5EFuyNpHaA3xs8Mbgabp8f912Ytqff Отправитель: ВашКошелек Получатель: N/A Отправлено: 0.000015 SOL
1. Обработка комиссий:
2. Улучшение определения получателя:
3. Логирование:
Использование Solana Mainnet API:
Уведомления только о SOL:
Минимальные изменения в других функциях:
send_notifications
для передачи адреса кошелька пользователя.Тестирование:
Проблемы с отображением сумм:
sol_change
в функции get_sol_transaction
.Отсутствие уведомлений:
get_latest_sol_transactions
корректно получает последние транзакции.Ошибки в логах:
Если вам потребуется дополнительная помощь с настройкой или отладкой кода, пожалуйста, дайте мне знать, и я с радостью помогу вам!
Что делать с функциями load_token_list и get_token_symbol?
Здравствуйте,
Поскольку вы решили изменить логику бота так, чтобы он обрабатывал только транзакции, связанные с SOL, и игнорировал все остальные токены, функции load_token_list
и get_token_symbol
больше не нужны в вашем коде. Эти функции изначально предназначались для отображения символов токенов при обработке транзакций с различными токенами.
load_token_list
и get_token_symbol
1. Удалить ненужные функции и связанные с ними части кода
Удалите функции:
load_token_list
get_token_symbol
Удалите загрузку списка токенов:
load_token_list()
при запуске приложения, его тоже можно удалить.Удалите переменные:
token_map
, их можно удалить.2. Проверить оставшийся код на наличие ссылок на эти функции
В функции get_sol_transaction
:
Убедитесь, что в обновлённой функции нет ссылок на get_token_symbol
или использование переменной token
.
В текущей реализации вы обрабатываете только изменения баланса SOL, поэтому переменная token
не используется.
В функции send_notifications
:
Проверьте, что формирование сообщения не зависит от переменной token
.
Ваше сообщение должно отображать только информацию о SOL.
3. Обновить формирование сообщения
Обновите amount_info
в функции get_sol_transaction
:
amount_info
формируется корректно и не использует переменную token
.pythonamount_info = f"{action}: {amount_formatted} SOL\n"
Обновите формирование текста сообщения в send_notifications
:
token
или другие связанные с токенами данные.1. Тестируйте бота с различными транзакциями
Отправка SOL на другой адрес:
Получение SOL от другого адреса:
Свап SOL на токен:
Свап токена на SOL:
2. Убедитесь, что бот игнорирует транзакции без изменений SOL
Удаление функций: Поскольку вы больше не обрабатываете транзакции с токенами, функции load_token_list
и get_token_symbol
больше не нужны и могут быть удалены.
Упрощение кода: Удаление этих функций упрощает код и снижает вероятность возникновения ошибок, связанных с обработкой токенов.
Фокус на SOL-транзакциях: Бот теперь будет более точным и эффективным при обработке только SOL-транзакций, соответствуя вашим требованиям.
1. Обновите документацию и комментарии
Обновите комментарии в коде, чтобы они отражали текущую логику и не ссылались на удалённые функции.
Если у вас есть документация или README-файл, убедитесь, что они актуальны.
2. Очистите импортированные модули
Если у вас есть импортированные модули, которые использовались только в load_token_list
и get_token_symbol
(например, requests
для загрузки списка токенов), их можно удалить.
python# Пример удаляемого импорта import requests
3. Улучшите логирование
Поскольку ваш код стал проще, вы можете улучшить логирование для SOL-транзакций.
Добавьте логирование успешных транзакций и потенциальных ошибок.
4. Подготовьтесь к будущим изменениям
load_token_list
и get_token_symbol
для возможного использования.Функция get_sol_transaction
:
pythonasync def get_sol_transaction(tx_signature: str, user_wallet: str) -> dict | None: """ Получение информации о транзакции Solana по ее подписи. """ # ... [код функции без использования load_token_list и get_token_symbol] ...
Функция send_notifications
:
pythonasync def send_notifications(bot: Bot, timeout: int): # ... [код функции без изменений, но убедитесь, что нет ссылок на токены] ...
Удаление функций load_token_list
и get_token_symbol
соответствует вашей текущей задаче: бот должен обрабатывать только SOL-транзакции и игнорировать другие токены. Это упрощает код и делает его более понятным и поддерживаемым.
Если у вас возникнут дополнительные вопросы или потребуется помощь с другими аспектами вашего кода, пожалуйста, дайте мне знать, и я с радостью помогу вам!
Что делать с функциями load_token_list и get_token_symbol?
Здравствуйте,
Поскольку ваш бот теперь настроен на обработку только транзакций, связанных с SOL, и игнорирует все другие токены, функции load_token_list
и get_token_symbol
больше не нужны в вашем коде. Эти функции использовались для загрузки списка токенов и преобразования адресов mint токенов в их символы при обработке транзакций с разными токенами.
Вот что вам следует сделать с этими функциями:
load_token_list
и get_token_symbol
Удалите определения функций:
Удалите полный код функций load_token_list
и get_token_symbol
из вашего проекта.
python# Удалите этот код def load_token_list(): # Код функции... def get_token_symbol(mint_address): # Код функции...
Удалите вызовы функций в вашем коде:
Проверьте ваш код на наличие вызовов этих функций и удалите их.
python# Пример удаления вызова функции # Удалите или закомментируйте эту строку load_token_list()
Удалите использование get_token_symbol
:
В функции get_sol_transaction
или в других местах убедитесь, что вы не вызываете get_token_symbol
.
python# Пример удаления использования функции # Удалите или замените этот код token = get_token_symbol(token_mint)
Удалите переменные, связанные со списком токенов:
Если у вас есть глобальные переменные, связанные с функциями, например token_map
, удалите их.
python# Удалите эту переменную token_map = {}
Удалите ненужные импорты:
Если функции использовали модули, которые больше не нужны (например, requests
), удалите их из импортов.
python# Удалите ненужные импорты import requests
Обновите функцию get_sol_transaction
:
Убедитесь, что в этой функции нет ссылок на удалённые функции или переменные. Например, если вы ранее использовали token
или token_mint
, удалите или замените их.
python# Убедитесь, что код не использует переменные token или token_mint amount_info = f"{action}: {amount_formatted} SOL\n"
Обновите функцию send_notifications
:
Проверьте, что формирование сообщений не использует переменные или данные, связанные с токенами.
python# Пример формирования сообщения text = f"Транзакция: {transaction['transaction']} \n" text += f"Отправитель: {transaction['sender']} \n" text += f"Получатель: {transaction['receiver']} \n" text += transaction['amount_info']
Проверьте и обновите комментарии:
Убедитесь, что комментарии в коде соответствуют текущей функциональности и не ссылаются на удалённые функции.
Обновите документацию:
Если у вас есть документация или инструкции по использованию бота, обновите их, чтобы отразить изменения.
Проведите тестирование:
Убедитесь, что бот работает корректно после удаления функций. Проверьте обработку транзакций с SOL и убедитесь, что уведомления отправляются правильно.
Проверка на ошибки:
Запустите код и просмотрите логи на предмет ошибок или исключений, связанных с удалёнными функциями.
Преимущества удаления этих функций:
Упрощение кода: Код станет более чистым и понятным, что облегчает его поддержку и развитие.
Снижение вероятности ошибок: Удаление неиспользуемых функций и переменных уменьшает риск возникновения ошибок, связанных с ними.
Оптимизация производительности: Бот будет работать немного быстрее, так как не будет загружать и обрабатывать ненужные данные о токенах.
Пример обновлённого кода функции get_sol_transaction
:
pythonasync def get_sol_transaction(tx_signature: str, user_wallet: str) -> dict | None: """ Получение информации о транзакции Solana по ее подписи. """ # Ваш обновлённый код, который обрабатывает только SOL-транзакции # Убедитесь, что нет ссылок на token или get_token_symbol
Пример обновлённого кода функции send_notifications
:
pythonasync def send_notifications(bot: Bot, timeout: int): while True: # Ваш код для отправки уведомлений # Убедитесь, что сообщения формируются правильно и не используют данные о токенах
Если в будущем вы решите снова обрабатывать транзакции с другими токенами, вы можете сохранить резервную копию удалённых функций или вернуться к предыдущей версии кода.
Если у вас возникнут трудности или дополнительные вопросы при удалении функций или обновлении кода, пожалуйста, дайте мне знать, и я с радостью помогу вам!