Skip to content

Commit 3cf441a

Browse files
authored
sam/DX-251 (#795)
* actions * @orca-so actions sdk * updated yarn.lock * updated yarn lock * addresses comments * lock * changeset * new lock * changes web3.js -> kit * formatted * more lint * lint * new yarn lock * small readme update * reverts lint change for whirlpools prog * try 2 rever wpools change * moves actions into regular sdk * rm changeset * changeset * revert cargo * eof * new yarn lock * added typedoc for setPayerFromBytes * added actions to readme for wpools ts sdk
1 parent 89e5f36 commit 3cf441a

File tree

10 files changed

+962
-858
lines changed

10 files changed

+962
-858
lines changed

.changeset/weak-coats-end.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@orca-so/whirlpools": minor
3+
---
4+
5+
Added actions (wrapped with execution logic)

ts-sdk/whirlpool/README.md

Lines changed: 183 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,27 +19,33 @@ npm install @orca-so/whirlpools @solana/kit@2
1919
## Basic Usage
2020

2121
### 1. Wallet Creation
22+
2223
You can [generate a file system wallet using the Solana CLI](https://docs.solanalabs.com/cli/wallets/file-system) and load it in your program.
2324

2425
```tsx
25-
import { createKeyPairSignerFromBytes } from '@solana/kit';
26-
import fs from 'fs';
26+
import { createKeyPairSignerFromBytes } from "@solana/kit";
27+
import fs from "fs";
2728

28-
const keyPairBytes = new Uint8Array(JSON.parse(fs.readFileSync('path/to/solana-keypair.json', 'utf8')));
29+
const keyPairBytes = new Uint8Array(
30+
JSON.parse(fs.readFileSync("path/to/solana-keypair.json", "utf8"))
31+
);
2932
const wallet = await createKeyPairSignerFromBytes(keyPairBytes);
3033
```
3134

3235
### 2. Configure the Whirlpools SDK for Your Network
36+
3337
Orca's Whirlpools SDK supports several networks: Solana Mainnet, Solana Devnet, Eclipse Mainnet, and Eclipse Testnet. To select a network, use the `setWhirlpoolsConfig` function. This ensures compatibility with the network you’re deploying on.
3438

3539
Example: Setting the SDK Configuration to Solana Devnet.
40+
3641
```tsx
37-
import { setWhirlpoolsConfig } from '@orca-so/whirlpools';
42+
import { setWhirlpoolsConfig } from "@orca-so/whirlpools";
3843

39-
await setWhirlpoolsConfig('solanaDevnet');
44+
await setWhirlpoolsConfig("solanaDevnet");
4045
```
4146

4247
Available networks are:
48+
4349
- solanaMainnet
4450
- solanaDevnet
4551
- eclipseMainnet
@@ -48,34 +54,36 @@ Available networks are:
4854
> ℹ️ The setWhirlpoolsConfig function accepts either one of Orca's default network keys or a custom Address. This allows you to specify a custom configuration if needed.
4955
5056
### 3. Create the Swap Instructions
57+
5158
After configuring the SDK, you can perform a swap. Here is an example of how to perform a token swap using the Whirlpools SDK:
5259

5360
```tsx
54-
import { swapInstructions } from '@orca-so/whirlpools';
61+
import { swapInstructions } from "@orca-so/whirlpools";
5562
const poolAddress = "POOL_ADDRESS";
5663
const mintAddress = "TOKEN_MINT";
5764
const amount = 1_000_000n;
5865
const slippageTolerance = 100; // 100 bps = 1%
5966

6067
const { instructions, quote } = await swapInstructions(
61-
devnetRpc,
62-
{
63-
inputAmount: amount,
64-
mint: mintAddress
65-
},
66-
poolAddress,
67-
slippageTolerance,
68-
wallet
68+
devnetRpc,
69+
{
70+
inputAmount: amount,
71+
mint: mintAddress,
72+
},
73+
poolAddress,
74+
slippageTolerance,
75+
wallet
6976
);
7077
```
7178

7279
### 4. Putting it all together
80+
7381
```tsx
74-
import { swapInstructions, setWhirlpoolsConfig } from '@orca-so/whirlpools';
75-
import { generateKeyPairSigner, createSolanaRpc, devnet } from '@solana/kit';
82+
import { swapInstructions, setWhirlpoolsConfig } from "@orca-so/whirlpools";
83+
import { generateKeyPairSigner, createSolanaRpc, devnet } from "@solana/kit";
7684

77-
const devnetRpc = createSolanaRpc(devnet('https://api.devnet.solana.com'));
78-
await setWhirlpoolsConfig('solanaDevnet');
85+
const devnetRpc = createSolanaRpc(devnet("https://api.devnet.solana.com"));
86+
await setWhirlpoolsConfig("solanaDevnet");
7987
const wallet = loadWallet();
8088
await devnetRpc.requestAirdrop(wallet.address, lamports(1000000000n)).send();
8189

@@ -92,13 +100,164 @@ const amount = 1_000_000n; // 0.001 WSOL (SOL has 9 decimals)
92100
const slippageTolerance = 100; // 100bps = 1%
93101

94102
const { instructions, quote } = await swapInstructions(
95-
devnetRpc,
103+
devnetRpc,
104+
{
105+
inputAmount: amount,
106+
mint: mintAddress,
107+
},
108+
poolAddress,
109+
slippageTolerance,
110+
wallet
111+
);
112+
```
113+
114+
## ACTIONS
115+
116+
To use actions, you need to set some configuration first.
117+
The only required configuration is the keypair of the payer, and the rpc url.
118+
119+
```tsx
120+
import { setPayerFromBytes, setRpc } from "@orca-so/whirlpools";
121+
122+
// Set payer from a private key byte array
123+
const privateKeyBytes = new Uint8Array([
124+
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
125+
23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
126+
]);
127+
await setPayerFromBytes(privateKeyBytes);
128+
129+
// Set rpc url
130+
await setRpc("https://api.devnet.solana.com");
131+
```
132+
133+
You can also optionally set up the prioritization fees and jito tip settings according to your needs.
134+
135+
```tsx
136+
// Set priority fee settings
137+
setPriorityFeeSetting({
138+
type: "dynamic",
139+
maxCapLamports: BigInt(4_000_000), // 0.004 SOL
140+
});
141+
142+
// Set Jito tip settings
143+
setJitoTipSetting({
144+
type: "dynamic",
145+
maxCapLamports: BigInt(4_000_000), // 0.004 SOL
146+
});
147+
148+
// Set compute unit margin multiplier
149+
setComputeUnitMarginMultiplier(1.1);
150+
151+
// Set priority fee percentile
152+
setPriorityFeePercentile("50");
153+
154+
// Set Jito fee percentile (50ema is the default)
155+
setJitoFeePercentile("50ema");
156+
157+
// Set Jito block engine URL
158+
await setJitoBlockEngineUrl("https://bundles.jito.wtf");
159+
```
160+
161+
### 1. Create a new whirlpool
162+
163+
```tsx
164+
import { createSplashPool } from "@orca-so/whirlpools";
165+
import { address } from "@solana/kit";
166+
167+
// Create a new splash pool between SOL and USDC
168+
const { poolAddress, callback } = await createSplashPool(
169+
address("So11111111111111111111111111111111111111112"), // SOL
170+
address("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") // USDC
171+
);
172+
173+
// Execute the transaction
174+
const signature = await callback();
175+
console.log(`Pool created at ${poolAddress} in tx ${signature}`);
176+
```
177+
178+
### 2. Add liquidity to a whirlpool
179+
180+
```tsx
181+
import {
182+
openFullRangePosition,
183+
openConcentratedPosition,
184+
} from "@orca-so/whirlpools";
185+
import { address } from "@solana/kit";
186+
187+
// Add full range liquidity to a splash pool
188+
const { positionAddress, callback: fullRangeCallback } =
189+
await openFullRangePosition(
190+
address("POOL_ADDRESS"), // The pool address
96191
{
97-
inputAmount: amount,
98-
mint: mintAddress
192+
tokenA: BigInt(1_000_000), // Amount of token A to add (in native units)
99193
},
100-
poolAddress,
101-
slippageTolerance,
102-
wallet
194+
50 // Optional: Slippage tolerance in basis points (0.5%)
195+
);
196+
197+
// Execute the transaction
198+
const fullRangeSig = await fullRangeCallback();
199+
console.log(
200+
`Full range position created at ${positionAddress} in tx ${fullRangeSig}`
103201
);
202+
203+
// Add concentrated liquidity to a whirlpool
204+
const { positionAddress: concPosAddress, callback: concCallback } =
205+
await openConcentratedPosition(
206+
address("POOL_ADDRESS"), // The pool address
207+
{
208+
tokenA: BigInt(1_000_000), // Amount of token A to add (in native units)
209+
},
210+
19.5, // Lower price bound
211+
20.5, // Upper price bound
212+
50 // Optional: Slippage tolerance in basis points (0.5%)
213+
);
214+
215+
// Execute the transaction
216+
const concSig = await concCallback();
217+
console.log(
218+
`Concentrated position created at ${concPosAddress} in tx ${concSig}`
219+
);
220+
221+
// Increase liquidity in an existing position
222+
const { callback: increaseLiqCallback, quote } = await increasePosLiquidity(
223+
address("POSITION_ADDRESS"), // The position address
224+
{
225+
tokenA: BigInt(1_000_000), // Amount of token A to add (in native units)
226+
},
227+
50 // Optional: Slippage tolerance in basis points (0.5%)
228+
);
229+
230+
//optionally check quote
231+
if (quote.tokenMaxB < 1_000_000n) {
232+
// Check if max token B amount is acceptable
233+
const increaseLiqSig = await increaseLiqCallback();
234+
console.log(`Added liquidity in tx ${increaseLiqSig}`);
235+
} else {
236+
console.log(`Required token B amount ${quote.tokenMaxB} is too high`);
237+
}
238+
```
239+
240+
### 3. Harvest rewards from all positions
241+
242+
```tsx
243+
// Harvest fees and rewards from all positions owned by the wallet
244+
const signatures = await harvestAllPositionFees();
245+
console.log(`Harvested all positions in ${signatures.length} transactions`);
246+
247+
// Harvest fees and rewards from a single position
248+
const {
249+
callback: harvestCallback,
250+
feesQuote,
251+
rewardsQuote,
252+
} = await harvestPosition(
253+
address("POSITION_ADDRESS") // The position address
254+
);
255+
256+
// Check quotes
257+
console.log(`Fees to collect: ${feesQuote.feeOwedA}, ${feesQuote.feeOwedB}`);
258+
console.log(`Rewards to collect: ${rewardsQuote.rewards[0].rewardsOwed}`);
259+
260+
// Execute the transaction
261+
const harvestSig = await harvestCallback();
262+
console.log(`Harvested position in tx ${harvestSig}`);
104263
```
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import {
2+
address,
3+
createNoopSigner,
4+
getBase64EncodedWireTransaction,
5+
type IInstruction,
6+
type Rpc,
7+
type Signature,
8+
type SolanaRpcApi,
9+
type TransactionSigner,
10+
} from "@solana/kit";
11+
import { getPayer, getRpcConfig } from "./config";
12+
import {
13+
rpcFromUrl,
14+
buildAndSendTransaction,
15+
buildTransaction,
16+
} from "@orca-so/tx-sender";
17+
18+
/**
19+
* A generic wrapper function to reduce boilerplate when working with Whirlpool instructions
20+
* @param instructionFn The Whirlpool instruction function to execute
21+
* @returns A wrapped function that automatically includes rpc and owner params
22+
*/
23+
export function wrapFunctionWithExecution<T extends unknown[], R>(
24+
instructionFn: (
25+
rpc: Rpc<SolanaRpcApi>,
26+
...params: [...T, TransactionSigner]
27+
) => Promise<R & { instructions: IInstruction[] }>,
28+
): (...params: T) => Promise<R & { callback: () => Promise<Signature> }> {
29+
return async (...params: T) => {
30+
const { rpcUrl } = getRpcConfig();
31+
const rpc = rpcFromUrl(rpcUrl);
32+
const owner = getPayer();
33+
34+
const result = await instructionFn(rpc, ...params, owner);
35+
36+
return {
37+
...result,
38+
callback: () => buildAndSendTransaction(result.instructions, owner),
39+
};
40+
};
41+
}
42+
43+
/**
44+
* Check if adding additional instructions would exceed transaction size limits
45+
* @param currentInstructions Current list of instructions in transaction
46+
* @param instructionsToAdd Instructions to check if they can be added
47+
* @returns True if adding instructions would exceed size limit, false otherwise
48+
*/
49+
export async function wouldExceedTransactionSize(
50+
currentInstructions: IInstruction[],
51+
instructionsToAdd: IInstruction[],
52+
): Promise<boolean> {
53+
const noopSginer = createNoopSigner(
54+
address("11111111111111111111111111111111"),
55+
);
56+
const tx = await buildTransaction(
57+
[...currentInstructions, ...instructionsToAdd],
58+
noopSginer,
59+
);
60+
const encodedTransaction = getBase64EncodedWireTransaction(tx);
61+
62+
// The maximum size for a base64 encoded transaction is 1644 bytes
63+
// This is derived from PACKET_DATA_SIZE (1232) with base64 encoding overhead
64+
const TX_BASE64_ENCODED_SIZE_LIMIT = 1644;
65+
66+
return encodedTransaction.length >= TX_BASE64_ENCODED_SIZE_LIMIT;
67+
}

ts-sdk/whirlpool/src/config.ts

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,23 @@
11
import { getWhirlpoolsConfigExtensionAddress } from "@orca-so/whirlpools-client";
2-
import type { Address, TransactionSigner } from "@solana/kit";
3-
import { address, createNoopSigner, isAddress } from "@solana/kit";
4-
2+
import type { Address, TransactionSigner, KeyPairSigner } from "@solana/kit";
3+
import {
4+
address,
5+
createNoopSigner,
6+
isAddress,
7+
createKeyPairFromBytes,
8+
createSignerFromKeyPair,
9+
} from "@solana/kit";
10+
11+
export {
12+
setComputeUnitMarginMultiplier,
13+
setJitoBlockEngineUrl,
14+
setJitoTipSetting,
15+
setPriorityFeeSetting,
16+
setRpc,
17+
setJitoFeePercentile,
18+
setPriorityFeePercentile,
19+
getRpcConfig,
20+
} from "@orca-so/tx-sender";
521
/**
622
* The default (null) address.
723
*/
@@ -170,3 +186,37 @@ export function resetConfiguration() {
170186
SLIPPAGE_TOLERANCE_BPS = DEFAULT_SLIPPAGE_TOLERANCE_BPS;
171187
NATIVE_MINT_WRAPPING_STRATEGY = DEFAULT_NATIVE_MINT_WRAPPING_STRATEGY;
172188
}
189+
190+
let _payer: KeyPairSigner | undefined;
191+
192+
/**
193+
* Sets the payer from a private key byte array.
194+
*
195+
* @param {Uint8Array<ArrayBuffer>} pkBytes - The private key bytes to create the payer from.
196+
* @returns {Promise<KeyPairSigner>} - A promise that resolves to the created signer.
197+
*
198+
* @example
199+
* ```ts
200+
* // Set payer from a private key byte array
201+
* const privateKeyBytes = new Uint8Array([
202+
* 55, 244, 186, 115, 93, 3, 9, 47, 12, 168,
203+
* 86, 1, 5, 155, 127, 3, 44, 165, 155, 3,
204+
* 112, 1, 3, 99, 3, 211, 3, 77, 153,
205+
* 44, 1, 179
206+
* ]);
207+
* const signer = await setPayerFromBytes(privateKeyBytes);
208+
* ```
209+
*/
210+
export async function setPayerFromBytes(pkBytes: Uint8Array<ArrayBuffer>) {
211+
const kp = await createKeyPairFromBytes(pkBytes);
212+
const signer = await createSignerFromKeyPair(kp);
213+
_payer = signer;
214+
return signer;
215+
}
216+
217+
export function getPayer(): KeyPairSigner {
218+
if (!_payer) {
219+
throw new Error("Payer not set. Call setPayer() first.");
220+
}
221+
return _payer;
222+
}

0 commit comments

Comments
 (0)