-
Notifications
You must be signed in to change notification settings - Fork 166
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
Add send transaction
to TS and Rust code examples
#589
base: main
Are you sure you want to change the base?
Conversation
In this guide, we'll explore how to use the Whirlpools SDK in conjunction with the Solana SDK to send and successfully land transactions on the Solana blockchain. We'll cover key features such as: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perhaps we need a section to explain why the vanila sendAndConfirmTransaction doesn't work reliably and confirm that users can't rely on some wallets' auto-append PF fees (ex. phantom). (i think it's because we appended a signer in our tx somewhere)
@@ -33,7 +33,7 @@ This document covers the essential setup required to start building on Orca’s | |||
Install the necessary packages: | |||
|
|||
```bash | |||
npm install typescript @orca-so/whirlpools @solana/web3.js@rc | |||
npm install typescript @orca-so/whirlpools @solana/web3.js@2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is version 2 now recommended (meaning you can remove the tag)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
npm install @solana/web3.js
installs v1.98.0
</Tabs> | ||
|
||
|
||
#### What is WhirlpoolsConfig? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels kinda weird here. We are talking about sending and landing transactions. Then all of a sudden there is an explanation about what whirlpoolsconfig is
|
||
Here's how the swap instructions and quote are generated: | ||
|
||
<Tabs groupId="programming-languages"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we are generating the instructions in other docs pages already. Would it make sense to just take from there (so you don't have to explain any whirlpool stuff).
So just start from
const instructions = [] // <- your actual instructions
// build into a transaction
// sing transaction
// etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I initially thought to create an all-in-one doc, and I went for openPositionInstructions because it has additional signers. But in the context of the current doc-structure, and the location of this doc, it makes more sense to cut all of that out.
<Tabs groupId="programming-languages"> | ||
<TabItem value="ts" label="Typescript" default> | ||
```tsx title="sendTransaction.ts" | ||
const signedTransaction = await signTransactionMessageWithSigners(transactionMessageWithComputeUnitInstructions) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe good to explain that signTransactionMessageWithSigners
only signs with signers you provide. If you provided a noopSigner (for example for a frontend wallet) you need to still manually sign with that wallet.
|
||
You can get an estimation of the compute units by simulating the transaction on the RPC. To avoid transaction failures caused by underestimating this limit, an additional 100,000 compute units are added, but you can adjust this based on your own tests. | ||
|
||
The prioritization fee incentivizes validators to prioritize your transaction, especially during times of network congestion. You can get a list of recently paid prioritization fees, sorting them and selecting a value from that list. In this example, we select the median of the sorted list. The prioritization fee is provided in micro-lamports per compute unit. The estimated total priority fee is calculated as `estimated compute units * prioritization fee`. However, the actual fee charged will depend on the compute units your transaction ultimately consumes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe explain here that we take the 50th percentile in this example but that you can play around with it.
|
||
You can get an estimation of the compute units by simulating the transaction on the RPC. To avoid transaction failures caused by underestimating this limit, an additional 100,000 compute units are added, but you can adjust this based on your own tests. | ||
|
||
The prioritization fee incentivizes validators to prioritize your transaction, especially during times of network congestion. You can get a list of recently paid prioritization fees, sorting them and selecting a value from that list. In this example, we select the median of the sorted list. The prioritization fee is provided in micro-lamports per compute unit. The estimated total priority fee is calculated as `estimated compute units * prioritization fee`. However, the actual fee charged will depend on the compute units your transaction ultimately consumes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the actual fee charged will depend on the compute units your transaction ultimately consumes
Not 100% sure this is true. I thought it always takes your requested compute units.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought this to be true from previous experiences. But I just tested this and you are right.
### 5: Estimating Compute Unit Limit and Prioritization Fee | ||
When preparing a transaction, it's important to set a compute unit limit and an appropriate prioritization fee. Setting the compute units too low and the transaction will fail. Setting it too high will make the transaction less favorable for validators to process.. | ||
|
||
You can get an estimation of the compute units by simulating the transaction on the RPC. To avoid transaction failures caused by underestimating this limit, an additional 100,000 compute units are added, but you can adjust this based on your own tests. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be good to add here that txs that request fewer compute units get high priority for the same amount of prio fee. Thus lower = better but too low and the tx fails
|
||
let blockHashLifetime = 150n; | ||
let currentBlockHeight = latestBlockHash.value.lastValidBlockHeight - blockHashLifetime; | ||
while (latestBlockHash.value.lastValidBlockHeight >= currentBlockHeight) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think in most places we just have a time-based loop. This is advantageous if you send to multiple rpc nodes because some might be running behind more than others (meaning that rpc.getBlockHeight().send()
is never the actual blockheight of the chain)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I know this. My thinking was that this approach shows how blockhashes work, but it makes more sense to give the example with the time-based loop
} | ||
} | ||
currentBlockHeight = await rpc.getBlockHeight().send(); | ||
await new Promise(resolve => setTimeout(resolve, 100)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rpc.sendTransaction
and rpc.getSignatureStatuses
already take some time to execute (but actual time is not known). Adding the wait here adds on top of that (and 100ms is very low).
If you do a time-based loop you could make sure that for example the transaction is not sent more than 1x per second (instead of this manual wait).
} | ||
``` | ||
</TabItem> | ||
</Tabs> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be nice to add a section about further improvements you could make:
- Sending to multiple rpc nodes concurrently
- Sending to jito block engine and adding a jito tip
- swQoS
- More?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple nits and small remarks but LGTM!
|
||
You can get an estimation of the compute units by simulating the transaction on the RPC. To avoid transaction failures caused by underestimating this limit, an additional 100,000 compute units are added, but you can adjust this based on your own tests. | ||
The prioritization fee per compute unit also incentivizes validators to prioritize your transaction, especially during times of network congestion. You can get a list of recently paid prioritization fees, sort them and select a value from that list. In this example, we select the 50th percentile, but you can adjust this if needed. The prioritization fee is provided in micro-lamports per compute unit. The total priority fee in lamports you will pay is calculated as $(\text{estimated compute units} \cdot \text{prioritization fee}) / 10^6$. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be good to mention that the endpoint returns the lowest priority fee payed of all the transactions that landed in a given slot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, I didn't know this
### 3. Estimating Compute Unit Limit and Prioritization Fee | ||
Before sending a transaction, it's important to set a compute unit limit and an appropriate prioritization fee. | ||
|
||
Transactions that request fewer compute units get high priority for the same amount of prioritization fee (which is defined per compute unit). Setting the compute units too low will result in a failed transaction. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nuance: will result in a failed transaction
-> might result in your transaction not landing/being included in a block and expiring
or somethuing like that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought that if you set the compute units too low, you have a higher chance of it being picked up and included, but you risk transaction failure due to compute unit limit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh right yep compute units too low will fail the transaction. My bad read it wrong
@@ -67,133 +72,17 @@ Let's start by importing the necessary dependencies. | |||
</TabItem> | |||
</Tabs> | |||
|
|||
### 2. Create Transaction Message From Instructions | |||
To send a transaction on Solana, you need to include a blockhash to the transaction. A blockhash acts as a timestamp and ensures the transaction has a limited lifetime. Validators use the blockhash to verify the recency of a transaction before including it in a block. A transaction referencing a blockhash is only valid for 150 blocks (~1-2 minutes, depending on slot time). After that, the blockhash expires, and the transaction will be rejected. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can also sign using a durable nonce. You could add a small note that that is also possible but not what we are using in this example
@@ -330,20 +236,22 @@ Finally, the transaction is signed, encoded, and submitted to the network. A cli | |||
all_instructions.extend(open_position_instructions.instructions); | |||
let message = Message::new(&all_instructions, Some(&wallet.pubkey())); | |||
|
|||
let transaction = Transaction::new(&signers, message, recent_blockhash); | |||
let transaction = Transaction::new(& , message, recent_blockhash); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?
|
||
## Handling transactions with Wallets in web apps. | ||
#### Creating Noop Signers | ||
When sending transactions from your web application, users need to sign the transaction using their wallet. Since the transaction needs to assembled beforehand, you can create a `noopSigner` (no-operation signer). This will act as a placeholder for you instructions, indicating that a given account is a signer and the signature wil be added later. After the user of your web app signs the transaction with their wallet, you need to manually add that signature to the transaction. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think you can just pass the serialized transaction to the wallet extension and they will return a serialized transaction (with the added signature). add the signature manually
might sound a little more complicated than it actually is
When sending transactions from your web application, users need to sign the transaction using their wallet. Since the transaction needs to assembled beforehand, you can create a `noopSigner` (no-operation signer). This will act as a placeholder for you instructions, indicating that a given account is a signer and the signature wil be added later. After the user of your web app signs the transaction with their wallet, you need to manually add that signature to the transaction. | ||
|
||
#### Prioritization Fees | ||
Browser wallets, like Phantom, will calculate and apply priority fees for your transactions, provided: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Browser wallets
-> Some wallets
? Think Phantom app also adds prio fee
Title
Add Docs for Sending and Landing Transactions with Whirlpools SDK in Rust and TS
Details
This PR adds documentation on sending and landing transactions using the Whirlpools SDK and Solana SDK in both Rust and TypeScript.