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

Signing a transaction, what is wrong here? #470

Open
discusser231 opened this issue Nov 19, 2024 · 12 comments
Open

Signing a transaction, what is wrong here? #470

discusser231 opened this issue Nov 19, 2024 · 12 comments

Comments

@discusser231
Copy link

discusser231 commented Nov 19, 2024

So I am trying to implement Jupiter Swap via their documentation and I have created the code as below, as per the swap documentation from the official Jupiter website.

       var quote = await GetQuoteResponseAsync(fromAddress, toAddress, amountLamperts, slippage);
       var swapResponse = await GetSwapResponseAsync(quote, slippage);
       var swapTransactionBuf = Convert.FromBase64String(swapResponse.SwapTransaction);
       var transaction = VersionedTransaction.Deserialize(swapTransactionBuf);
       var signed = transaction.Sign(Account);
       var serialziedTx = transaction.Serialize();
       return await _connection.SendTransactionAsync(serialziedTx,true);

In retrospect, this is the simplified version of the code. When attempting to sign, I get "invalid transaction: Transaction failed to sanitize accounts offsets correctly". I would just like to know what I am doing wrong here? The account property is just obtaining an account from a private key Account.FromSecretKey(myKeyHere)

There can be absolutely nothing wrong implementation wise with my code, as it's pretty much identical to the documentation. Does the library process things a little bit differently than the official sdk? I'm finding it hard to believe it's an issue on my end.

I also forgot to mention I diffed my payload sent to the api, it was identical comparing it to their one on the official web frontend site. So there's nothing wrong with the quote & swap response.

@BifrostTitan
Copy link
Contributor

The response is a transaction message not a compiled transaction. Try deserializing the message and populate a new transaction object.
More details on how to do it here:
#468

@discusser231
Copy link
Author

The response is a transaction message not a compiled transaction. Try deserializing the message and populate a new transaction object. More details on how to do it here: #468

          var swapTransactionBuf = Convert.FromBase64String(swapResponse.SwapTransaction);
          var message = Message.Deserialize(swapTransactionBuf);
          var swap = Solnet.Rpc.Models.Transaction.Populate(message);
          var txBytes = swap.Build(Account);

Attempting to do it your way, as done in the mentioned issue. When attempting to populate the transaction, I get System.ArgumentOutOfRangeException: 'Index was out of range. Must be non-negative and less than the size of the collection. Arg_ParamName_Name'

Upon checking the message after deserializing, it is empty.

AccountKeys, Instructions are both 0. Recent block hash is set to "11111111111111111111111111111111" but when deserializing with VersionedTransaction everything shows correctly. Trying to deserialize as a VersionedMessage prompts that it's a legacy transaction. This tells me something has gone wrong with deserialization. Now I have no idea what the problem could be and I'd appreciate if you could help me a little bit with some guidance.

When we deserialize the transaction, it shows all the instructions and everything as it should. I just don't get why deserializing as a message would not show anything. Are messages and transactions two separate things? So if Jupiter returns a transaction, then that would mean in the block of bytes, it would have an offset of [message] and [transaction metadata]. If that's the case, then how would I go about doing this?

       var transaction = VersionedTransaction.Deserialize(swapTransactionBuf);
       var message = VersionedMessage.Deserialize(transaction.CompileMessage());
       var swap = Solnet.Rpc.Models.VersionedTransaction.Populate(message);
       var txBytes = swap.Build(Account);

This works, but in the end I get Program Error: "Instruction # 4 Failed - Cross-program invocation with unauthorized signer or writable account" most likely because it didn't sign correctly. I'm just confused. The examples I see with web3 solana and solnet, are like contradictory in their own sense. Most likely because I don't understand solana all that well (very new to the space). I'd just like to know what I'm doing wrong here.

@BifrostTitan
Copy link
Contributor

Have you tried signing it as a legacy transaction?

From their documentation website:
"V4 uses Versioned Transactions and Address Lookup Tables

All Jupiter swaps are now only a single transaction. Not all wallets support Versioned Transactions yet, so if you detect a wallet that does not support versioned transactions you may request a legacy transaction instead from the API."

Recommend testing legacy transaction signing to determine if its an issue with the type of transaction. If both dont work then its an issue with how you are signing it or serializing it to wire format

@discusser231
Copy link
Author

Have you tried signing it as a legacy transaction?

From their documentation website: "V4 uses Versioned Transactions and Address Lookup Tables

All Jupiter swaps are now only a single transaction. Not all wallets support Versioned Transactions yet, so if you detect a wallet that does not support versioned transactions you may request a legacy transaction instead from the API."

Recommend testing legacy transaction signing to determine if its an issue with the type of transaction. If both dont work then its an issue with how you are signing it or serializing it to wire format

I have tried with asLegacyTransaction to true

When parsing it as a real message I get "this is a version transaction please use versioned transaction" and then if Ignored, I get "System.ArgumentOutOfRangeException: 'Index was out of range. Must be non-negative and less than the size of the collection. Arg_ParamName_Name'".

I am very certain, I am doing nothing wrong here. I followed the documentation of jupiter on typescript and it worked flawlessly, no issues. There is most certainly a problem with solnet and how it deserializes transactions. While doing a workaround I get it to send but it says program panicked. Most likely because it didn't deserialize the transaction properly as it says it's a versioned transaction, but in reality it is not.

Please, by all means give it a try yourself. I can give you the exact endpoints and steps if you really need it. Either I am really stupid, or there's a bug in solnet somewhere. Since how come I can get it to work with typescript but not solnet? Makes no sense to me.

@shihanhana
Copy link

This library does not support versioned transactions and requires your own code changes to support them.

@discusser231
Copy link
Author

discusser231 commented Nov 21, 2024

This library does not support versioned transactions and requires your own code changes to support them.

Even if we send it as a legacy transaction (asLegacyTransaction = true), it does not parse the transaction as a normal transaction. It says it's a versioned transaction. There is something flawed with solnet it seems. If Jup returns a legacy transaction, then the transaction should be treated as such. But it treats it as a versioned transaction regardless. I don't know the specifics of solana and how it's meant to work, but it seems to me like they need to address this.

@BifrostTitan
Copy link
Contributor

The library supports Versioned and Legacy transactions. Use the default Transaction class to populate a legacy transaction instead of using the VersionedTransaction class. There is two of them.

Legacy signing for any program should work fine so you must be missing something in the transaction. Use legacy in the API and attempt to deserialize it as a message instead of a transaction. I will take a look later today to see what the issue is

@discusser231
Copy link
Author

The library supports Versioned and Legacy transactions. Use the default Transaction class to populate a legacy transaction instead of using the VersionedTransaction class. There is two of them.

Legacy signing for any program should work fine so you must be missing something in the transaction. Use legacy in the API and attempt to deserialize it as a message instead of a transaction. I will take a look later today to see what the issue is

Yes that is what I am trying to say.

I tried using Solnet.Rpc.Models.Transaction.Deserialize and it returns a VersionedTransaction. Trying to sign it gives the same offset error. If I clear the signatures from the transaction and re-sign it, it will work fine and send the transaction but then you will get an error, this error is because the programID is incorrect. When comparing the legacy transaction on jup (web ui) and from solnet, it is missing another instruction that gives the program id.

Here are some tries with asLegacyTransaction = true to the Jup protocol. So Jup is sending the leegacy messages (according to their documentation)

 //Try one
 var transaction = Solnet.Rpc.Models.Transaction.Deserialize(swapTransactionBuf);
 transaction.Signatures.Clear(); //this works but will throw an error in the tx
 var signed = transaction.Sign(Account); //will return false if signature isnt cleared
 
 //Try Two
var message = Message.Deserialize(swapTransactionBuf); //parses but the  message object is empty
var swap = Solnet.Rpc.Models.Transaction.Populate(message);//System.ArgumentOutOfRangeException: 'Index was out of range. Must be non-negative and less than the size of the collection. Arg_ParamName_Name'
var txBytes = swap.Build(Account);

 //Try three
var transaction = Solnet.Rpc.Models.Transaction.Deserialize(swapTransactionBuf);
var message = Message.Deserialize(transaction.CompileMessage()); //System.NotSupportedException: 'The message is a VersionedMessage, use VersionedMessage.Deserialize instead.'
var swap = Solnet.Rpc.Models.Transaction.Populate(message);
var txBytes = swap.Build(Account);

I could go on, but basically no matter what you will try, it will fail one way or the other. I am finding it hard to believe that I am the problem. Unless I am missing another Transaction class in another namespace, I still stand by my words.

@BifrostTitan
Copy link
Contributor

Jupiter swap has been implemented into the Unity branch of Solnet already using the same classes its where the VersionedTransaction class is referenced from.
https://github.com/magicblock-labs/Solana.Unity-Core/blob/master/src/Solana.Unity.Dex/Jupiter/JupiterDex.cs

@discusser231
Copy link
Author

Jupiter swap has been implemented into the Unity branch of Solnet already using the same classes its where the VersionedTransaction class is referenced from. https://github.com/magicblock-labs/Solana.Unity-Core/blob/master/src/Solana.Unity.Dex/Jupiter/JupiterDex.cs

Thanks for referencing this. I have copied down the code he uses for signing and initiated a transaction with it. While no errors appear, the transaction does not initiate at all and hangs. A full example of the project can be found here https://magicblock-labs.github.io/Solana.Unity-SDK/ which does similar, it permanently hangs on loading when pressing swap (tested using phantom wallet).

Long story short, the code calls https://github.com/magicblock-labs/Solana.Unity-SDK/blob/8b35d38c62f4236068ad873b9342d0230c85459c/Runtime/codebase/WalletBase.cs#L252

Now if his implementation was correct, why does it fail to register the transaction on the blockchain? From what I see, he did exactly what I did except for checking for duplicate signatures in the transaction object outside of overriding functions for connected wallets (which in our case we don't need to worry about them).

Now I'm not sure what the exact issue is, but what is the problem? Why's it easy to do in web3solana but not solnet? What exactly is different here that web3solana doesn't have problems with? Yes, I may be signing it incorrectly. But if it incorrect then I'd love your guidance here because it shouldn't have to be this complicated in the first place.

@BifrostTitan
Copy link
Contributor

Most likely does not use the compute budget program so it will never end up on-chain and its why it might stall like that. You would get an error back if there was an issue. Feel free to hop in the discord if you need help.
"Understanding Priority fees"

Solnet is a community built SDK and wont match the schema with the typescript web3 sdks because they are constantly in development. An account in c# is a keypair in typescript which was the original implementation but they changed it to keypair to try to make it easier to understand. These constant changes to the web3 sdk is one of the reasons the legacy maintainers no longer maintain solnet. They changed it again in web3 v2 which is why its pointless to try to keep up with them. Everything should work as intended just dont use their typescript documentation for Solnet because it will only confuse you.

I will play with jupiter swap in a little bit and post my code here if I get it working

@discusser231
Copy link
Author

discusser231 commented Nov 22, 2024

Oh okay, I didn't know there was a discord server. Where can I find it? I don't see any links on the front page of solnet.

Also, yes Jupiter should handle priorty fees themselves. In the post request to swap, I have inserted

PrioritizationFeeLamports = new PrioritizationFeeLamports() {
    PriorityLevelWithMaxLamports = new PriorityLevelWithMaxLamports() {
        MaxLamports = 10000000,
        PriorityLevel = "veryHigh",
        Global = false,
    },
    DynamicComputeUnitLimit = true
},

which works fine. I added this into the python sdk version of the jup implementation as a test, and it ran with no problems. I understand c# and typescript are fairly different, but there's something wrong with the signer. It returns true when I call verify signatures if I clear the signatures and sign it again to the account (to prevent duplicates). It returns true so it should be valid right? But on execution it says otherwise. It's really started to confuse me that's all. I appreciate the patience and it's sad to hear that the original maintainers are no longer here. I totally get it, with constant changes you need a development team to keep it up to date. Community run projects are different and I would love to help if I understood how solana worked inside and out, but I just don't have the time unfortunately.

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

3 participants