-
Notifications
You must be signed in to change notification settings - Fork 293
sam/DX-251 #795
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
sam/DX-251 #795
Changes from 6 commits
db6d93d
6fe2cdc
6553638
990a279
61de840
d707722
0ad032c
fefa209
86ee47c
0e68385
8b386c2
d669e6a
75c2665
ffaeaa1
6c88ca3
d1d7084
ed9f669
1b63f18
999ec9b
cc6dbff
f064bc6
8119efe
f58383b
00a4d6c
d21d16d
3d24ec5
2ce3a74
cc950b3
c57778e
83f5e09
e0f1c92
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,155 @@ | ||
| # Orca Actions SDK | ||
|
|
||
| The Orca Actions SDK provides a set of utilities for executing common actions with Orca Whirlpools on Solana and Eclipse blockchains. This package simplifies the process of creating and managing transactions for Whirlpool operations. | ||
|
|
||
| ## Installation | ||
|
|
||
| ```bash | ||
| npm install @orca-so/actions | ||
| ``` | ||
|
|
||
| ## Usage | ||
|
|
||
| ### Config | ||
|
|
||
| Before using the SDK, you need to configure the payer and RPC settings (and optionally priority fee and jito tip settings). | ||
|
|
||
| ```ts | ||
| import { setPayerFromBytes, setRpc, setPriorityFeeSetting, setJitoTipSetting, setDefaultSlippageToleranceBps } from "@orca-so/actions"; | ||
|
|
||
| await setPayerFromBytes( | ||
| new Uint8Array(Buffer.from(process.env.PAYER_PRIVATE_KEY, "base64")) | ||
| ); | ||
|
|
||
| await setRpc(process.env.RPC_URL); | ||
|
|
||
| // optional | ||
| await setPriorityFeeSetting({ | ||
| type: "dynamic"; | ||
| maxCapLamports: BigInt(5_000_000), // 0.005 SOL | ||
| priorityFeePercentile: "75", | ||
| }); | ||
|
|
||
| // optional | ||
| await setJitoTipSetting({ | ||
| type: "dynamic", | ||
| maxCapLamports: BigInt(2_000_000), // 0.002 SOL | ||
| priorityFeePercentile: "50ema", | ||
| }); | ||
|
|
||
| // you can optionally set a default slippage tolerance; that will be used for all transactions if not overridden | ||
| setDefaultSlippageToleranceBps(100) | ||
|
|
||
|
|
||
| ``` | ||
|
|
||
| ### Harvest Fees | ||
|
|
||
| ```ts | ||
| import { harvestAllPositionFees } from "@orca-so/actions"; | ||
|
|
||
| const txs = await harvestAllPositionFees(); | ||
| ``` | ||
|
|
||
| ### Create a Splash Liquidity Pool | ||
|
|
||
| ```ts | ||
| import { createSplashPool } from "@orca-so/actions"; | ||
|
|
||
| const { callback, initializationCost, poolAddress } = await createSplashPool( | ||
| tokenMintA, | ||
| tokenMintB, | ||
| initialPrice | ||
| ); | ||
| // you can check initialization cost of the pool before sending the transaction | ||
|
|
||
| await callback(); | ||
| ``` | ||
|
|
||
| ### Open a Concentrated Liquidity Position | ||
|
|
||
| ```ts | ||
| import { openConcentratedPosition } from "@orca-so/actions"; | ||
|
|
||
| const { callback, quote, initializationCost, positionMint } = | ||
| await openConcentratedPosition( | ||
| poolAddress, | ||
| tokenAmount, | ||
| lowerPrice, | ||
| upperPrice | ||
| ); | ||
| // you can check quote and initialization cost of the position before sending the transaction | ||
|
|
||
| // callback builds and sends the transaction and returns the transaction signature | ||
| await callback(); | ||
| ``` | ||
|
|
||
| ### Open a Full Range Liquidity Position | ||
|
|
||
| ```ts | ||
| import { openFullRangePosition } from "@orca-so/actions"; | ||
|
|
||
| const { callback, quote, initializationCost } = await openFullRangePosition( | ||
| poolAddress, | ||
| tokenAmount | ||
| ); | ||
| // you can check quote and initialization cost of the position before sending the transaction | ||
|
|
||
| // callback builds and sends the transaction and returns the transaction signature | ||
| await callback(); | ||
| ``` | ||
|
|
||
| ### Close a Liquidity Position | ||
|
|
||
| ```ts | ||
| import { closePositionAndCollectFees } from "@orca-so/actions"; | ||
|
|
||
| const { callback, quote, feesQuote, rewardsQuote } = | ||
| await closePositionAndCollectFees(positionMintAddress); | ||
| // you can check quote, feesQuote, and rewardsQuote of the position before sending the transaction | ||
|
|
||
| // callback builds and sends the transaction and returns the transaction signature | ||
| await callback(); | ||
| ``` | ||
|
|
||
| ### Increase Liquidity | ||
|
|
||
| ```ts | ||
| import { increasePosLiquidity } from "@orca-so/actions"; | ||
|
|
||
| const { callback, quote } = await increasePosLiquidity( | ||
| positionMintAddress, | ||
| tokenAmount | ||
| ); | ||
| // you can check quote of the position before sending the transaction | ||
|
|
||
| // callback builds and sends the transaction and returns the transaction signature | ||
| await callback(); | ||
| ``` | ||
|
|
||
| ### Decrease Liquidity | ||
|
|
||
| ```ts | ||
| import { decreasePosLiquidity } from "@orca-so/actions"; | ||
|
|
||
| const { callback, quote } = await decreasePosLiquidity( | ||
| positionMintAddress, | ||
| tokenAmount | ||
| ); | ||
|
|
||
| await callback(); | ||
| ``` | ||
|
|
||
| ### Swap | ||
|
|
||
| ```ts | ||
| import { swap } from "@orca-so/actions"; | ||
|
|
||
| const { callback, quote } = await swap( | ||
| poolAddress, | ||
| tokenAmount, | ||
| slippageToleranceBps // optional | ||
| ); | ||
|
|
||
| await callback(); | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| { | ||
| "name": "@orca-so/actions", | ||
| "version": "0.0.2", | ||
| "type": "module", | ||
| "main": "./dist/index.cjs", | ||
| "module": "./dist/index.js", | ||
| "types": "./dist/index.d.ts", | ||
| "exports": { | ||
| "import": { | ||
| "types": "./dist/index.d.ts", | ||
| "import": "./dist/index.js" | ||
| }, | ||
| "require": { | ||
| "types": "./dist/index.d.cts", | ||
| "require": "./dist/index.cjs" | ||
| } | ||
| }, | ||
| "sideEffects": false, | ||
| "files": [ | ||
| "dist", | ||
| "README.md" | ||
| ], | ||
| "scripts": { | ||
| "build": "tsup src/index.ts --format cjs,esm --dts --sourcemap", | ||
| "clean": "rimraf dist" | ||
| }, | ||
| "dependencies": { | ||
| "@orca-so/tx-sender": "*", | ||
| "@orca-so/whirlpools": "*", | ||
| "@solana/web3.js": "^2.0.0" | ||
| }, | ||
| "peerDependencies": { | ||
| "@solana/web3.js": "^2.0.0" | ||
| }, | ||
| "devDependencies": { | ||
| "tsup": "^8.3.6" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| import { | ||
| createKeyPairFromBytes, | ||
| createSignerFromKeyPair, | ||
| KeyPairSigner, | ||
| } from "@solana/web3.js"; | ||
| export { | ||
| setComputeUnitMarginMultiplier, | ||
| setJitoBlockEngineUrl, | ||
| setJitoTipSetting, | ||
| setPriorityFeeSetting, | ||
| setRpc, | ||
| setJitoFeePercentile, | ||
| setPriorityFeePercentile, | ||
| getRpcConfig, | ||
| } from "@orca-so/tx-sender"; | ||
|
|
||
| let _payer: KeyPairSigner | undefined; | ||
|
|
||
| export async function setPayerFromBytes(pkBytes: Uint8Array<ArrayBuffer>) { | ||
| const kp = await createKeyPairFromBytes(pkBytes); | ||
| const signer = await createSignerFromKeyPair(kp); | ||
| _payer = signer; | ||
| return signer; | ||
| } | ||
|
|
||
| export function getPayer(): KeyPairSigner { | ||
| if (!_payer) { | ||
| throw new Error("Payer not set. Call setPayer() first."); | ||
| } | ||
| return _payer; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import { | ||
| getRpcConfig, | ||
| rpcFromUrl, | ||
| buildAndSendTransaction, | ||
| } from "@orca-so/tx-sender"; | ||
| import { | ||
| fetchPositionsForOwner, | ||
| harvestPositionInstructions, | ||
| } from "@orca-so/whirlpools"; | ||
| import { IInstruction } from "@solana/web3.js"; | ||
| import { getPayer } from "./config"; | ||
| import { wouldExceedTransactionSize } from "./helpers"; | ||
|
|
||
| // Harvest fees from all positions owned by an address | ||
| export async function harvestAllPositionFees(): Promise<string[]> { | ||
| const { rpcUrl } = getRpcConfig(); | ||
| const rpc = rpcFromUrl(rpcUrl); | ||
| const owner = getPayer(); | ||
|
|
||
| const positions = await fetchPositionsForOwner(rpc, owner.address); | ||
| const instructionSets: IInstruction[][] = []; | ||
| let currentInstructions: IInstruction[] = []; | ||
| for (const position of positions) { | ||
| if ("positionMint" in position.data) { | ||
| const { instructions } = await harvestPositionInstructions( | ||
| rpc, | ||
| position.data.positionMint, | ||
| owner | ||
| ); | ||
| if (wouldExceedTransactionSize(currentInstructions, instructions)) { | ||
| instructionSets.push(currentInstructions); | ||
| currentInstructions = [...instructions]; | ||
| } else { | ||
| currentInstructions.push(...instructions); | ||
| } | ||
| } | ||
| } | ||
| return Promise.all( | ||
| instructionSets.map(async (instructions) => { | ||
| let txHash = await buildAndSendTransaction(instructions, owner); | ||
| return String(txHash); | ||
| }) | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import { IInstruction } from "@solana/web3.js"; | ||
|
|
||
| /** | ||
| * Check if adding additional instructions would exceed transaction size limits | ||
| * @param currentInstructions Current list of instructions in transaction | ||
| * @param instructionsToAdd Instructions to check if they can be added | ||
| * @returns True if adding instructions would exceed size limit, false otherwise | ||
| */ | ||
| export function wouldExceedTransactionSize( | ||
| currentInstructions: IInstruction[], | ||
| instructionsToAdd: IInstruction[] | ||
| ): boolean { | ||
| // Current Solana transaction size limit is 1232 bytes | ||
| const MAX_TRANSACTION_SIZE = 1232; | ||
| const INSTRUCTION_OVERHEAD = 40; // Each instruction has ~40 bytes overhead | ||
|
|
||
| // Calculate total size in one pass | ||
| const totalSize = [...currentInstructions, ...instructionsToAdd].reduce( | ||
| (sum, ix) => sum + INSTRUCTION_OVERHEAD + (ix.data?.length ?? 0), | ||
|
||
| 0 | ||
| ); | ||
|
|
||
| return totalSize > MAX_TRANSACTION_SIZE; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| export * from "./pools"; | ||
|
||
| export * from "./positions"; | ||
| export * from "./swap"; | ||
| export * from "./harvestFees"; | ||
| export * from "./config"; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| import { Address } from "@solana/web3.js"; | ||
| import { getPayer } from "./config"; | ||
| import { | ||
| buildAndSendTransaction, | ||
| getRpcConfig, | ||
| rpcFromUrl, | ||
| } from "@orca-so/tx-sender"; | ||
| import { | ||
| createSplashPoolInstructions, | ||
| createConcentratedLiquidityPoolInstructions, | ||
| } from "@orca-so/whirlpools"; | ||
| export { setDefaultSlippageToleranceBps } from "@orca-so/whirlpools"; | ||
|
|
||
| // Create a splash liquidity pool | ||
| export async function createSplashPool( | ||
| tokenMintA: Address, | ||
| tokenMintB: Address, | ||
| initialPrice: number | ||
| ) { | ||
| const { rpcUrl } = getRpcConfig(); | ||
| const rpc = rpcFromUrl(rpcUrl); | ||
| const owner = getPayer(); | ||
|
|
||
| const { instructions, initializationCost, poolAddress } = | ||
| await createSplashPoolInstructions( | ||
| rpc, | ||
| tokenMintA, | ||
| tokenMintB, | ||
| initialPrice, | ||
| owner | ||
| ); | ||
|
|
||
| return { | ||
| callback: () => buildAndSendTransaction(instructions, owner), | ||
| initializationCost, | ||
| poolAddress, | ||
| }; | ||
| } | ||
|
|
||
| // Create a concentrated liquidity pool | ||
| export async function createConcentratedLiquidityPool( | ||
| tokenMintA: Address, | ||
| tokenMintB: Address, | ||
| tickSpacing: number, | ||
| initialPrice: number | ||
| ) { | ||
| const { rpcUrl } = getRpcConfig(); | ||
| const rpc = rpcFromUrl(rpcUrl); | ||
| const owner = getPayer(); | ||
|
|
||
| const { instructions, initializationCost, poolAddress } = | ||
| await createConcentratedLiquidityPoolInstructions( | ||
| rpc, | ||
| tokenMintA, | ||
| tokenMintB, | ||
| tickSpacing, | ||
| initialPrice, | ||
| owner | ||
| ); | ||
|
|
||
| return { | ||
| callback: () => buildAndSendTransaction(instructions, owner), | ||
| initializationCost, | ||
| poolAddress, | ||
| }; | ||
| } |
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.
Can we do @orca-so/whirlpools-actions?