# Snowsight

Snowsight is a mempool streaming service and a transaction propagation service for MEV searchers on the Avalanche C-Chain.

{% embed url="<https://snowsight.chainsight.dev>" %}
Snowsight App
{% endembed %}

{% embed url="<https://t.me/snowsight_avax>" %}
Snowsight Users Telegram
{% endembed %}

Avalanche C-Chain is an EVM-compatible smart contract chain with fast block finality via a novel PoS consensus mechanism.&#x20;

Despite its superior speed, some of Avalanche C-Chain's design trade-offs that provide users with fast block finality and a more pleasant user experience result in a mempool only available to users with vast amounts of capital.  As a result, only sophisticated actors have thus far been able to profit from the [intrinsic MEV](https://www.mev.wiki/) that exists on Avalanche C-Chain.&#x20;

But no longer.

Snowsight opens up the mempool to all.  Simply open a websocket to our service with a signed key from your local wallet, and the Dark Forest™ can soon be yours!

{% embed url="<https://twitter.com/ChainsightLabs/status/1518585868876926976>" %}
Snowsight featured on @BanklessHQ ROLLUP podcast
{% endembed %}


# Services

Snowsight provides two different services:

1. Mempool Stream
   * Provides users with a websocket interface to receive pending (yet to be finalized) transactions
   * Access to the mempool allows for same-block arbitrage, backrunning, and other MEV strategies to be performed
2. Transaction Propagator
   * Provides users with a low-latency transaction relay that propagates transactions to validator nodes much faster than any public RPC
   * Access to the propagator results in users being able to participate in [Priority Gas Auctions](https://www.mev.wiki/terms-and-concepts/priority-gas-auctions) that would otherwise be unwinnable when relying on public RPCs alone


# Mempool Stream

Mempool data on Avalanche C-Chain is only shared amongst validator nodes.  When any node receives a pending transaction, it is randomly gossiped to ten other validator nodes in its peer list regardless of stake.\
\
The Mempool Stream solves three core problems for users:

1. Technical: Running a validator is technically difficult and time consuming
2. Capital: Each validator node requires 2000 AVAX to be staked
3. Infrastructure: A single validator node does not provide an accurate view of the mempool

The Mempool Stream solves these problems by aggregating all pending transactions from Snowsight's validator node network and streamlines them into one source for users. &#x20;

Upon connecting to Snowsight, users must authenticate the connection by sending their `'signed_key'`  to the server.  After this, Snowsight will authenticate the connection and begin streaming pending transactions.

The user has the option whether to receive a notification when pending transactions are finalized into a real block by passing a true or false flag in the `'include_finalized'` field when authenticating the connection .  This allows the user to measure the amount of time a transaction was in the mempool before being finalized into a block.&#x20;


# web3.py

python

```python
import websocket
from web3 import Web3
from eth_account.signers.local import LocalAccount
from eth_account.account import Account
from eth_account.messages import encode_defunct
import json

# get private key from the wallet you paid with
with open(KEY_FILE_PATH) as keyfile:
    priv_key = web3.eth.account.decrypt(keyfile.read(), 'key_file_pw')

# sign key string with your signing account wallet
msg = "Sign this message to authenticate your wallet with Snowsight."
encoded_msg = encode_defunct(text=message)
signed_msg = web3.eth.account.sign_message(encoded_msg, priv_key)

# make websocket connection and send 'signed_key'
ws = websocket.create_connection("ws://mempool-stream.snowsight.chainsight.dev:8589")
packet = {'signed_key': signed_message.signature.hex(), 'include_finalized': True}
ws.send(json.dumps(packet))

# on success, first message will contain {'status': 'authenticated'}
resp = json.loads(ws.recv())
if resp['status'] == 'authenticated': {
    # < do shadowy super coder stuff here >
}
```


# ethers.js

javascript

```javascript
const provider = new ethers.providers.StaticJsonRpcProvider(AVAX_RPC_URL);
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);

// Sign in
const key = "Sign this message to authenticate your wallet with Snowsight.";
const signed_key = await wallet.signMessage(key);

// Open websocket
const ws = new WebSocket("ws://mempool-stream.snowsight.chainsight.dev:8589");

ws.on("open", () => {
  console.log("Ws::open");
  ws.send(JSON.stringify({ signed_key: signed_key, include_finalized: true }));
});

ws.on("message", async (data: any) => {
  if (data.status === "authenticated") {
    console.log("Ws::authenticated");
  }
  console.log("received: %s", data);
});
```


# ethers.rs

rust

coming soon (if you have a working example, please share on telegram!)


# Transaction Propagator

The Transaction Propagator is an HTTP endpoint that users may leverage to propagate their transactions faster through Avalanche C-Chain.  Any transactions recevied by the Transaction Propagator are immediately relayed throughout Snowsight's validator node network, resulting in  much better performance than a public RPC.

Similar to the Mempool Stream, users must authenticate by sending their `'signed_key'` in the same payload that contains their transaction.&#x20;


# web3.py

```python
import requests
import json
from web3 import Web3
from eth_account.signers.local import LocalAccount

# get web3 provider
web3 = Web3(Web3.IPCProvider())

# get private key from the wallet you paid with
with open(KEY_FILE_PATH) as keyfile:
    private_key = web3.eth.account.decrypt(keyfile.read(), 'key_file_pw')
    
# generate account from private key
BASE_ACCOUNT_FROM: LocalAccount = Account.from_key(private_key)

# sign key string with your signing account wallet
message = "Sign this message to authenticate your wallet with Snowsight."
encoded_message = encode_defunct(text=message)
signed_message = web3.eth.account.sign_message(encoded_message, private_key=private_key)

# create and sign transaction
tx = {
        'from': Web3.toChecksumAddress(BASE_ACCOUNT_FROM.address), 'nonce': web3.eth.getTransactionCount(BASE_ACCOUNT_FROM.address), 'value': 0,
        'to': Web3.toChecksumAddress(BASE_ACCOUNT_FROM.address),
        'gas': int(23000 + 3000),
        'chainId': 43114
    }
signed = web3.eth.account.sign_transaction(tx, private_key)

# send 'signed_key' and 'raw_tx'
packet = {'signed_key': signed_message.signature.hex(), 'raw_tx': signed.rawTransaction.hex()}
r = requests.post('http://tx-propagator.snowsight.chainsight.dev:8081', data=json.dumps(packet))
```


# ethers.js

javascript

coming soon (if you have a working example, please share on telegram!)


# ethers.rs

rust

coming soon (if you have a working example, please share on telegram!)


# Tiers

Snowsight provides three tiers available to users:

1. Premium Tier\*
   * Lowest latency connection to mempool, with up to 3 concurrent websocket connections
   * Highest priority access for institutional users
   * **𝛿** (pending -> finalized): \~2.1 s
   * Per-block pending transaction coverage: >95%
2. Standard Tier
   * Generic connection to mempool, lower priority than Premium Tier
   * **𝛿** (pending -> finalized): \~900 ms
   * Per-block pending transaction coverage: \~75%
3. Trial Tier
   * No cost connection to mempool
   * Lowest priority access, mempool access can be delayed and no pending transaction coverage guarantees

\*only Premium Tier is eligible to use the Transaction Propagator&#x20;


# Payment

Snowsight is not a free service; users pay for access to Snowsight in AVAX for a user-determined length of time.

Payment amount is determined by the amount of time a user desires to activate the service.  The payment rate may be adjusted by Chainsight over time depending on market conditions.  However, payments are always kept at the payment rate from which they were received by the smart contract.

The easiest way to pay for access to Snowsight is through the [web interface](https://snowsight.chainsight.dev), on the 'Manage' tab. &#x20;

More advanced users may pay for access by calling the pay() function on the SnowsightManager contract, at address [0x727Dc3C412cCb942c6b5f220190ebAB3eFE0Eb93](https://snowtrace.io/address/0x727Dc3C412cCb942c6b5f220190ebAB3eFE0Eb93).


# Authentication

Upon connection to the service, Snowsight expects a message to be sent in JSON format with a key-value pair named 'signed\_key' containing a signed message in [EIP-191 compatible format](https://eips.ethereum.org/EIPS/eip-191) from the wallet that paid for access to Snowsight.

Example signed key:

```json
"signed_key": "0x65233a17f581aa0404f1867560d3a2ee1a654596fc71c6c4f3706d1bd9b7da6873a3f0b153f7da5d55e04af9c8e67003d178134fb28c2aa8695f9c0227fe13a21b"
```

This allows Snowsight to confirm that your external address has paid the appropriate amount of AVAX to accept the connection.

The Services section contains code snippets of how to generate the signed key for your desired service.


# Tips

*My ping is high, wat do?*\
Run your bot in AWS US-east-1. Ping will be less than 150 μs.

*How many nodes does Snowsight have?*\
We are running \[redacted] nodes. Revealing too much about our backend at this time would result in loss of competitive advantage. Our backend aggregates many nodes p2p in pure Rust for best performance.

*Where do I start, I'm confused?*\
Check out [@BowTieDevil](https://twitter.com/BowTiedDevil)'s excellent blog series on Snowsight:\
<https://degencode.substack.com/p/project-snowsight-arbitrage-bot?s=r>


# Contracts

SnowsightManager.sol

[0x727Dc3C412cCb942c6b5f220190ebAB3eFE0Eb93](https://snowtrace.io/address/0x727dc3c412ccb942c6b5f220190ebab3efe0eb93#code)

```
/**
 *Submitted for verification at snowtrace.io on 2022-05-12
*/

//  ▄▄▄▄▄▄▄▄▄▄▄  ▄         ▄  ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄▄  ▄▄        ▄  ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄▄  ▄         ▄  ▄▄▄▄▄▄▄▄▄▄▄       ▄            ▄▄▄▄▄▄▄▄▄▄▄  ▄▄▄▄▄▄▄▄▄▄   ▄▄▄▄▄▄▄▄▄▄▄
//  ▐░░░░░░░░░░░▌▐░▌       ▐░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░▌      ▐░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░▌       ▐░▌▐░░░░░░░░░░░▌     ▐░▌          ▐░░░░░░░░░░░▌▐░░░░░░░░░░▌ ▐░░░░░░░░░░░▌
//  ▐░█▀▀▀▀▀▀▀▀▀ ▐░▌       ▐░▌▐░█▀▀▀▀▀▀▀█░▌ ▀▀▀▀█░█▀▀▀▀ ▐░▌░▌     ▐░▌▐░█▀▀▀▀▀▀▀▀▀  ▀▀▀▀█░█▀▀▀▀ ▐░█▀▀▀▀▀▀▀▀▀ ▐░▌       ▐░▌ ▀▀▀▀█░█▀▀▀▀      ▐░▌          ▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀▀▀
//  ▐░▌          ▐░▌       ▐░▌▐░▌       ▐░▌     ▐░▌     ▐░▌▐░▌    ▐░▌▐░▌               ▐░▌     ▐░▌          ▐░▌       ▐░▌     ▐░▌          ▐░▌          ▐░▌       ▐░▌▐░▌       ▐░▌▐░▌
//  ▐░▌          ▐░█▄▄▄▄▄▄▄█░▌▐░█▄▄▄▄▄▄▄█░▌     ▐░▌     ▐░▌ ▐░▌   ▐░▌▐░█▄▄▄▄▄▄▄▄▄      ▐░▌     ▐░▌ ▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄█░▌     ▐░▌          ▐░▌          ▐░█▄▄▄▄▄▄▄█░▌▐░█▄▄▄▄▄▄▄█░▌▐░█▄▄▄▄▄▄▄▄▄
//  ▐░▌          ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌     ▐░▌     ▐░▌  ▐░▌  ▐░▌▐░░░░░░░░░░░▌     ▐░▌     ▐░▌▐░░░░░░░░▌▐░░░░░░░░░░░▌     ▐░▌          ▐░▌          ▐░░░░░░░░░░░▌▐░░░░░░░░░░▌ ▐░░░░░░░░░░░▌
//  ▐░▌          ▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀█░▌     ▐░▌     ▐░▌   ▐░▌ ▐░▌ ▀▀▀▀▀▀▀▀▀█░▌     ▐░▌     ▐░▌ ▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀█░▌     ▐░▌          ▐░▌          ▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀█░▌ ▀▀▀▀▀▀▀▀▀█░▌
//  ▐░▌          ▐░▌       ▐░▌▐░▌       ▐░▌     ▐░▌     ▐░▌    ▐░▌▐░▌          ▐░▌     ▐░▌     ▐░▌       ▐░▌▐░▌       ▐░▌     ▐░▌          ▐░▌          ▐░▌       ▐░▌▐░▌       ▐░▌          ▐░▌
//  ▐░█▄▄▄▄▄▄▄▄▄ ▐░▌       ▐░▌▐░▌       ▐░▌ ▄▄▄▄█░█▄▄▄▄ ▐░▌     ▐░▐░▌ ▄▄▄▄▄▄▄▄▄█░▌ ▄▄▄▄█░█▄▄▄▄ ▐░█▄▄▄▄▄▄▄█░▌▐░▌       ▐░▌     ▐░▌          ▐░█▄▄▄▄▄▄▄▄▄ ▐░▌       ▐░▌▐░█▄▄▄▄▄▄▄█░▌ ▄▄▄▄▄▄▄▄▄█░▌
//  ▐░░░░░░░░░░░▌▐░▌       ▐░▌▐░▌       ▐░▌▐░░░░░░░░░░░▌▐░▌      ▐░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░▌       ▐░▌     ▐░▌          ▐░░░░░░░░░░░▌▐░▌       ▐░▌▐░░░░░░░░░░▌ ▐░░░░░░░░░░░▌
//  ▀▀▀▀▀▀▀▀▀▀▀  ▀         ▀  ▀         ▀  ▀▀▀▀▀▀▀▀▀▀▀  ▀        ▀▀  ▀▀▀▀▀▀▀▀▀▀▀  ▀▀▀▀▀▀▀▀▀▀▀  ▀▀▀▀▀▀▀▀▀▀▀  ▀         ▀       ▀            ▀▀▀▀▀▀▀▀▀▀▀  ▀         ▀  ▀▀▀▀▀▀▀▀▀▀   ▀▀▀▀▀▀▀▀▀▀▀
//
//  .----------------. .-----------------..----------------. .----------------. .----------------. .----------------. .----------------. .----------------. .----------------.
//  | .--------------. | .--------------. | .--------------. | .--------------. | .--------------. | .--------------. | .--------------. | .--------------. | .--------------. |
//  | |    _______   | | | ____  _____  | | |     ____     | | | _____  _____ | | |    _______   | | |     _____    | | |    ______    | | |  ____  ____  | | |  _________   | |
//  | |   /  ___  |  | | ||_   \|_   _| | | |   .'    `.   | | ||_   _||_   _|| | |   /  ___  |  | | |    |_   _|   | | |  .' ___  |   | | | |_   ||   _| | | | |  _   _  |  | |
//  | |  |  (__ \_|  | | |  |   \ | |   | | |  /  .--.  \  | | |  | | /\ | |  | | |  |  (__ \_|  | | |      | |     | | | / .'   \_|   | | |   | |__| |   | | | |_/ | | \_|  | |
//  | |   '.___`-.   | | |  | |\ \| |   | | |  | |    | |  | | |  | |/  \| |  | | |   '.___`-.   | | |      | |     | | | | |    ____  | | |   |  __  |   | | |     | |      | |
//  | |  |`\____) |  | | | _| |_\   |_  | | |  \  `--'  /  | | |  |   /\   |  | | |  |`\____) |  | | |     _| |_    | | | \ `.___]  _| | | |  _| |  | |_  | | |    _| |_     | |
//  | |  |_______.'  | | ||_____|\____| | | |   `.____.'   | | |  |__/  \__|  | | |  |_______.'  | | |    |_____|   | | |  `._____.'   | | | |____||____| | | |   |_____|    | |
//  | |   *******    | | |   *******    | | |   *******    | | |   *******    | | |   *******    | | |    *******   | | |   *******    | | |    *******   | | |   *******    | |
//  | '--------------' | '--------------' | '--------------' | '--------------' | '--------------' | '--------------' | '--------------' | '--------------' | '--------------' |
//  '----------------' '----------------' '----------------' '----------------' '----------------' '----------------' '----------------' '----------------' '----------------'
//
//  Version: 2
//  May 9, 2022
//


pragma solidity 0.8.13;

abstract contract IERC20
{
    function transfer(address to, uint value) virtual external returns (bool);
}

struct PaymentTierSettings
{
    uint256 ratePerSecond;
    uint256 timeMaximum;
    uint256 timeMinimum;
    bool enabled;
}

struct PaymentEntry
{
    bool exists;
    uint endTimestamp;
}

contract SnowsightManager
{
    address public admin;
    address public feeCollector;
    uint8 public constant version = 2;

    enum paymentTierTypes 
    {
        FREE,
        STANDARD,
        PRIORITY
    }
    uint constant numPaymentTiers = 3;
    PaymentTierSettings[numPaymentTiers] public tierSettings;

    mapping(address => PaymentEntry[numPaymentTiers]) public payments;

    event AccountPayment(address account, uint256 payment, uint256 endTimestamp);

    constructor()
    {
        admin = msg.sender;
        feeCollector = msg.sender;
        for (uint i = 0 ; i < numPaymentTiers; i++)
        {
            tierSettings[i].enabled = true;
        }

        tierSettings[uint(paymentTierTypes.FREE)].ratePerSecond = 1;
        tierSettings[uint(paymentTierTypes.STANDARD)].ratePerSecond = 3360215053763; 
        tierSettings[uint(paymentTierTypes.PRIORITY)].ratePerSecond = 12445240939864;

        tierSettings[uint(paymentTierTypes.FREE)].timeMaximum = 1 days;
        tierSettings[uint(paymentTierTypes.STANDARD)].timeMaximum = 60 days;
        tierSettings[uint(paymentTierTypes.PRIORITY)].timeMaximum = 60 days;

        tierSettings[uint(paymentTierTypes.FREE)].timeMinimum = 1 days;
        tierSettings[uint(paymentTierTypes.STANDARD)].timeMinimum = 3 days;
        tierSettings[uint(paymentTierTypes.PRIORITY)].timeMinimum = 3 days;
    }

    modifier onlyValidTier(uint tier) {
       require (tier < numPaymentTiers);
        _;
    }

    modifier onlyAdmin {
        require (msg.sender == admin, "ERR_ADMIN_PROTECT");
        _;
    }

    // Function to receive Ether. msg.data must be empty
    receive() external payable {}

    // Fallback function is called when msg.data is not empty
    fallback() external payable {}

    function setAdmin(address payable newAdmin) external onlyAdmin
    {
        admin = newAdmin;
    }

    function setFeeCollector(address payable newfeeCollector) external onlyAdmin
    {
        feeCollector = newfeeCollector;
    }

    function setPaymentsEnabled(uint tier, bool enabled) external onlyAdmin onlyValidTier(tier)
    {
        tierSettings[tier].enabled = enabled;
    }

    function setPaymentRatePerSecond(uint tier, uint256 newPaymentRatePerSecond) external onlyAdmin onlyValidTier(tier)
    {
        tierSettings[tier].ratePerSecond = newPaymentRatePerSecond;
    }

    function setMaximumPaymentTime(uint tier, uint256 newPaymentTimeMaximum) external onlyAdmin onlyValidTier(tier)
    {
        tierSettings[tier].timeMaximum = newPaymentTimeMaximum;
    }

    function setMinimumPaymentTime(uint tier, uint256 newPaymentTimeMinimum) external onlyAdmin onlyValidTier(tier)
    {
        tierSettings[tier].timeMinimum = newPaymentTimeMinimum;
    }

    function calculateMaxPayment(uint tier) public onlyValidTier(tier) view returns (uint256)
    {
        return calculate_max_payment(msg.sender, tier);
    }

    function calculateMinPayment(uint tier) public onlyValidTier(tier) view returns (uint256)
    {
        return tierSettings[tier].timeMinimum * tierSettings[tier].ratePerSecond;
    }

    function calculatePaymentByTierAndTime(uint tier, uint256 _seconds) public onlyValidTier(tier) view returns (uint256)
    {
        uint256 payment = _seconds * tierSettings[tier].ratePerSecond;

        require (payment <= calculate_max_payment(msg.sender, tier), "ERROR_PAYMENT_TOO_LARGE");
        require (payment >= tierSettings[tier].timeMinimum * tierSettings[tier].ratePerSecond, "ERROR_PAYMENT_TOO_SMALL");

        return payment;
    }

    function calculate_max_payment(address payer, uint tier) internal view returns (uint256)
    {
        uint256 maxPayment = 0;

        if (payments[payer][tier].exists && payments[payer][tier].endTimestamp > block.timestamp)
        {
            maxPayment = (tierSettings[tier].timeMaximum - (payments[payer][tier].endTimestamp - block.timestamp)) * tierSettings[tier].ratePerSecond;
        }
        else
        {
            maxPayment = tierSettings[tier].timeMaximum * tierSettings[tier].ratePerSecond;
        }

        return maxPayment;
    }

    function pay(uint tier) external payable onlyValidTier(tier)
    {
        require (tierSettings[tier].enabled == true, "ERROR_PAYMENTS_DISABLED");
        require (msg.value <= calculate_max_payment(msg.sender, tier), "ERROR_PAYMENT_TOO_LARGE");
        require (msg.value >= calculateMinPayment(tier), "ERROR_PAYMENT_TOO_SMALL");

        if (payments[msg.sender][tier].exists)
        {
            if (payments[msg.sender][tier].endTimestamp > block.timestamp)
            {
                // account has a payment active
                payments[msg.sender][tier].endTimestamp = payments[msg.sender][tier].endTimestamp + (msg.value / tierSettings[tier].ratePerSecond);
            }
            else
            {
                // account exists, but payment expired
                payments[msg.sender][tier].endTimestamp = block.timestamp + (msg.value / tierSettings[tier].ratePerSecond);
            }
        }
        else
        {
            // account does not exist yet
            payments[msg.sender][tier].exists = true;
            payments[msg.sender][tier].endTimestamp = block.timestamp + (msg.value / tierSettings[tier].ratePerSecond);
        }

        emit AccountPayment(msg.sender, msg.value, payments[msg.sender][tier].endTimestamp);
        payable(feeCollector).transfer(msg.value);
    }

    function grant(address[] calldata addresses, uint256[] calldata timestamps, uint8[] calldata tiers) onlyAdmin external
    {
        for (uint i = 0; i < addresses.length; i++)
        {
            if (payments[addresses[i]][tiers[i]].exists == false)
            {
                payments[addresses[i]][tiers[i]].exists = true;
            }

            payments[addresses[i]][tiers[i]].endTimestamp = timestamps[i];
        }
    }

    function transferEth(uint256 amount) onlyAdmin external
    {
        payable(msg.sender).transfer(amount);
    }

    function transferToken(address tokenAddress, uint256 amount) onlyAdmin external
    {
        IERC20 token = IERC20(tokenAddress);
        token.transfer(msg.sender, amount);
    }
}
```


# About

@ChainsightLabs - MEV experts creating mempool-aware software

{% embed url="<https://snowsight.chainsight.dev>" %}
Snowsight App
{% endembed %}

{% embed url="<https://t.me/snowsight_avax>" %}
Join us on Telegram
{% endembed %}

{% embed url="<https://twitter.com/ChainsightLabs>" %}


