Sova Labs
  • Introduction
  • FAQ
  • MEV on TON
    • Overview
  • MEV Searcher Guide
    • FAQ
    • Rust SDK
    • Golang SDK
    • Javascript SDK
  • API Overview
  • Blockchain Node
    • Overview
    • First Validator Setup
    • Existing Validator Update
    • Configuration Validator client or LiteServer
    • Rollback to the original TON version
Powered by GitBook
On this page
  • Prerequisite
  • Create a SovaClient Instance for Testnet
  • Authenticate the Client
  • Subscribe for Messages
  • Subscribe by addresses
  • Subscribe by Workchain
  • Subscribe by Workchain shard
  • Subscribe by external out message body opcode
  • Subscribe by internal message body opcode
  • Bundles Submission
  • Bundles Results tracking
  • Examples
  1. MEV Searcher Guide

Rust SDK

PreviousFAQNextGolang SDK

Last updated 2 months ago

Prerequisite

The Rust SDK is available at .

Add the Sova SDK by running:

cargo add --git https://github.com/sova-network/sova-sdk-rs --branch main

CAUTION: By default, each searcher enforces a limit of 5 requests per second. If you need to have a higher limit, you can send a request to hello@sova.network.

Create a SovaClient Instance for Testnet

use sova_sdk_rs::client::SovaClient;

fn main() {
    // Create a client instance for testnet.
    let client = SovaClient::testnet();
}

Authenticate the Client

If you need authenticated access, you can sign the challenge provided by the Sova Engine using your ED25519 private key. Replace the dummy private key with your actual 32-byte ED25519 key. If you choose to skip authentication, simply do not call this method.

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create the testnet client.
    let mut client = SovaClient::testnet();

    // Replace with your actual 32-byte private key.
    let private_key: [u8; 32] = [0; 32];

    // Authenticate and obtain an access token.
    let token = client
        .authenticate(private_key)
        .await
        .expect("Failed authentication");
    println!("Authenticated successfully. Token: {:?}", token);

    // Proceed to subscribe for workchain messages.
    // subscribe_workchain_messages(&client).await?;

    // Keep the application running to receive messages.
    loop {
        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
    }
}

Subscribe for Messages

Regardless of authentication, you can use the searcher service to subscribe to Mempool messages.

Subscribe by addresses

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create the testnet client.
    let client = SovaClient::testnet();

    // Obtain a SovaSearcher instance from the client.
    let mut searcher = client.searcher().await?;

    // Subscribe to mempool updates filtered by a list of addresses.
    searcher
        .subscribe_by_addresses(
            vec!["address1".to_string(), "address2".to_string()],
            |packet| {
                println!("Mempool packet (by addresses) received: {:?}", packet);
            },
        )
        .await?;

    // ...
    Ok(())
}

Subscribe by Workchain

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create the testnet client.
    let client = SovaClient::testnet();

    // Obtain a SovaSearcher instance from the client.
    let mut searcher = client.searcher().await?;

    // Subscribe to mempool updates for a specific workchain.
    searcher
        .subscribe_by_workchain(0, |packet| {
            println!("Mempool packet (by workchain) received: {:?}", packet);
        })
        .await?;

    // ...
    Ok(())
}

Subscribe by Workchain shard

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create the testnet client.
    let client = SovaClient::testnet();

    // Obtain a SovaSearcher instance from the client.
    let mut searcher = client.searcher().await?;

    // Subscribe to mempool updates for a specific workchain and shard.
    searcher
        .subscribe_by_workchain_shard(0, vec![96, 0, 0, 0, 0, 0, 0, 0], |packet| {
            println!("Mempool packet (by workchain shard) received: {:?}", packet);
        })
        .await?;

    // ...
    Ok(())
}

Subscribe by external out message body opcode

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create the testnet client.
    let client = SovaClient::testnet();

    // Obtain a SovaSearcher instance from the client.
    let mut searcher = client.searcher().await?;

    // Subscribe using external out message body opcode filtering.
    searcher
        .subscribe_by_external_out_msg_body_opcode(
            0,
            Some(vec![96, 0, 0, 0, 0, 0, 0, 0]),
            42,
            |packet| {
                println!(
                    "Mempool packet (by external out opcode) received: {:?}",
                    packet
                );
            },
        )
        .await?;

    // ...
    Ok(())
}

Subscribe by internal message body opcode

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create the testnet client.
    let client = SovaClient::testnet();

    // Obtain a SovaSearcher instance from the client.
    let mut searcher = client.searcher().await?;

    // Subscribe using internal message body opcode filtering.
    searcher
        .subscribe_by_internal_msg_body_opcode(0, Some(vec![96, 0, 0, 0, 0, 0, 0, 0]), 42, |packet| {
            println!("Mempool packet (by internal opcode) received: {:?}", packet);
        })
        .await?;

    // ...
    Ok(())
}

Bundles Submission

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create the testnet client.
    let client = SovaClient::testnet();

    // Obtain a SovaSearcher instance from the client.
    let mut searcher = client.searcher().await?;

    // Retrieve the current tip addresses from the network.
    // The bundle must include a transfer to one of these addresses.
    let tip_addresses = searcher.get_tip_addresses().await?;

    // Prepare a new bundle.
    // A bundle is a collection of external messages that will be applied to a block in a declared sequence.
    // IMPORTANT: The bundle must include a transfer to one of the tip addresses with a non-zero amount of TON.
    // No TON will be sent unless the complete bundle is applied to a block.
    let mut bundle = sova_sdk_rs::searcher::Bundle {
        message: vec![],
        expiration_ns: None,
    };

    // Create an external message.
    // This message should be serialized in a Bag Of Cells format.
    let ext_message_data: Vec<u8> = vec![];

    // Add a transfer message to the bundle.
    // The transfer must send a non-zero amount of TON to one of the tip addresses.
    // Here we push an ExternalMessage, assuming 'ext_message_data' represents a properly constructed external message data.
    bundle.message.push(sova_sdk_rs::searcher::ExternalMessage {
        data: ext_message_data,
    });

    // Extend the bundle with any additional external messages.
    let ext_messages = vec![];
    // These messages can provide additional instructions or data.
    bundle.message.extend(ext_messages);

    // Send the bundle using the searcher.
    // If the bundle is successfully applied to a block, the TON transfer will occur.
    // Otherwise, no TON is sent.
    let auction_id;

    match searcher.send_bundle(bundle).await {
        Ok(result) => {
            // On success, capture bundle identifier that can be used to track auction status of bundle.
            auction_id = result.id.clone();
        }
        Err(e) => {
            // Handle errors gracefully.
            println!("Failed to send bundle: {:?}", e);
        }
    }

    // ...
    Ok(())
}

Bundles Results tracking

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create the testnet client.
    let client = SovaClient::testnet();

    // Obtain a SovaSearcher instance from the client.
    let mut searcher = client.searcher().await?;

    searcher.subscribe_bundle_results(move |bundle_result| {
        // Print the received bundle result.
        println!("Received BundleResult: {:?}", bundle_result);
        // Process the bundle result based on its type.
        match bundle_result.result {
            // Bundle won the auction.
            Some(sova_sdk_rs::searcher::bundle_result::Result::Win(win)) => {
                println!("Bundle won the auction!");
                println!("Auction ID: {}", win.auction_id);
                println!("Estimated nanoton tip: {}", win.estimated_nanoton_tip);
            },
            // Bundle lost the auction.
            Some(sova_sdk_rs::searcher::bundle_result::Result::Loose(loose)) => {
                println!("Bundle lost the auction.");
                println!("Auction ID: {}", loose.auction_id);
            },
            // Bundle was interrupted.
            Some(sova_sdk_rs::searcher::bundle_result::Result::Drop(drop)) => {
                println!("Bundle was interrupted.");
                if let Some(reason) = drop.reason {
                    match reason {
                        // Bundle was partially processed.
                        sova_sdk_rs::searcher::bundle_result_interrupted::Reason::PartiallyProcessed(proc) => {
                            println!("Bundle partially processed.");
                            println!("Auction ID: {}", proc.auction_id);
                            println!("Processed message digests: {:?}", proc.digest);
                        },
                        // Bundle partially expired.
                        sova_sdk_rs::searcher::bundle_result_interrupted::Reason::Expired(expired) => {
                            println!("Bundle partially expired.");
                            println!("Auction ID: {}", expired.auction_id);
                            println!("Expired message digests: {:?}", expired.digest);
                        },
                    }
                }
            },
            // Bundle auction failed.
            Some(sova_sdk_rs::searcher::bundle_result::Result::Failure(failure)) => {
                println!("Bundle auction failed.");
                if let Some(reason) = failure.reason {
                    match reason {
                        // Auction failed due to an estimate error.
                        sova_sdk_rs::searcher::bundle_result_auction_failed::Reason::EstimateError(err) => {
                            println!("Auction failed due to estimate error.");
                            println!("Auction ID: {}", err.auction_id);
                            println!("Failed message digest: {:?}", err.digest);
                            println!("Error message: {}", err.message);
                        },
                        // Auction failed due to an internal error.
                        sova_sdk_rs::searcher::bundle_result_auction_failed::Reason::InternalError(err) => {
                            println!("Auction failed due to internal error.");
                            println!("Auction ID: {}", err.auction_id);
                            println!("Message digests: {:?}", err.digest);
                            println!("Error message: {}", err.message);
                        },
                    }
                }
            },
            // Handle the case where the bundle result is not recognized.
            None => {
                println!("Received an unrecognized bundle result.");
            },
        }
    }).await.expect("Failed to subscribe to bundle results");

    // ...
    Ok(())
}

Examples

  1. Priority/Anti-MEV transactions

https://github.com/sova-network/sova-sdk-rs