<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>armutbey</title>
        <link>https://paragraph.com/@codeesura</link>
        <description>undefined</description>
        <lastBuildDate>Sun, 30 Aug 2026 04:58:24 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>armutbey</title>
            <url>https://storage.googleapis.com/papyrus_images/d461add6fc27eaa127cdee75f1730ee754e054f902cb69ebeb87953d7be302ef.jpg</url>
            <link>https://paragraph.com/@codeesura</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[The Dark Side of Crypto: zkSync Recovery Operation]]></title>
            <link>https://paragraph.com/@codeesura/the-dark-side-of-crypto-zksync-recovery-operation</link>
            <guid>bCtBAV0IQ2RWOYkbLhcF</guid>
            <pubDate>Sun, 23 Jun 2024 18:36:08 GMT</pubDate>
            <description><![CDATA[Last week, zkSync announced a new airdrop event. Shortly after this announcement, many users reached out to me on Twitter, reporting that their wallets had been hacked. They were curious about what they could do in this situation and how they could recover their assets. In this article, I will detail how hacked wallets were recovered during zkSync and LayerZero airdrops and the technical challenges encountered in this process.Initial Steps and GoalsMy primary goal was to help users receive th...]]></description>
            <content:encoded><![CDATA[<p>Last week, zkSync announced a new airdrop event. Shortly after this announcement, many users reached out to me on Twitter, reporting that their wallets had been hacked. They were curious about what they could do in this situation and how they could recover their assets. In this article, I will detail how hacked wallets were recovered during zkSync and LayerZero airdrops and the technical challenges encountered in this process.</p><h4 id="h-initial-steps-and-goals" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Initial Steps and Goals</h4><p>My primary goal was to help users receive their airdrops as quickly and securely as possible. To achieve this, I needed to process over 50 wallets in parallel and send prepared transactions to the network swiftly. I followed several critical steps to manage this process successfully.</p><p>First and foremost, setting up a reliable and fast node was essential. Node setup was crucial for transaction validation and synchronization with the network.</p><h4 id="h-research-and-preparation-process" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Research and Preparation Process</h4><p>Initially, I continuously scraped the ZKNation (airdrop page) site. Any data added in real-time could significantly contribute to my work. At first, I only found the token address on the site and began examining the wallets that created this token address. During this process, I noticed that a Merkle Distributor proxy contract had been deployed recently and saw that the contract codes were open.</p><p>Upon examining the contract in detail, I discovered that tokens could be claimed using two functions, &quot;claim&quot; and &quot;claimOnBehalf&quot;. The &quot;claim&quot; function required three input parameters: index, amount, and merkleProof. I realized that I could access these input parameters through the zkSync API. However, knowing that I currently did not have access to these proofs, I focused on constructing the general code structure.</p><h4 id="h-technical-details-and-technologies-used" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Technical Details and Technologies Used</h4><p>Given the need to process more than 50 wallets, performance and parallelism were of critical importance to me. Therefore, I decided to write my code in Rust. Rust is known for its high performance and memory safety features. It is also highly effective in handling parallel processing. While writing my code, I used the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/alloy-rs/alloy">Alloy-rs</a> library developed by <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.paradigm.xyz/">Paradigm</a>. <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/alloy-rs/alloy">Alloy-rs</a> allowed me to manage blockchain transactions more efficiently and securely. This library offered significant advantages, especially in low-level transaction management and optimization.</p><p>Another reason for using Rust and Alloy-rs was to enhance the overall efficiency of the system and minimize errors. Rust’s memory safety and prevention of race conditions helped minimize potential errors when processing multiple wallets simultaneously. Alloy-rs played a crucial role in the success of my project by providing flexibility in transaction validation and data management processes.</p><p>Using the <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.zksync.io/build/zksync-101/paymaster">Paymaster</a> feature on the zkSync network gave me a strategic advantage. Typically, hacked wallets had no ETH (due to a sweeper bot), requiring an extra transfer transaction. On a different network, the claim procedure would be: Transfer + claim + transfer. However, thanks to Paymaster, I simplified this process to just Claim + Transfer. This allowed me to execute transactions faster and more cost-effectively.</p><p>Paymaster usage was particularly advantageous for executing transactions in wallets without ETH. This minimized transaction time and costs by avoiding extra transfer transactions when claiming tokens from hacked wallets.</p><h3 id="h-general-code-structure" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">General Code Structure</h3><p>The main structure of my code was built around parallel processing and error management. Here are the important parts of the code and their functions:</p><h4 id="h-1-parallel-processing-management" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">1. Parallel Processing Management:</h4><p>This section creates a separate asynchronous task for each wallet. By using <code>tokio::spawn</code>, I can process the transactions for each wallet in parallel. This enables me to handle more than 50 wallets simultaneously.</p><pre data-type="codeBlock" text="    let tasks: Vec&lt;_&gt; = config
        .private_keys
        .into_iter()
        .map(|private_key| {
            let signer: LocalWallet = private_key.parse().unwrap();
            let signer_address = signer.address().to_string();

            if let Some(user_data_array) = airdrop_data.get(&amp;signer_address) {
                let user_data = &amp;user_data_array[0];
                let index = user_data[&quot;merkleIndex&quot;].as_str().unwrap().parse::&lt;u64&gt;().unwrap();
                let amount = user_data[&quot;tokenAmount&quot;].as_str().unwrap().parse::&lt;u128&gt;().unwrap();
                let merkle_proof: Vec&lt;String&gt; = user_data[&quot;merkleProof&quot;]
                    .as_array()
                    .unwrap()
                    .iter()
                    .map(|v| v.as_str().unwrap().to_string())
                    .collect();

                let provider = Arc::clone(&amp;provider);
                let params = WalletProcessParams::new(
                    provider,
                    private_key,
                    chain_id,
                    index,
                    amount,
                    merkle_proof,
                );
                tokio::spawn(async move { process_wallet(params).await })
            } else {
                tokio::spawn(async { Err(eyre::eyre!(&quot;User data not found&quot;)) })
            }
        })
        .collect();
"><code>    let tasks: Vec<span class="hljs-operator">&#x3C;</span><span class="hljs-keyword">_</span><span class="hljs-operator">></span> <span class="hljs-operator">=</span> config
        .private_keys
        .into_iter()
        .map(<span class="hljs-operator">|</span>private_key<span class="hljs-operator">|</span> {
            let signer: LocalWallet <span class="hljs-operator">=</span> private_key.parse().<span class="hljs-built_in">unwrap</span>();
            let signer_address <span class="hljs-operator">=</span> signer.<span class="hljs-built_in">address</span>().to_string();

            <span class="hljs-keyword">if</span> let Some(user_data_array) <span class="hljs-operator">=</span> airdrop_data.get(<span class="hljs-operator">&#x26;</span>signer_address) {
                let user_data <span class="hljs-operator">=</span> <span class="hljs-operator">&#x26;</span>user_data_array[<span class="hljs-number">0</span>];
                let index <span class="hljs-operator">=</span> user_data[<span class="hljs-string">"merkleIndex"</span>].as_str().<span class="hljs-built_in">unwrap</span>().parse::<span class="hljs-operator">&#x3C;</span>u64<span class="hljs-operator">></span>().<span class="hljs-built_in">unwrap</span>();
                let amount <span class="hljs-operator">=</span> user_data[<span class="hljs-string">"tokenAmount"</span>].as_str().<span class="hljs-built_in">unwrap</span>().parse::<span class="hljs-operator">&#x3C;</span>u128<span class="hljs-operator">></span>().<span class="hljs-built_in">unwrap</span>();
                let merkle_proof: Vec<span class="hljs-operator">&#x3C;</span>String<span class="hljs-operator">></span> <span class="hljs-operator">=</span> user_data[<span class="hljs-string">"merkleProof"</span>]
                    .as_array()
                    .<span class="hljs-built_in">unwrap</span>()
                    .iter()
                    .map(<span class="hljs-operator">|</span>v<span class="hljs-operator">|</span> v.as_str().<span class="hljs-built_in">unwrap</span>().to_string())
                    .collect();

                let provider <span class="hljs-operator">=</span> Arc::clone(<span class="hljs-operator">&#x26;</span>provider);
                let params <span class="hljs-operator">=</span> WalletProcessParams::<span class="hljs-keyword">new</span>(
                    provider,
                    private_key,
                    chain_id,
                    index,
                    amount,
                    merkle_proof,
                );
                tokio::spawn(async move { process_wallet(params).await })
            } <span class="hljs-keyword">else</span> {
                tokio::spawn(async { Err(eyre::eyre<span class="hljs-operator">!</span>(<span class="hljs-string">"User data not found"</span>)) })
            }
        })
        .collect();
</code></pre><h4 id="h-2-loading-and-processing-data" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">2. Loading and Processing Data:</h4><p>This function loads the airdrop data I prepared beforehand. By minimizing API calls, it enhances processing speed.</p><pre data-type="codeBlock" text="let airdrop_data = load_airdrop_data(&quot;airdrop_data.json&quot;).await?;
"><code>let <span class="hljs-attr">airdrop_data</span> = load_airdrop_data(<span class="hljs-string">"airdrop_data.json"</span>).await?<span class="hljs-comment">;</span>
</code></pre><h4 id="h-3-wallet-processing-function" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0"><strong>3. Wallet Processing Function:</strong></h4><p>This function manages the claim and transfer transactions for each wallet. The <code>tokio::join!</code> macro allows me to run these two transactions concurrently.</p><pre data-type="codeBlock" text="async fn process_wallet(params: WalletProcessParams) -&gt; Result&lt;(), eyre::Report&gt; {
    let WalletProcessParams {
        provider,
        private_key,
        chain_id,
        claim_contract_address,
        token_contract_address,
        recipient_address,
        index,
        amount,
        merkle_proof,
    } = params;

    let signer: LocalWallet = private_key.parse().unwrap();
    let signer = Arc::new(signer);
    let provider = Arc::clone(&amp;provider);
    let merkle_proof = Arc::new(merkle_proof);

    loop {
        let claim_future = {
            let provider = Arc::clone(&amp;provider);
            let signer = Arc::clone(&amp;signer);
            let merkle_proof = Arc::clone(&amp;merkle_proof);
            async move {
                match perform_claim(
                    provider.clone(),
                    &amp;signer,
                    chain_id,
                    claim_contract_address,
                    index,
                    amount,
                    merkle_proof.to_vec(),
                )
                .await
                {
                    Ok(_) =&gt; {
                        println!(&quot;Claim transaction succeeded.&quot;);
                        Ok(())
                    }
                    Err(e) =&gt; {
                        eprintln!(&quot;Claim transaction failed: {:?}&quot;, e);
                        Err(e)
                    }
                }
            }
        };

        let transfer_future = {
            let provider = Arc::clone(&amp;provider);
            let signer = Arc::clone(&amp;signer);
            async move {
                match perform_transfer(
                    provider.clone(),
                    &amp;signer,
                    chain_id,
                    token_contract_address,
                    recipient_address,
                    amount,
                )
                .await
                {
                    Ok(_) =&gt; {
                        println!(&quot;Transfer transaction succeeded.&quot;);
                        Ok(())
                    }
                    Err(e) =&gt; {
                        eprintln!(&quot;Transfer transaction failed: {:?}&quot;, e);
                        Err(e)
                    }
                }
            }
        };
"><code>async fn process_wallet(params: WalletProcessParams) <span class="hljs-operator">-</span><span class="hljs-operator">></span> Result<span class="hljs-operator">&#x3C;</span>(), eyre::Report<span class="hljs-operator">></span> {
    let WalletProcessParams {
        provider,
        private_key,
        chain_id,
        claim_contract_address,
        token_contract_address,
        recipient_address,
        index,
        amount,
        merkle_proof,
    } <span class="hljs-operator">=</span> params;

    let signer: LocalWallet <span class="hljs-operator">=</span> private_key.parse().<span class="hljs-built_in">unwrap</span>();
    let signer <span class="hljs-operator">=</span> Arc::<span class="hljs-keyword">new</span>(signer);
    let provider <span class="hljs-operator">=</span> Arc::clone(<span class="hljs-operator">&#x26;</span>provider);
    let merkle_proof <span class="hljs-operator">=</span> Arc::<span class="hljs-keyword">new</span>(merkle_proof);

    loop {
        let claim_future <span class="hljs-operator">=</span> {
            let provider <span class="hljs-operator">=</span> Arc::clone(<span class="hljs-operator">&#x26;</span>provider);
            let signer <span class="hljs-operator">=</span> Arc::clone(<span class="hljs-operator">&#x26;</span>signer);
            let merkle_proof <span class="hljs-operator">=</span> Arc::clone(<span class="hljs-operator">&#x26;</span>merkle_proof);
            async move {
                match perform_claim(
                    provider.clone(),
                    <span class="hljs-operator">&#x26;</span>signer,
                    chain_id,
                    claim_contract_address,
                    index,
                    amount,
                    merkle_proof.to_vec(),
                )
                .await
                {
                    Ok(<span class="hljs-keyword">_</span>) <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
                        println<span class="hljs-operator">!</span>(<span class="hljs-string">"Claim transaction succeeded."</span>);
                        Ok(())
                    }
                    Err(e) <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
                        eprintln<span class="hljs-operator">!</span>(<span class="hljs-string">"Claim transaction failed: {:?}"</span>, e);
                        Err(e)
                    }
                }
            }
        };

        let transfer_future <span class="hljs-operator">=</span> {
            let provider <span class="hljs-operator">=</span> Arc::clone(<span class="hljs-operator">&#x26;</span>provider);
            let signer <span class="hljs-operator">=</span> Arc::clone(<span class="hljs-operator">&#x26;</span>signer);
            async move {
                match perform_transfer(
                    provider.clone(),
                    <span class="hljs-operator">&#x26;</span>signer,
                    chain_id,
                    token_contract_address,
                    recipient_address,
                    amount,
                )
                .await
                {
                    Ok(<span class="hljs-keyword">_</span>) <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
                        println<span class="hljs-operator">!</span>(<span class="hljs-string">"Transfer transaction succeeded."</span>);
                        Ok(())
                    }
                    Err(e) <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
                        eprintln<span class="hljs-operator">!</span>(<span class="hljs-string">"Transfer transaction failed: {:?}"</span>, e);
                        Err(e)
                    }
                }
            }
        };
</code></pre><h4 id="h-4-error-management-and-retry-mechanism" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0"><strong>4. Error Management and Retry Mechanism:</strong></h4><p>This structure checks the results of the claim and transfer transactions and retries the transactions in case of errors. This ensures transaction completion even in the face of network issues or other temporary errors.</p><pre data-type="codeBlock" text="let (claim_result, transfer_result) = tokio::join!(claim_future, transfer_future);

match (claim_result, transfer_result) {
    (Ok(_), Ok(_)) =&gt; return Ok(()),
    (Err(claim_err), Ok(_)) =&gt; {
        eprintln!(&quot;Retrying claim transaction due to error: {:?}&quot;, claim_err);
    }
    (Ok(_), Err(transfer_err)) =&gt; {
        eprintln!(&quot;Retrying transfer transaction due to error: {:?}&quot;, transfer_err);
    }
    (Err(claim_err), Err(transfer_err)) =&gt; {
        eprintln!(
            &quot;Retrying both transactions due to errors: claim - {:?}, transfer - {:?}&quot;,
            claim_err, transfer_err
        );
    }
}
"><code>let (claim_result, transfer_result) <span class="hljs-operator">=</span> tokio::join<span class="hljs-operator">!</span>(claim_future, transfer_future);

match (claim_result, transfer_result) {
    (Ok(<span class="hljs-keyword">_</span>), Ok(<span class="hljs-keyword">_</span>)) <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">return</span> Ok(()),
    (Err(claim_err), Ok(<span class="hljs-keyword">_</span>)) <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
        eprintln<span class="hljs-operator">!</span>(<span class="hljs-string">"Retrying claim transaction due to error: {:?}"</span>, claim_err);
    }
    (Ok(<span class="hljs-keyword">_</span>), Err(transfer_err)) <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
        eprintln<span class="hljs-operator">!</span>(<span class="hljs-string">"Retrying transfer transaction due to error: {:?}"</span>, transfer_err);
    }
    (Err(claim_err), Err(transfer_err)) <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
        eprintln<span class="hljs-operator">!</span>(
            <span class="hljs-string">"Retrying both transactions due to errors: claim - {:?}, transfer - {:?}"</span>,
            claim_err, transfer_err
        );
    }
}
</code></pre><h4 id="h-5-paymaster-integration" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0"><strong>5. Paymaster Integration:</strong></h4><p>Paymaster usage was integrated into the <code>perform_claim</code> and <code>perform_transfer</code> functions. These functions use the Paymaster contract to perform gasless transactions.</p><h3 id="h-conclusion" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Conclusion</h3><p>Thanks to this code structure, I was able to process multiple wallets in parallel, manage errors effectively, and perform gasless transactions using Paymaster, ensuring a fast and efficient airdrop recovery operation.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/edee991b4321cf4efad39584565824d98784228401fd0542f2e712c30e104582.png" alt="Recovered ZK tokens" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Recovered ZK tokens</figcaption></figure><p>As a result, with this technical approach and code structure, I successfully recovered 90% of the wallets sent to me. The remaining 10% of failures were mostly due to external factors such as network issues preventing transaction propagation.</p><h4 id="h-source-code" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Source Code :</h4><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/codeesura/zksync-airdrop-rescue">https://github.com/codeesura/zksync-airdrop-rescue</a></p><h4 id="h-contact" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Contact:</h4><p><br>Twitter : <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/codeesura">twitter.com/codeesura</a><br>Github: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/codeesura">github.com/codeesura</a></p>]]></content:encoded>
            <author>codeesura@newsletter.paragraph.com (armutbey)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/c9b6fdb7eae159d2fb128fdea50ef8f93525040889657afca51e171df63ab2f4.jpg" length="0" type="image/jpg"/>
        </item>
    </channel>
</rss>