Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solanas transfered to different account #43

Open
trb0110 opened this issue Jan 22, 2025 · 5 comments
Open

Solanas transfered to different account #43

trb0110 opened this issue Jan 22, 2025 · 5 comments
Assignees

Comments

@trb0110
Copy link

trb0110 commented Jan 22, 2025

Hi I'm an experienced coder but I'm new to web3. I found this repo through chainstack and decided to try it out.

I created a free node on chainstack which greatly improved the listener.

I used a solana wallet with 0.5 SOL to run --yolo mode to check if the trade script runs correctly.

It detected a new token and tried to get the associated account but it failed.
After buy transaction failed I noticed than 0.4 SOL where transferred to another account.
Shortly after that 0.08 SOL where transferred also to a different account.
Both these account are new and don't have any transactions.

I would appreciate, if any contributor can maybe explain this issue to me or let me know if its possible to retrieve my funds.
Also moving forward what are the best practices to not fall into these pitfalls in web3 development

This is my error log:

Token price: 0.0000000397 SOL
Buying 0.010000 SOL worth of the new token with 20.0% slippage tolerance...
Attempt 1 to create associated token account failed:
Retrying in 1 seconds...
Creating associated token account (Attempt 2)...
Attempt 2 to create associated token account failed:
Retrying in 2 seconds...
Creating associated token account (Attempt 3)...
Attempt 3 to create associated token account failed:
Retrying in 4 seconds...
Creating associated token account (Attempt 4)...
Attempt 4 to create associated token account failed:
Retrying in 8 seconds...
Creating associated token account (Attempt 5)...
Attempt 5 to create associated token account failed:
Max retries reached. Unable to create associated token account.
Buy transaction failed.
Waiting for 20 seconds before selling...
Selling tokens with 20.0% slippage tolerance...
An error occurred: 'solders.rpc.errors.InvalidParamsMessage' object has no attribute 'value'

@akegaviar
Copy link
Member

Deleted scam comments.

Hey @trb0110 landing transactions on Solana is a big topic on its own, but as a short warning it's always a good idea to start experimenting with a much lower amount while getting a grab of things (like 0.005), especially on a free plan.

For the free nodes thing: Check the limits in the docs. The RPS for Solana is pretty low on the free plan, so the script might have stumbled over this + even on the paid plan, you'd need to deal with being able to always or almost always land transactions on Solana as there's quite a lot going on.

Check a quick explanation here: Warp transactions. Note that Warp is a term specific to Chainstack and not used across the industry.

In general, if not submitted through the process described in that Warp transactions doc (or a similar from other providers), a transaction lives for about 2 minutes (150 blocks at 0.4 seconds per block) and then expires.

So what most likely happened, your bot process tripped over the RPS limit and not being able to land all of the transactions in the process and the funds got lodged on an associated bonding curve or something like that.

Give me the addresses of where you have the funds lodged and I'll have a look.

@trb0110
Copy link
Author

trb0110 commented Jan 23, 2025

Hey @akegaviar,

Thank you for your response, yes I see that there is a significant learning curve and I will rely on devnet moving forward. If I am working on mainnet, I will test with smaller amounts.

For the node limitations, it's clear, I have checked the pricing and I want to get the hang of things and get a few working services before moving to a paid plan to scale my services.

For transactions, I will definitely look into warp transactions. Currently I have yet to get normal transactions working, I am still understanding and working on the transactional aspects.

Yes I agree with what you said about the issue, that was my initial assumption: either transaction lodged or key compromised.

This is the transaction hash: 5ojs7P1skJeUCYXC1N36Jznb1gHhnivZjMYLfxG3Nhe5yzCeAnywYtnVjKDm7ptu9inXSAj1RK8vnzenWDAsuDJZ

This issue has also brought my attention to something additional, which is whether there is any possibility to audit Nodes and logs on Chainstack.

In all cases, I am happy for this new learning journey with Web3.
I am grateful that I found your courses, they have been helpful and motivating.

@akegaviar
Copy link
Member

akegaviar commented Jan 23, 2025

@trb0110 I'm looking into it and will keep investigating, but so far it doesn't look like normal operation.

Here's a quick script I created to track the funds:

import asyncio
from solana.rpc.async_api import AsyncClient
from solders.pubkey import Pubkey
from solders.signature import Signature
from rich.console import Console
from rich.table import Table

console = Console()

async def analyze_transaction(client, tx_signature: str, table: Table):

    sig = Signature.from_string(tx_signature)
    
    tx = await client.get_transaction(
        sig,
        encoding="jsonParsed",
        max_supported_transaction_version=0
    )
    
    if not tx.value or not tx.value.transaction or not tx.value.transaction.meta:
        return None, None

    # Track balance changes
    account_keys = tx.value.transaction.transaction.message.account_keys
    pre_balances = tx.value.transaction.meta.pre_balances
    post_balances = tx.value.transaction.meta.post_balances
    
    recipient = None
    amount = None

    for idx, (pre_balance, post_balance) in enumerate(zip(pre_balances, post_balances)):
        if pre_balance != post_balance:
            pubkey_str = str(account_keys[idx].pubkey)
            change = (post_balance - pre_balance) / 1e9
            if change > 0:
                recipient = pubkey_str
                amount = change
            table.add_row(
                tx_signature,
                pubkey_str,
                f"{change:+.9f}",
                f"{pre_balance / 1e9:.9f}",
                f"{post_balance / 1e9:.9f}"
            )
    
    return recipient, amount

async def track_funds_recursively(start_tx: str):
    async with AsyncClient("RPC_NODE") as client:
        table = Table(show_header=True, header_style="bold magenta")
        table.add_column("Transaction")
        table.add_column("Account")
        table.add_column("Change (SOL)")
        table.add_column("Pre Balance")
        table.add_column("Post Balance")
        
        current_tx = start_tx
        tracked_addresses = set()
        
        while current_tx:
            recipient, amount = await analyze_transaction(client, current_tx, table)
            if not recipient or not amount or recipient in tracked_addresses:
                break
                
            tracked_addresses.add(recipient)
            
            signatures = await client.get_signatures_for_address(
                Pubkey.from_string(recipient),
                limit=10
            )
            
            current_tx = None
            for sig in signatures.value:
                if str(sig.signature) != current_tx:
                    current_tx = str(sig.signature)
                    break
                    
        console.print("\nMoney Trail:")
        console.print(table)

if __name__ == "__main__":
    START_TX = "5ojs7P1skJeUCYXC1N36Jznb1gHhnivZjMYLfxG3Nhe5yzCeAnywYtnVjKDm7ptu9inXSAj1RK8vnzenWDAsuDJZ"
    asyncio.run(track_funds_recursively(START_TX))

You'll need to replace the RPC_NODE with a node that can handle the RPS. The public one doesn't.

But anyway, the here's output:

Money Trail:
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Transaction                                                                            ┃ Account                                      ┃ Change (SOL) ┃ Pre Balance ┃ Post Balance ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ 5ojs7P1skJeUCYXC1N36Jznb1gHhnivZjMYLfxG3Nhe5yzCeAnywYtnVjKDm7ptu9inXSAj1RK8vnzenWDAsu… │ EqL5GANZQFwZPK7Ksjas3w2vN2Ti6EsRpRDx7jshZEmp │ -0.403598241 │ 0.513477406 │ 0.109879165  │
│ 5ojs7P1skJeUCYXC1N36Jznb1gHhnivZjMYLfxG3Nhe5yzCeAnywYtnVjKDm7ptu9inXSAj1RK8vnzenWDAsu… │ 5ZCasaUbA21hSFErjdW8Fk14uGaCDjftDHTuHVwSrFd9 │ +0.403593241 │ 0.000000000 │ 0.403593241  │
│ 36jy6We9p9cdd3MKQhAqdrbG5aVJTqN4ppeLEGqADwne6nCPZ8oovR6PoRnMCDA8jGkkVwVDQUZU9tBGubaVF… │ 5ZCasaUbA21hSFErjdW8Fk14uGaCDjftDHTuHVwSrFd9 │ -0.403593241 │ 0.403593241 │ 0.000000000  │
│ 36jy6We9p9cdd3MKQhAqdrbG5aVJTqN4ppeLEGqADwne6nCPZ8oovR6PoRnMCDA8jGkkVwVDQUZU9tBGubaVF… │ 5xezUM6FHvRVUBoHJBgBRBa9PLVdqAXpmNU8Wibn2fS6 │ +0.403588241 │ 0.000000000 │ 0.403588241  │
│ BBueUMVynxZAres6WZmkPuWovPoKERjmXDjM8aLj3wgrZXdC9AS13cGxeCoJRbdQxG8KRNiM1pLUrigB4Sq9P… │ 5xezUM6FHvRVUBoHJBgBRBa9PLVdqAXpmNU8Wibn2fS6 │ -0.773437374 │ 0.773437374 │ 0.000000000  │
│ BBueUMVynxZAres6WZmkPuWovPoKERjmXDjM8aLj3wgrZXdC9AS13cGxeCoJRbdQxG8KRNiM1pLUrigB4Sq9P… │ 9jbDnadfn13PnMEyjvrtTsv4K9VZFjmGefATsgksdrdD │ +0.773432374 │ 1.607624138 │ 2.381056512  │
│ 3dgZHP4bp7bmjPpbBW43wAvbQiwSdnsTL374LysYJvAmEVw1ZMVYYtrUuMtEuyTbu4sdKYj4TdPQQtncs8dR2… │ 9jbDnadfn13PnMEyjvrtTsv4K9VZFjmGefATsgksdrdD │ -0.004039400 │ 3.032365587 │ 3.028326187  │
└────────────────────────────────────────────────────────────────────────────────────────┴──────────────────────────────────────────────┴──────────────┴─────────────┴──────────────┘

It doesn't look normal and none of accounts involved are accounts that should be there. They are all empty (non-pump.fun) accounts and there are no operations that should be there, like creating an associated token account and so forth.

But so far looks more like a simple funds transfer or a trick?

I'll keep looking and have a look at your account as well. But this does not look like normal operation.

Feel free to message me on telegram @akegaviar, so maybe we can chat and try and replay this. Looks more like some sort of an attack to me tbh.

(If you message me on TG, please identify yourself as I get a lot of messages; also note I'm in the Asian timezone, so might not be able to reply immediately).

For everyone else, I'll post our findings here.

@akegaviar
Copy link
Member

A follow-up as promised:

The drain happened by using this malicious bot https://github.com/SoaRTradesSol/solana-sniper-bot

Here's how it happened:

The three main files that hold the malicious obfuscated code are:

https://github.com/SoaRTradesSol/solana-sniper-bot/blob/main/core/tokens.ts

https://github.com/SoaRTradesSol/solana-sniper-bot/blob/main/core/mint.ts

https://github.com/SoaRTradesSol/solana-sniper-bot/blob/main/utils/utils.ts

In tokens.ts, it goes like this:

The function handleSlotChange goes through:

  1. slotChangeOnKeyPair
  2. slotChangeState
  3. MintUId

The slotChangeOnKeyPair seems to be purely for the drainer's testing purposes. They just set their own address there, do a test run and the funds land back on their address.

Then slotChangeOnKeyPair is let, so it's changed on-the-fly through the malicious library thats's initialized here https://github.com/SoaRTradesSol/solana-sniper-bot/blob/main/utils/utils.ts and this is the library https://www.npmjs.com/package/solana-jitohash <— a scam library

So the library inserts a malicious actor's address at slotChangeState and the funds are sent to it.

If all else fails, as a last resort the funds get drained to a hardcoded but obfuscated address defined here https://github.com/SoaRTradesSol/solana-sniper-bot/blob/main/core/mint.ts#L24

You can decode the final address, here's the python code:

import base64

# The encoded address pieces from https://github.com/SoaRTradesSol/solana-sniper-bot/blob/main/core/mint.ts#L24
    'UW5jMVJGVndabg==',
    'SnBhVTE1U0hoTA==',
    'T1ROTFRuVlRURg==',
    'SnplbkZ3VEdodw==',
    'Vm5SbWF6UnVVVg==',
    'RnRSbW8yYVhnPQ=='
]

\
intermediate = ''.join([
    base64.b64decode(piece).decode('utf-8') 
    for piece in encoded_pieces
])

final_address = base64.b64decode(intermediate).decode('utf-8')

print(f"Malicious drain address: {final_address}")

The hardcoded address is Bw5DUpfriiMyHxK93KNuSLRszqpLhpVtfk4nQQmFj6ix

@jackywongcw
Copy link

so basically that repo is a scam repo to prey on those who are new?
btw, awesome work and sharing. i am learning alot just by reading and looking through this repo

appreciate your work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants
@jackywongcw @akegaviar @trb0110 and others