bmresearch / Solnet

Solana's .NET SDK and integration library.
https://blockmountain.io/Solnet
MIT License
322 stars 130 forks source link

[Bug] invalid transaction: Transaction failed to sanitize accounts offsets correctly (signature verification failure) #468

Closed DanyLinuxoid closed 2 weeks ago

DanyLinuxoid commented 4 weeks ago

Describe the bug I'm getting swap txn from an API, txn is already signed and I have to sign it on my end as well in order to submit to an rpc. But when I try to sign it in any way, I get "invalid transaction: Transaction failed to sanitize accounts offsets correctly"

To Reproduce From https://docs.bloxroute.com/solana/trader-api-v2/api-endpoints/raydium/create-swap-transaction-amm There is an example:

curl -X 'POST' \
  -H "Authorization: $AUTH_HEADER" \
  -H 'Content-Type: application/json' \
  'https://ny.solana.dex.blxrbdn.com/api/v2/raydium/swap' \
  -d '{
  "ownerAddress": "AFT8VayE7qr8MoQsW3wHsDS83HhEvhGWdbNSHRKeUDfQ",
  "inToken": "SOL",
  "outToken": "USDC",
  "inAmount": 0.1,
  "slippage": 0.001
}'

Which will return

{
    "transactions":[
      {
        "content": "txn_base64",
        "isCleanup": false
      }
    ],
    "outAmount":3.296703,
    "outAmountMin":0.032966,
    "priceImpact":{
        "percent":4.5386233671508175e-7,
        "infinity":"INF_NOT"
    },
    "fee":[{
        "amount":0.00025,
        "mint":"",
        "percent":0
    }]
}

My code that is failing in C#:

            var txn = await _bloxRouteApiLogic.GetRaydiumSwapTransaction(
                "2WWkjFr3iAZ2uhLZkhLwThNyvE45FHc7BH1fwSam667y",
                "So11111111111111111111111111111111111111112",
                "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
                0.001M,
                0,
                100000);
            string cleanTxn = txn.transactions[0].content.ToString();
            var rpc = SolanaHelper.GetRpc("https://solana-api.instantnodes.io/token-uAUp4SnNaZzTAOGszRfpl7FjpFidLf4T");
            var owner = new Account("PRIV_KEY", "PUB_KEY");
            byte[] decodedTxn = Convert.FromBase64String(cleanTxn);
            var transaction = Transaction.Deserialize(cleanTxn);
            transaction.Sign(owner);
            var sim = await rpc.SimulateTransactionAsync(transaction.Serialize(), sigVerify: true);

FULLY WORKING EXAMPLE (Private key, Public key, Rpc, and Authorization token should be filled)

using Newtonsoft.Json;

using Solnet.Rpc;
using Solnet.Rpc.Models;

using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

public class RaydiumSwap
{
    private static readonly HttpClient client = new HttpClient();

    public static async Task<dynamic> PerformRaydiumSwap(string authHeader)
    {
        var url = "https://ny.solana.dex.blxrbdn.com/api/v2/raydium/swap";
        var jsonPayload = @"{
            ""ownerAddress"": ""AFT8VayE7qr8MoQsW3wHsDS83HhEvhGWdbNSHRKeUDfQ"",
            ""inToken"": ""SOL"",
            ""outToken"": ""USDC"",
            ""inAmount"": 0.1,
            ""slippage"": 0.001
        }";
        client.DefaultRequestHeaders.Clear();
        client.DefaultRequestHeaders.Add("Authorization", authHeader);
        client.DefaultRequestHeaders.Add("Accept", "application/json");
        var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");

        try
        {
            var response = await client.PostAsync(url, content);
            response.EnsureSuccessStatusCode();
            var responseBody = await response.Content.ReadAsStringAsync();
            return JsonConvert.DeserializeAnonymousType(responseBody, new object());
        }
        catch (HttpRequestException e)
        {
            return $"Error: {e.Message}";
        }
    }

    public static async Task Main(string[] args)
    {
        // Fill only these
        string privKey = "";
        string pubKey = "";
        string authHeader = "";
        string rpc = "";

        var txn = await PerformRaydiumSwap(authHeader);
        string cleanTxn = txn.transactions[0].content.ToString();
        IRpcClient rpc = ClientFactory.GetClient(rpc);
        var owner = new Solnet.Wallet.Account(privKey, pubKey);
        byte[] decodedTxn = Convert.FromBase64String(cleanTxn);
        var transaction = Transaction.Deserialize(cleanTxn);
        transaction.Sign(owner);
        var sim = await rpc.SimulateTransactionAsync(transaction.Serialize(), sigVerify: true);
        var a = 0; // Break point
    }
}

Expected behavior Should not be getting "invalid transaction: Transaction failed to sanitize accounts offsets correctly"

Desktop (please complete the following information):

Additional context I'm successfully using Raydium + Jupiter + Jito + other API's to sign and execute transactions. But since this API is signing txn on it's own on their end, I'm having issues (for the first time).

If this is already known bug or something like that, then please guide me on how to resolve that and then issue can be closed.

BifrostTitan commented 3 weeks ago

You can get this error from signing the tx with too many signers or not enough. You want to pass the transaction message to the signer and populate a transaction from it. Not suppose to use Transaction.Deserialize unless its a completed transaction that has already been signed.

Account trader = Account.FromSecretKey("");
 Transaction swap = Transaction.Populate(Message.Deserialize(swapBytes));
 bytes[] tx = swap.Build(trader);
 //Send the tx byte array as a raw transaction to the rpc

Check out the c# client for Raydium. Available on nuget as well https://github.com/Bifrost-Technologies/Solnet.Raydium

Solnet.Raydium Quickstart:

using Solnet.Programs.Utilities;
using Solnet.Raydium.Client;
using Solnet.Raydium.Types;
using Solnet.Rpc;
using Solnet.Wallet;

IRpcClient connection = ClientFactory.GetClient("RPC LINK HERE");
RaydiumAmmClient raydiumAmmClient = new RaydiumAmmClient(connection);

Account trader = Account.FromSecretKey("SECRET KEY HERE");

//SOL in lamports as the amountIn
//Minimum out can be 0 to always execute no matter what or set it specifically to apply a fixed slippage rate
var swap_test = await raydiumAmmClient.SendSwapAsync(new PublicKey("POOL ADDRESS HERE"), SolHelper.ConvertToLamports(0.01m), 0, OrderSide.Buy, trader, trader);

Console.WriteLine(swap_test.RawRpcResponse.ToString());
Console.ReadKey();