Add collectFeesAndRewardsForPositions to allow easy fee/reward collection (#57)
- Add collectFeesAndRewardsForPositions to WhirlpoolClient - Add collectAllForPositionAddressesTxns to WhirlpoolIx
This commit is contained in:
parent
8a7ed57bc5
commit
3c7b90c147
|
@ -3,8 +3,7 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@project-serum/anchor": "~0.25.0",
|
"@project-serum/anchor": "~0.25.0",
|
||||||
"@solana/spl-token": "^0.1.8",
|
"@solana/spl-token": "^0.1.8"
|
||||||
"@orca-so/whirlpool-client-sdk": "0.0.7"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mocha": "^9.0.0",
|
"@types/mocha": "^9.0.0",
|
||||||
|
|
|
@ -7,15 +7,15 @@
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@metaplex-foundation/mpl-token-metadata": "1.2.5",
|
"@metaplex-foundation/mpl-token-metadata": "1.2.5",
|
||||||
"@orca-so/common-sdk": "~0.1.1",
|
"@solana/web3.js": "1.66.0",
|
||||||
|
"@orca-so/common-sdk": "^0.1.4",
|
||||||
"@project-serum/anchor": "~0.25.0",
|
"@project-serum/anchor": "~0.25.0",
|
||||||
"@solana/spl-token": "^0.1.8",
|
"@solana/spl-token": "^0.1.8",
|
||||||
"decimal.js": "10.3.1",
|
"decimal.js": "^10.3.1",
|
||||||
"tiny-invariant": "^1.2.0"
|
"tiny-invariant": "^1.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bn.js": "~5.1.0",
|
"@types/bn.js": "~5.1.0",
|
||||||
"@types/decimal.js": "^7.4.0",
|
|
||||||
"@types/jest": "^26.0.24",
|
"@types/jest": "^26.0.24",
|
||||||
"@types/mocha": "^9.0.0",
|
"@types/mocha": "^9.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.26.0",
|
"@typescript-eslint/eslint-plugin": "^4.26.0",
|
||||||
|
|
|
@ -5,27 +5,21 @@ import {
|
||||||
TransactionBuilder,
|
TransactionBuilder,
|
||||||
} from "@orca-so/common-sdk";
|
} from "@orca-so/common-sdk";
|
||||||
import { Address } from "@project-serum/anchor";
|
import { Address } from "@project-serum/anchor";
|
||||||
|
import { PublicKey } from "@solana/web3.js";
|
||||||
import { WhirlpoolContext } from "../context";
|
import { WhirlpoolContext } from "../context";
|
||||||
import {
|
import {
|
||||||
IncreaseLiquidityInput,
|
|
||||||
DecreaseLiquidityInput,
|
DecreaseLiquidityInput,
|
||||||
increaseLiquidityIx,
|
|
||||||
decreaseLiquidityIx,
|
decreaseLiquidityIx,
|
||||||
|
IncreaseLiquidityInput,
|
||||||
|
increaseLiquidityIx,
|
||||||
} from "../instructions";
|
} from "../instructions";
|
||||||
import { PositionData } from "../types/public";
|
import { PositionData } from "../types/public";
|
||||||
|
import { PDAUtil, TickUtil } from "../utils/public";
|
||||||
import { Position } from "../whirlpool-client";
|
import { Position } from "../whirlpool-client";
|
||||||
import { PublicKey } from "@solana/web3.js";
|
|
||||||
import { AccountFetcher } from "../network/public";
|
|
||||||
import { PDAUtil, TickUtil, toTx } from "../utils/public";
|
|
||||||
|
|
||||||
export class PositionImpl implements Position {
|
export class PositionImpl implements Position {
|
||||||
private data: PositionData;
|
private data: PositionData;
|
||||||
constructor(
|
constructor(readonly ctx: WhirlpoolContext, readonly address: PublicKey, data: PositionData) {
|
||||||
readonly ctx: WhirlpoolContext,
|
|
||||||
readonly fetcher: AccountFetcher,
|
|
||||||
readonly address: PublicKey,
|
|
||||||
data: PositionData
|
|
||||||
) {
|
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +51,7 @@ export class PositionImpl implements Position {
|
||||||
: this.ctx.wallet.publicKey;
|
: this.ctx.wallet.publicKey;
|
||||||
const ataPayerKey = ataPayer ? AddressUtil.toPubKey(ataPayer) : this.ctx.wallet.publicKey;
|
const ataPayerKey = ataPayer ? AddressUtil.toPubKey(ataPayer) : this.ctx.wallet.publicKey;
|
||||||
|
|
||||||
const whirlpool = await this.fetcher.getPool(this.data.whirlpool, true);
|
const whirlpool = await this.ctx.fetcher.getPool(this.data.whirlpool, true);
|
||||||
if (!whirlpool) {
|
if (!whirlpool) {
|
||||||
throw new Error("Unable to fetch whirlpool for this position.");
|
throw new Error("Unable to fetch whirlpool for this position.");
|
||||||
}
|
}
|
||||||
|
@ -78,7 +72,7 @@ export class PositionImpl implements Position {
|
||||||
{ tokenMint: whirlpool.tokenMintA, wrappedSolAmountIn: liquidityInput.tokenMaxA },
|
{ tokenMint: whirlpool.tokenMintA, wrappedSolAmountIn: liquidityInput.tokenMaxA },
|
||||||
{ tokenMint: whirlpool.tokenMintB, wrappedSolAmountIn: liquidityInput.tokenMaxB },
|
{ tokenMint: whirlpool.tokenMintB, wrappedSolAmountIn: liquidityInput.tokenMaxB },
|
||||||
],
|
],
|
||||||
() => this.fetcher.getAccountRentExempt(),
|
() => this.ctx.fetcher.getAccountRentExempt(),
|
||||||
ataPayerKey
|
ataPayerKey
|
||||||
);
|
);
|
||||||
const { address: ataAddrA, ...tokenOwnerAccountAIx } = ataA!;
|
const { address: ataAddrA, ...tokenOwnerAccountAIx } = ataA!;
|
||||||
|
@ -132,7 +126,7 @@ export class PositionImpl implements Position {
|
||||||
? AddressUtil.toPubKey(positionWallet)
|
? AddressUtil.toPubKey(positionWallet)
|
||||||
: this.ctx.wallet.publicKey;
|
: this.ctx.wallet.publicKey;
|
||||||
const ataPayerKey = ataPayer ? AddressUtil.toPubKey(ataPayer) : this.ctx.wallet.publicKey;
|
const ataPayerKey = ataPayer ? AddressUtil.toPubKey(ataPayer) : this.ctx.wallet.publicKey;
|
||||||
const whirlpool = await this.fetcher.getPool(this.data.whirlpool, true);
|
const whirlpool = await this.ctx.fetcher.getPool(this.data.whirlpool, true);
|
||||||
|
|
||||||
if (!whirlpool) {
|
if (!whirlpool) {
|
||||||
throw new Error("Unable to fetch whirlpool for this position.");
|
throw new Error("Unable to fetch whirlpool for this position.");
|
||||||
|
@ -150,7 +144,7 @@ export class PositionImpl implements Position {
|
||||||
this.ctx.connection,
|
this.ctx.connection,
|
||||||
sourceWalletKey,
|
sourceWalletKey,
|
||||||
[{ tokenMint: whirlpool.tokenMintA }, { tokenMint: whirlpool.tokenMintB }],
|
[{ tokenMint: whirlpool.tokenMintA }, { tokenMint: whirlpool.tokenMintB }],
|
||||||
() => this.fetcher.getAccountRentExempt(),
|
() => this.ctx.fetcher.getAccountRentExempt(),
|
||||||
ataPayerKey
|
ataPayerKey
|
||||||
);
|
);
|
||||||
const { address: ataAddrA, ...tokenOwnerAccountAIx } = ataA!;
|
const { address: ataAddrA, ...tokenOwnerAccountAIx } = ataA!;
|
||||||
|
@ -190,7 +184,7 @@ export class PositionImpl implements Position {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async refresh() {
|
private async refresh() {
|
||||||
const account = await this.fetcher.getPosition(this.address, true);
|
const account = await this.ctx.fetcher.getPosition(this.address, true);
|
||||||
if (!!account) {
|
if (!!account) {
|
||||||
this.data = account;
|
this.data = account;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,12 @@ import { Keypair, PublicKey } from "@solana/web3.js";
|
||||||
import invariant from "tiny-invariant";
|
import invariant from "tiny-invariant";
|
||||||
import { WhirlpoolContext } from "../context";
|
import { WhirlpoolContext } from "../context";
|
||||||
import { initTickArrayIx } from "../instructions";
|
import { initTickArrayIx } from "../instructions";
|
||||||
|
import { collectAllForPositionAddressesTxns } from "../instructions/composites";
|
||||||
import { WhirlpoolIx } from "../ix";
|
import { WhirlpoolIx } from "../ix";
|
||||||
import { AccountFetcher } from "../network/public";
|
import { AccountFetcher } from "../network/public";
|
||||||
import { WhirlpoolData } from "../types/public";
|
import { WhirlpoolData } from "../types/public";
|
||||||
import { PDAUtil, PoolUtil, PriceMath, TickArrayUtil, TickUtil } from "../utils/public";
|
import { PDAUtil, PoolUtil, PriceMath, TickUtil } from "../utils/public";
|
||||||
import { WhirlpoolClient, Whirlpool, Position } from "../whirlpool-client";
|
import { Position, Whirlpool, WhirlpoolClient } from "../whirlpool-client";
|
||||||
import { PositionImpl } from "./position-impl";
|
import { PositionImpl } from "./position-impl";
|
||||||
import { getRewardInfos, getTokenMintInfos, getTokenVaultAccountInfos } from "./util";
|
import { getRewardInfos, getTokenMintInfos, getTokenVaultAccountInfos } from "./util";
|
||||||
import { WhirlpoolImpl } from "./whirlpool-impl";
|
import { WhirlpoolImpl } from "./whirlpool-impl";
|
||||||
|
@ -34,7 +35,6 @@ export class WhirlpoolClientImpl implements WhirlpoolClient {
|
||||||
const rewardInfos = await getRewardInfos(this.ctx.fetcher, account, refresh);
|
const rewardInfos = await getRewardInfos(this.ctx.fetcher, account, refresh);
|
||||||
return new WhirlpoolImpl(
|
return new WhirlpoolImpl(
|
||||||
this.ctx,
|
this.ctx,
|
||||||
this.ctx.fetcher,
|
|
||||||
AddressUtil.toPubKey(poolAddress),
|
AddressUtil.toPubKey(poolAddress),
|
||||||
tokenInfos[0],
|
tokenInfos[0],
|
||||||
tokenInfos[1],
|
tokenInfos[1],
|
||||||
|
@ -78,7 +78,6 @@ export class WhirlpoolClientImpl implements WhirlpoolClient {
|
||||||
whirlpools.push(
|
whirlpools.push(
|
||||||
new WhirlpoolImpl(
|
new WhirlpoolImpl(
|
||||||
this.ctx,
|
this.ctx,
|
||||||
this.ctx.fetcher,
|
|
||||||
AddressUtil.toPubKey(poolAddress),
|
AddressUtil.toPubKey(poolAddress),
|
||||||
tokenInfos[0],
|
tokenInfos[0],
|
||||||
tokenInfos[1],
|
tokenInfos[1],
|
||||||
|
@ -97,12 +96,7 @@ export class WhirlpoolClientImpl implements WhirlpoolClient {
|
||||||
if (!account) {
|
if (!account) {
|
||||||
throw new Error(`Unable to fetch Position at address at ${positionAddress}`);
|
throw new Error(`Unable to fetch Position at address at ${positionAddress}`);
|
||||||
}
|
}
|
||||||
return new PositionImpl(
|
return new PositionImpl(this.ctx, AddressUtil.toPubKey(positionAddress), account);
|
||||||
this.ctx,
|
|
||||||
this.ctx.fetcher,
|
|
||||||
AddressUtil.toPubKey(positionAddress),
|
|
||||||
account
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getPositions(
|
public async getPositions(
|
||||||
|
@ -116,15 +110,7 @@ export class WhirlpoolClientImpl implements WhirlpoolClient {
|
||||||
return [address, null];
|
return [address, null];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [address, new PositionImpl(this.ctx, AddressUtil.toPubKey(address), positionAccount)];
|
||||||
address,
|
|
||||||
new PositionImpl(
|
|
||||||
this.ctx,
|
|
||||||
this.ctx.fetcher,
|
|
||||||
AddressUtil.toPubKey(address),
|
|
||||||
positionAccount
|
|
||||||
),
|
|
||||||
];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return Object.fromEntries(results);
|
return Object.fromEntries(results);
|
||||||
|
@ -217,4 +203,22 @@ export class WhirlpoolClientImpl implements WhirlpoolClient {
|
||||||
tx: txBuilder,
|
tx: txBuilder,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async collectFeesAndRewardsForPositions(
|
||||||
|
positionAddresses: Address[],
|
||||||
|
refresh?: boolean | undefined
|
||||||
|
): Promise<TransactionBuilder[]> {
|
||||||
|
const walletKey = this.ctx.wallet.publicKey;
|
||||||
|
return collectAllForPositionAddressesTxns(
|
||||||
|
this.ctx,
|
||||||
|
{
|
||||||
|
positions: positionAddresses,
|
||||||
|
receiver: walletKey,
|
||||||
|
positionAuthority: walletKey,
|
||||||
|
positionOwner: walletKey,
|
||||||
|
payer: walletKey,
|
||||||
|
},
|
||||||
|
refresh
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
resolveOrCreateATAs,
|
resolveOrCreateATAs,
|
||||||
TokenUtil,
|
TokenUtil,
|
||||||
TransactionBuilder,
|
TransactionBuilder,
|
||||||
ZERO
|
ZERO,
|
||||||
} from "@orca-so/common-sdk";
|
} from "@orca-so/common-sdk";
|
||||||
import { Address, BN, translateAddress } from "@project-serum/anchor";
|
import { Address, BN, translateAddress } from "@project-serum/anchor";
|
||||||
import { Keypair, PublicKey } from "@solana/web3.js";
|
import { Keypair, PublicKey } from "@solana/web3.js";
|
||||||
|
@ -21,9 +21,8 @@ import {
|
||||||
openPositionIx,
|
openPositionIx,
|
||||||
openPositionWithMetadataIx,
|
openPositionWithMetadataIx,
|
||||||
SwapInput,
|
SwapInput,
|
||||||
swapIx
|
swapIx,
|
||||||
} from "../instructions";
|
} from "../instructions";
|
||||||
import { AccountFetcher } from "../network/public";
|
|
||||||
import { decreaseLiquidityQuoteByLiquidityWithParams } from "../quotes/public";
|
import { decreaseLiquidityQuoteByLiquidityWithParams } from "../quotes/public";
|
||||||
import { TokenAccountInfo, TokenInfo, WhirlpoolData, WhirlpoolRewardInfo } from "../types/public";
|
import { TokenAccountInfo, TokenInfo, WhirlpoolData, WhirlpoolRewardInfo } from "../types/public";
|
||||||
import { PDAUtil, TickArrayUtil, TickUtil } from "../utils/public";
|
import { PDAUtil, TickArrayUtil, TickUtil } from "../utils/public";
|
||||||
|
@ -34,7 +33,6 @@ export class WhirlpoolImpl implements Whirlpool {
|
||||||
private data: WhirlpoolData;
|
private data: WhirlpoolData;
|
||||||
constructor(
|
constructor(
|
||||||
readonly ctx: WhirlpoolContext,
|
readonly ctx: WhirlpoolContext,
|
||||||
readonly fetcher: AccountFetcher,
|
|
||||||
readonly address: PublicKey,
|
readonly address: PublicKey,
|
||||||
readonly tokenAInfo: TokenInfo,
|
readonly tokenAInfo: TokenInfo,
|
||||||
readonly tokenBInfo: TokenInfo,
|
readonly tokenBInfo: TokenInfo,
|
||||||
|
@ -121,7 +119,7 @@ export class WhirlpoolImpl implements Whirlpool {
|
||||||
this.ctx.program.programId,
|
this.ctx.program.programId,
|
||||||
this.address,
|
this.address,
|
||||||
this.data.tickSpacing,
|
this.data.tickSpacing,
|
||||||
this.fetcher,
|
this.ctx.fetcher,
|
||||||
refresh
|
refresh
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -229,7 +227,7 @@ export class WhirlpoolImpl implements Whirlpool {
|
||||||
|
|
||||||
invariant(liquidity.gt(new BN(0)), "liquidity must be greater than zero");
|
invariant(liquidity.gt(new BN(0)), "liquidity must be greater than zero");
|
||||||
|
|
||||||
const whirlpool = await this.fetcher.getPool(this.address, false);
|
const whirlpool = await this.ctx.fetcher.getPool(this.address, false);
|
||||||
if (!whirlpool) {
|
if (!whirlpool) {
|
||||||
throw new Error(`Whirlpool not found: ${translateAddress(this.address).toBase58()}`);
|
throw new Error(`Whirlpool not found: ${translateAddress(this.address).toBase58()}`);
|
||||||
}
|
}
|
||||||
|
@ -279,7 +277,7 @@ export class WhirlpoolImpl implements Whirlpool {
|
||||||
{ tokenMint: whirlpool.tokenMintA, wrappedSolAmountIn: tokenMaxA },
|
{ tokenMint: whirlpool.tokenMintA, wrappedSolAmountIn: tokenMaxA },
|
||||||
{ tokenMint: whirlpool.tokenMintB, wrappedSolAmountIn: tokenMaxB },
|
{ tokenMint: whirlpool.tokenMintB, wrappedSolAmountIn: tokenMaxB },
|
||||||
],
|
],
|
||||||
() => this.fetcher.getAccountRentExempt(),
|
() => this.ctx.fetcher.getAccountRentExempt(),
|
||||||
funder
|
funder
|
||||||
);
|
);
|
||||||
const { address: tokenOwnerAccountA, ...tokenOwnerAccountAIx } = ataA;
|
const { address: tokenOwnerAccountA, ...tokenOwnerAccountAIx } = ataA;
|
||||||
|
@ -331,7 +329,7 @@ export class WhirlpoolImpl implements Whirlpool {
|
||||||
positionWallet: PublicKey,
|
positionWallet: PublicKey,
|
||||||
payerKey: PublicKey
|
payerKey: PublicKey
|
||||||
): Promise<TransactionBuilder> {
|
): Promise<TransactionBuilder> {
|
||||||
const position = await this.fetcher.getPosition(positionAddress, true);
|
const position = await this.ctx.fetcher.getPosition(positionAddress, true);
|
||||||
if (!position) {
|
if (!position) {
|
||||||
throw new Error(`Position not found: ${positionAddress.toBase58()}`);
|
throw new Error(`Position not found: ${positionAddress.toBase58()}`);
|
||||||
}
|
}
|
||||||
|
@ -367,7 +365,7 @@ export class WhirlpoolImpl implements Whirlpool {
|
||||||
this.ctx.connection,
|
this.ctx.connection,
|
||||||
destinationWallet,
|
destinationWallet,
|
||||||
[{ tokenMint: whirlpool.tokenMintA }, { tokenMint: whirlpool.tokenMintB }],
|
[{ tokenMint: whirlpool.tokenMintA }, { tokenMint: whirlpool.tokenMintB }],
|
||||||
() => this.fetcher.getAccountRentExempt(),
|
() => this.ctx.fetcher.getAccountRentExempt(),
|
||||||
payerKey
|
payerKey
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -460,7 +458,7 @@ export class WhirlpoolImpl implements Whirlpool {
|
||||||
{ tokenMint: whirlpool.tokenMintA, wrappedSolAmountIn: aToB ? amount : ZERO },
|
{ tokenMint: whirlpool.tokenMintA, wrappedSolAmountIn: aToB ? amount : ZERO },
|
||||||
{ tokenMint: whirlpool.tokenMintB, wrappedSolAmountIn: !aToB ? amount : ZERO },
|
{ tokenMint: whirlpool.tokenMintB, wrappedSolAmountIn: !aToB ? amount : ZERO },
|
||||||
],
|
],
|
||||||
() => this.fetcher.getAccountRentExempt()
|
() => this.ctx.fetcher.getAccountRentExempt()
|
||||||
);
|
);
|
||||||
|
|
||||||
const { address: tokenOwnerAccountA, ...tokenOwnerAccountAIx } = ataA;
|
const { address: tokenOwnerAccountA, ...tokenOwnerAccountAIx } = ataA;
|
||||||
|
@ -488,11 +486,11 @@ export class WhirlpoolImpl implements Whirlpool {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async refresh() {
|
private async refresh() {
|
||||||
const account = await this.fetcher.getPool(this.address, true);
|
const account = await this.ctx.fetcher.getPool(this.address, true);
|
||||||
if (!!account) {
|
if (!!account) {
|
||||||
const rewardInfos = await getRewardInfos(this.fetcher, account, true);
|
const rewardInfos = await getRewardInfos(this.ctx.fetcher, account, true);
|
||||||
const [tokenVaultAInfo, tokenVaultBInfo] = await getTokenVaultAccountInfos(
|
const [tokenVaultAInfo, tokenVaultBInfo] = await getTokenVaultAccountInfos(
|
||||||
this.fetcher,
|
this.ctx.fetcher,
|
||||||
account,
|
account,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import Decimal from "decimal.js";
|
import Decimal from "decimal.js";
|
||||||
|
|
||||||
export * from "./context";
|
export * from "./context";
|
||||||
export * from "./types/public";
|
export * from "./impl/position-impl";
|
||||||
export * from "./utils/public";
|
export * from "./ix";
|
||||||
export * from "./network/public";
|
export * from "./network/public";
|
||||||
export * from "./quotes/public";
|
export * from "./quotes/public";
|
||||||
export * from "./ix";
|
export * from "./types/public";
|
||||||
export * from "./whirlpool-client";
|
|
||||||
|
|
||||||
export * from "./types/public/anchor-types";
|
export * from "./types/public/anchor-types";
|
||||||
|
export * from "./utils/public";
|
||||||
|
export * from "./whirlpool-client";
|
||||||
|
|
||||||
// Global rules for Decimals
|
// Global rules for Decimals
|
||||||
// - 40 digits of precision for the largest number
|
// - 40 digits of precision for the largest number
|
||||||
|
|
|
@ -0,0 +1,292 @@
|
||||||
|
import { AddressUtil, Instruction, TokenUtil, TransactionBuilder, ZERO } from "@orca-so/common-sdk";
|
||||||
|
import { createWSOLAccountInstructions } from "@orca-so/common-sdk/dist/helpers/token-instructions";
|
||||||
|
import { Address } from "@project-serum/anchor";
|
||||||
|
import { NATIVE_MINT } from "@solana/spl-token";
|
||||||
|
import { PACKET_DATA_SIZE, PublicKey } from "@solana/web3.js";
|
||||||
|
import { updateFeesAndRewardsIx } from "..";
|
||||||
|
import { WhirlpoolContext } from "../..";
|
||||||
|
import { PositionImpl } from "../../impl/position-impl";
|
||||||
|
import { WhirlpoolIx } from "../../ix";
|
||||||
|
import { WhirlpoolData } from "../../types/public";
|
||||||
|
import { PDAUtil, PoolUtil, TickUtil } from "../../utils/public";
|
||||||
|
import { getAssociatedTokenAddressSync } from "../../utils/spl-token-utils";
|
||||||
|
import { convertListToMap } from "../../utils/txn-utils";
|
||||||
|
import { Position } from "../../whirlpool-client";
|
||||||
|
import { resolveAtaForWhirlpoolsIxs } from "./resolve-atas-ix";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters to collect all fees and rewards from a list of positions.
|
||||||
|
*
|
||||||
|
* @category Instruction Types
|
||||||
|
* @param positionAddrs - An array of Whirlpool position addresses.
|
||||||
|
* @param receiver - The destination wallet that collected fees & reward will be sent to. Defaults to ctx.wallet key.
|
||||||
|
* @param positionOwner - The wallet key that contains the position token. Defaults to ctx.wallet key.
|
||||||
|
* @param positionAuthority - The authority key that can authorize operation on the position. Defaults to ctx.wallet key.
|
||||||
|
* @param payer - The key that will pay for the initialization of ATA token accounts. Defaults to ctx.wallet key.
|
||||||
|
*/
|
||||||
|
export type CollectAllPositionAddressParams = {
|
||||||
|
positions: Address[];
|
||||||
|
} & CollectAllParams;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters to collect all fees and rewards from a list of positions.
|
||||||
|
*
|
||||||
|
* @category Instruction Types
|
||||||
|
* @param positions - An array of Whirlpool positions.
|
||||||
|
* @param receiver - The destination wallet that collected fees & reward will be sent to. Defaults to ctx.wallet key.
|
||||||
|
* @param positionOwner - The wallet key that contains the position token. Defaults to ctx.wallet key.
|
||||||
|
* @param positionAuthority - The authority key that can authorize operation on the position. Defaults to ctx.wallet key.
|
||||||
|
* @param payer - The key that will pay for the initialization of ATA token accounts. Defaults to ctx.wallet key.
|
||||||
|
*/
|
||||||
|
export type CollectAllPositionParams = {
|
||||||
|
positions: Position[];
|
||||||
|
} & CollectAllParams;
|
||||||
|
|
||||||
|
type CollectAllParams = {
|
||||||
|
receiver?: PublicKey;
|
||||||
|
positionOwner?: PublicKey;
|
||||||
|
positionAuthority?: PublicKey;
|
||||||
|
payer?: PublicKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a set of transactions to collect fees and rewards for a set of Whirlpool Positions.
|
||||||
|
*
|
||||||
|
* @category Instructions
|
||||||
|
* @experimental
|
||||||
|
* @param ctx - WhirlpoolContext object for the current environment.
|
||||||
|
* @param params - CollectAllPositionAddressParams object
|
||||||
|
* @param refresh - if true, will always fetch for the latest on-chain data.
|
||||||
|
* @returns A set of transaction-builders to resolve ATA for affliated tokens, collect fee & rewards for all positions.
|
||||||
|
* The first transaction should always be processed as it contains all the resolve ATA instructions to receive tokens.
|
||||||
|
*/
|
||||||
|
export async function collectAllForPositionAddressesTxns(
|
||||||
|
ctx: WhirlpoolContext,
|
||||||
|
params: CollectAllPositionAddressParams,
|
||||||
|
refresh = false
|
||||||
|
): Promise<TransactionBuilder[]> {
|
||||||
|
const { positions, ...rest } = params;
|
||||||
|
const posDatas = await ctx.fetcher.listPositions(positions, refresh);
|
||||||
|
const positionsObjs = posDatas.reduce<Position[]>((accu, curr, index) => {
|
||||||
|
if (curr) {
|
||||||
|
accu.push(new PositionImpl(ctx, AddressUtil.toPubKey(positions[index]), curr));
|
||||||
|
}
|
||||||
|
return accu;
|
||||||
|
}, []);
|
||||||
|
return collectAllForPositionsTxns(ctx, { positions: positionsObjs, ...rest });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a set of transactions to collect fees and rewards for a set of Whirlpool Positions.
|
||||||
|
*
|
||||||
|
* @experimental
|
||||||
|
* @param ctx - WhirlpoolContext object for the current environment.
|
||||||
|
* @param params - CollectAllPositionParams object
|
||||||
|
* @returns A set of transaction-builders to resolve ATA for affliated tokens, collect fee & rewards for all positions.
|
||||||
|
*/
|
||||||
|
export async function collectAllForPositionsTxns(
|
||||||
|
ctx: WhirlpoolContext,
|
||||||
|
params: CollectAllPositionParams
|
||||||
|
): Promise<TransactionBuilder[]> {
|
||||||
|
const { positions, receiver, positionAuthority, positionOwner, payer } = params;
|
||||||
|
const receiverKey = receiver ?? ctx.wallet.publicKey;
|
||||||
|
const positionAuthorityKey = positionAuthority ?? ctx.wallet.publicKey;
|
||||||
|
const positionOwnerKey = positionOwner ?? ctx.wallet.publicKey;
|
||||||
|
const payerKey = payer ?? ctx.wallet.publicKey;
|
||||||
|
|
||||||
|
if (positions.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const whirlpoolAddrs = positions.map((pos) => pos.getData().whirlpool.toBase58());
|
||||||
|
const whirlpoolDatas = await ctx.fetcher.listPools(whirlpoolAddrs, false);
|
||||||
|
const whirlpools = convertListToMap(whirlpoolDatas, whirlpoolAddrs);
|
||||||
|
|
||||||
|
// TODO: Payer is not configurable here. Forced to use wallet
|
||||||
|
const { ataTokenAddresses: affliatedTokenAtaMap, resolveAtaIxs } =
|
||||||
|
await resolveAtaForWhirlpoolsIxs(ctx, {
|
||||||
|
whirlpools: whirlpoolDatas,
|
||||||
|
receiver: receiverKey,
|
||||||
|
payer: payerKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
const latestBlockhash = await ctx.connection.getLatestBlockhash("singleGossip");
|
||||||
|
const accountExemption = await ctx.fetcher.getAccountRentExempt();
|
||||||
|
const txBuilders: TransactionBuilder[] = [];
|
||||||
|
|
||||||
|
let pendingTxBuilder = new TransactionBuilder(ctx.connection, ctx.wallet).addInstructions(
|
||||||
|
resolveAtaIxs
|
||||||
|
);
|
||||||
|
let pendingTxBuilderTxSize = await pendingTxBuilder.txnSize({ latestBlockhash });
|
||||||
|
|
||||||
|
let posIndex = 0;
|
||||||
|
let reattempt = false;
|
||||||
|
|
||||||
|
while (posIndex < positions.length) {
|
||||||
|
const position = positions[posIndex];
|
||||||
|
let positionTxBuilder = new TransactionBuilder(ctx.connection, ctx.wallet);
|
||||||
|
const { whirlpool: whirlpoolKey, positionMint } = position.getData();
|
||||||
|
const whirlpool = whirlpools[whirlpoolKey.toBase58()];
|
||||||
|
|
||||||
|
if (!whirlpool) {
|
||||||
|
throw new Error(
|
||||||
|
`Unable to process positionMint ${positionMint} - unable to derive whirlpool ${whirlpoolKey.toBase58()}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const posHandlesNativeMint =
|
||||||
|
TokenUtil.isNativeMint(whirlpool.tokenMintA) || TokenUtil.isNativeMint(whirlpool.tokenMintB);
|
||||||
|
const txBuilderHasNativeMint = !!affliatedTokenAtaMap[NATIVE_MINT.toBase58()];
|
||||||
|
|
||||||
|
// Add NATIVE_MINT token account creation to this transaction if position requires NATIVE_MINT handling.
|
||||||
|
if (posHandlesNativeMint && !txBuilderHasNativeMint) {
|
||||||
|
addNativeMintHandlingIx(
|
||||||
|
positionTxBuilder,
|
||||||
|
affliatedTokenAtaMap,
|
||||||
|
receiverKey,
|
||||||
|
accountExemption
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Build position instructions
|
||||||
|
const collectIxForPosition = constructCollectPositionIx(
|
||||||
|
ctx,
|
||||||
|
position,
|
||||||
|
whirlpools,
|
||||||
|
positionOwnerKey,
|
||||||
|
positionAuthorityKey,
|
||||||
|
affliatedTokenAtaMap
|
||||||
|
);
|
||||||
|
positionTxBuilder.addInstructions(collectIxForPosition);
|
||||||
|
|
||||||
|
// Attempt to push the new instructions into the pending builder
|
||||||
|
// Iterate to the next position if possible
|
||||||
|
// Create a builder and reattempt if the current one is full.
|
||||||
|
const incrementTxSize = await positionTxBuilder.txnSize({ latestBlockhash });
|
||||||
|
if (pendingTxBuilderTxSize + incrementTxSize < PACKET_DATA_SIZE) {
|
||||||
|
pendingTxBuilder.addInstruction(positionTxBuilder.compressIx(false));
|
||||||
|
pendingTxBuilderTxSize = pendingTxBuilderTxSize + incrementTxSize;
|
||||||
|
posIndex += 1;
|
||||||
|
reattempt = false;
|
||||||
|
} else {
|
||||||
|
if (reattempt) {
|
||||||
|
throw new Error(
|
||||||
|
`Unable to fit collection ix for ${position.getAddress().toBase58()} in a Transaction.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
txBuilders.push(pendingTxBuilder);
|
||||||
|
delete affliatedTokenAtaMap[NATIVE_MINT.toBase58()];
|
||||||
|
pendingTxBuilder = new TransactionBuilder(ctx.connection, ctx.provider.wallet);
|
||||||
|
pendingTxBuilderTxSize = 0;
|
||||||
|
reattempt = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
txBuilders.push(pendingTxBuilder);
|
||||||
|
return txBuilders;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper methods.
|
||||||
|
*/
|
||||||
|
function addNativeMintHandlingIx(
|
||||||
|
txBuilder: TransactionBuilder,
|
||||||
|
affliatedTokenAtaMap: Record<string, PublicKey>,
|
||||||
|
destinationWallet: PublicKey,
|
||||||
|
accountExemption: number
|
||||||
|
) {
|
||||||
|
let { address: wSOLAta, ...resolveWSolIx } = createWSOLAccountInstructions(
|
||||||
|
destinationWallet,
|
||||||
|
ZERO,
|
||||||
|
accountExemption
|
||||||
|
);
|
||||||
|
affliatedTokenAtaMap[NATIVE_MINT.toBase58()] = wSOLAta;
|
||||||
|
txBuilder.prependInstruction(resolveWSolIx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Once individual collect ix for positions is implemented, maybe migrate over if it can take custom ATA?
|
||||||
|
const constructCollectPositionIx = (
|
||||||
|
ctx: WhirlpoolContext,
|
||||||
|
position: Position,
|
||||||
|
whirlpools: Record<string, WhirlpoolData | null>,
|
||||||
|
positionOwner: PublicKey,
|
||||||
|
positionAuthority: PublicKey,
|
||||||
|
affliatedTokenAtaMap: Record<string, PublicKey>
|
||||||
|
) => {
|
||||||
|
const ixForPosition: Instruction[] = [];
|
||||||
|
const {
|
||||||
|
whirlpool: whirlpoolKey,
|
||||||
|
liquidity,
|
||||||
|
tickLowerIndex,
|
||||||
|
tickUpperIndex,
|
||||||
|
positionMint,
|
||||||
|
rewardInfos: positionRewardInfos,
|
||||||
|
} = position.getData();
|
||||||
|
const positionKey = AddressUtil.toPubKey(position.getAddress());
|
||||||
|
const whirlpool = whirlpools[whirlpoolKey.toBase58()];
|
||||||
|
|
||||||
|
if (!whirlpool) {
|
||||||
|
throw new Error(
|
||||||
|
`Unable to process positionMint ${positionMint} - unable to derive whirlpool ${whirlpoolKey.toBase58()}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const { tickSpacing } = whirlpool;
|
||||||
|
|
||||||
|
// Update fee and reward values if necessary
|
||||||
|
if (!liquidity.eq(ZERO)) {
|
||||||
|
ixForPosition.push(
|
||||||
|
updateFeesAndRewardsIx(ctx.program, {
|
||||||
|
position: positionKey,
|
||||||
|
whirlpool: whirlpoolKey,
|
||||||
|
tickArrayLower: PDAUtil.getTickArray(
|
||||||
|
ctx.program.programId,
|
||||||
|
whirlpoolKey,
|
||||||
|
TickUtil.getStartTickIndex(tickLowerIndex, tickSpacing)
|
||||||
|
).publicKey,
|
||||||
|
tickArrayUpper: PDAUtil.getTickArray(
|
||||||
|
ctx.program.programId,
|
||||||
|
whirlpoolKey,
|
||||||
|
TickUtil.getStartTickIndex(tickUpperIndex, tickSpacing)
|
||||||
|
).publicKey,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect Fee
|
||||||
|
const positionTokenAccount = getAssociatedTokenAddressSync(
|
||||||
|
positionMint.toBase58(),
|
||||||
|
positionOwner.toBase58()
|
||||||
|
);
|
||||||
|
ixForPosition.push(
|
||||||
|
WhirlpoolIx.collectFeesIx(ctx.program, {
|
||||||
|
whirlpool: whirlpoolKey,
|
||||||
|
position: positionKey,
|
||||||
|
positionAuthority,
|
||||||
|
positionTokenAccount,
|
||||||
|
tokenOwnerAccountA: affliatedTokenAtaMap[whirlpool.tokenMintA.toBase58()],
|
||||||
|
tokenOwnerAccountB: affliatedTokenAtaMap[whirlpool.tokenMintB.toBase58()],
|
||||||
|
tokenVaultA: whirlpool.tokenVaultA,
|
||||||
|
tokenVaultB: whirlpool.tokenVaultB,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Collect Rewards
|
||||||
|
// TODO: handle empty vault values?
|
||||||
|
positionRewardInfos.forEach((_, index) => {
|
||||||
|
const rewardInfo = whirlpool.rewardInfos[index];
|
||||||
|
if (PoolUtil.isRewardInitialized(rewardInfo)) {
|
||||||
|
ixForPosition.push(
|
||||||
|
WhirlpoolIx.collectRewardIx(ctx.program, {
|
||||||
|
whirlpool: whirlpoolKey,
|
||||||
|
position: positionKey,
|
||||||
|
positionAuthority,
|
||||||
|
positionTokenAccount,
|
||||||
|
rewardIndex: index,
|
||||||
|
rewardOwnerAccount: affliatedTokenAtaMap[rewardInfo.mint.toBase58()],
|
||||||
|
rewardVault: rewardInfo.vault,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ixForPosition;
|
||||||
|
};
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./collect-all-txn";
|
||||||
|
export * from "./resolve-atas-ix";
|
|
@ -0,0 +1,136 @@
|
||||||
|
import { Instruction, TokenUtil } from "@orca-so/common-sdk";
|
||||||
|
import { ASSOCIATED_TOKEN_PROGRAM_ID, Token, TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||||
|
import { PublicKey } from "@solana/web3.js";
|
||||||
|
import { WhirlpoolContext } from "../..";
|
||||||
|
import { WhirlpoolData } from "../../types/public";
|
||||||
|
import { getAssociatedTokenAddressSync } from "../../utils/spl-token-utils";
|
||||||
|
import { convertListToMap } from "../../utils/txn-utils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters to resolve ATAs for affliated tokens in a list of Whirlpools
|
||||||
|
*
|
||||||
|
* @category Instruction Types
|
||||||
|
* @param whirlpools - The list of WhirlpoolData to generate affliated tokens for.
|
||||||
|
* @param destinationWallet - the wallet to generate ATAs against
|
||||||
|
* @param payer - The payer address that would pay for the creation of ATA addresses
|
||||||
|
*/
|
||||||
|
export type ResolveAtaInstructionParams = {
|
||||||
|
whirlpools: (WhirlpoolData | null)[];
|
||||||
|
receiver?: PublicKey;
|
||||||
|
payer?: PublicKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface of mapping between tokenMint & ATA & the instruction set to initialize them.
|
||||||
|
*
|
||||||
|
* @category Instruction Types
|
||||||
|
* @param ataTokenAddresses - A record between the token mint & generated ATA addresses
|
||||||
|
* @param resolveAtaIxs - An array of instructions to initialize all uninitialized ATA token accounts for the list above.
|
||||||
|
*/
|
||||||
|
export type ResolvedATAInstructionSet = {
|
||||||
|
ataTokenAddresses: Record<string, PublicKey>;
|
||||||
|
resolveAtaIxs: Instruction[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build instructions to resolve ATAs (Associated Tokens Addresses) for affliated tokens in a list of Whirlpools.
|
||||||
|
* Affliated tokens are tokens that are part of the trade pair or reward in a Whirlpool.
|
||||||
|
*
|
||||||
|
* SOL tokens does not use the ATA program and therefore not handled.
|
||||||
|
*
|
||||||
|
* @param ctx - WhirlpoolContext object for the current environment.
|
||||||
|
|
||||||
|
* @returns a ResolvedTokenAddressesIxSet containing the derived ATA addresses & ix set to initialize the accounts.
|
||||||
|
*/
|
||||||
|
export async function resolveAtaForWhirlpoolsIxs(
|
||||||
|
ctx: WhirlpoolContext,
|
||||||
|
params: ResolveAtaInstructionParams
|
||||||
|
): Promise<ResolvedATAInstructionSet> {
|
||||||
|
const { whirlpools, receiver, payer } = params;
|
||||||
|
const receiverKey = receiver ?? ctx.wallet.publicKey;
|
||||||
|
const payerKey = payer ?? ctx.wallet.publicKey;
|
||||||
|
const { affliatedTokenAtaMap, affliatedTokensInfoMap } = await getAffliatedTokenAtas(
|
||||||
|
ctx,
|
||||||
|
whirlpools,
|
||||||
|
receiverKey
|
||||||
|
);
|
||||||
|
|
||||||
|
const tokensRequiringAtaResolve = Object.fromEntries(
|
||||||
|
Object.entries(affliatedTokensInfoMap)
|
||||||
|
.filter(([, account]) => !account)
|
||||||
|
.map(([mint]) => [mint, affliatedTokenAtaMap[mint]])
|
||||||
|
);
|
||||||
|
|
||||||
|
const resolveAtaIxs: Instruction[] = [];
|
||||||
|
|
||||||
|
Object.entries(tokensRequiringAtaResolve).forEach(([mint, ataKey]) => {
|
||||||
|
const createAtaInstruction = Token.createAssociatedTokenAccountInstruction(
|
||||||
|
ASSOCIATED_TOKEN_PROGRAM_ID,
|
||||||
|
TOKEN_PROGRAM_ID,
|
||||||
|
new PublicKey(mint),
|
||||||
|
ataKey,
|
||||||
|
receiverKey,
|
||||||
|
payerKey
|
||||||
|
);
|
||||||
|
resolveAtaIxs.push({
|
||||||
|
instructions: [createAtaInstruction],
|
||||||
|
cleanupInstructions: [],
|
||||||
|
signers: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
ataTokenAddresses: affliatedTokenAtaMap,
|
||||||
|
resolveAtaIxs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAffliatedTokenAtas(
|
||||||
|
ctx: WhirlpoolContext,
|
||||||
|
whirlpoolDatas: (WhirlpoolData | null)[],
|
||||||
|
wallet: PublicKey
|
||||||
|
) {
|
||||||
|
const affliatedTokens = Array.from(
|
||||||
|
whirlpoolDatas.reduce<Set<string>>((accu, whirlpoolData) => {
|
||||||
|
if (whirlpoolData) {
|
||||||
|
const { tokenMintA, tokenMintB } = whirlpoolData;
|
||||||
|
if (!TokenUtil.isNativeMint(tokenMintA)) {
|
||||||
|
accu.add(tokenMintA.toBase58());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!TokenUtil.isNativeMint(tokenMintB)) {
|
||||||
|
accu.add(tokenMintB.toBase58());
|
||||||
|
}
|
||||||
|
|
||||||
|
const rewardInfos = whirlpoolData.rewardInfos;
|
||||||
|
rewardInfos.forEach((reward) => {
|
||||||
|
if (!reward.mint.equals(PublicKey.default)) {
|
||||||
|
accu.add(reward.mint.toBase58());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return accu;
|
||||||
|
}, new Set<string>())
|
||||||
|
);
|
||||||
|
|
||||||
|
const tokenMintInfoMap = convertListToMap(
|
||||||
|
await ctx.fetcher.listMintInfos(affliatedTokens, false),
|
||||||
|
affliatedTokens
|
||||||
|
);
|
||||||
|
|
||||||
|
// Derive associated addresses for all affliated spl-tokens
|
||||||
|
const affliatedTokenAtaMap = Object.fromEntries(
|
||||||
|
Object.keys(tokenMintInfoMap).map((addr) => [
|
||||||
|
addr,
|
||||||
|
getAssociatedTokenAddressSync(addr, wallet.toBase58()),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
affliatedTokenAtaMap: affliatedTokenAtaMap,
|
||||||
|
affliatedTokensInfoMap: convertListToMap(
|
||||||
|
await ctx.fetcher.listTokenInfos(Object.values(affliatedTokenAtaMap), false),
|
||||||
|
Object.keys(affliatedTokenAtaMap)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ export * from "./close-position-ix";
|
||||||
export * from "./collect-fees-ix";
|
export * from "./collect-fees-ix";
|
||||||
export * from "./collect-protocol-fees-ix";
|
export * from "./collect-protocol-fees-ix";
|
||||||
export * from "./collect-reward-ix";
|
export * from "./collect-reward-ix";
|
||||||
|
export * from "./composites";
|
||||||
export * from "./decrease-liquidity-ix";
|
export * from "./decrease-liquidity-ix";
|
||||||
export * from "./increase-liquidity-ix";
|
export * from "./increase-liquidity-ix";
|
||||||
export * from "./initialize-config-ix";
|
export * from "./initialize-config-ix";
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { PDA } from "@orca-so/common-sdk";
|
import { PDA } from "@orca-so/common-sdk";
|
||||||
import { Program } from "@project-serum/anchor";
|
import { Program } from "@project-serum/anchor";
|
||||||
|
import { WhirlpoolContext } from ".";
|
||||||
import { Whirlpool } from "./artifacts/whirlpool";
|
import { Whirlpool } from "./artifacts/whirlpool";
|
||||||
import * as ix from "./instructions";
|
import * as ix from "./instructions";
|
||||||
|
|
||||||
|
@ -415,4 +416,20 @@ export class WhirlpoolIx {
|
||||||
) {
|
) {
|
||||||
return ix.setRewardEmissionsSuperAuthorityIx(program, params);
|
return ix.setRewardEmissionsSuperAuthorityIx(program, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of transactions to collect all fees and rewards from a list of positions.
|
||||||
|
*
|
||||||
|
* @param ctx - WhirlpoolContext object for the current environment.
|
||||||
|
* @param params - CollectAllPositionAddressParams object.
|
||||||
|
* @param refresh - if true, will always fetch for the latest values on chain to compute.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public static collectAllForPositionsTxns(
|
||||||
|
ctx: WhirlpoolContext,
|
||||||
|
params: ix.CollectAllPositionAddressParams,
|
||||||
|
refresh: boolean
|
||||||
|
) {
|
||||||
|
return ix.collectAllForPositionAddressesTxns(ctx, params, refresh);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,9 @@ import { DecreaseLiquidityInput } from "../../instructions";
|
||||||
import {
|
import {
|
||||||
adjustForSlippage,
|
adjustForSlippage,
|
||||||
getTokenAFromLiquidity,
|
getTokenAFromLiquidity,
|
||||||
getTokenBFromLiquidity, PositionStatus, PositionUtil
|
getTokenBFromLiquidity,
|
||||||
|
PositionStatus,
|
||||||
|
PositionUtil,
|
||||||
} from "../../utils/position-util";
|
} from "../../utils/position-util";
|
||||||
import { PriceMath, TickUtil } from "../../utils/public";
|
import { PriceMath, TickUtil } from "../../utils/public";
|
||||||
import { Position, Whirlpool } from "../../whirlpool-client";
|
import { Position, Whirlpool } from "../../whirlpool-client";
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
export * from "./pda-utils";
|
|
||||||
export * from "./price-math";
|
|
||||||
export * from "./tick-utils";
|
|
||||||
export * from "./pool-utils";
|
|
||||||
export * from "./ix-utils";
|
export * from "./ix-utils";
|
||||||
export * from "./types";
|
export * from "./pda-utils";
|
||||||
|
export * from "./pool-utils";
|
||||||
|
export * from "./price-math";
|
||||||
export * from "./swap-utils";
|
export * from "./swap-utils";
|
||||||
|
export * from "./tick-utils";
|
||||||
|
export * from "./types";
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { ASSOCIATED_TOKEN_PROGRAM_ID, NATIVE_MINT, TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||||
|
import { PublicKey } from "@solana/web3.js";
|
||||||
|
|
||||||
|
export function isNativeMint(mint: PublicKey) {
|
||||||
|
return mint.equals(NATIVE_MINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Update spl-token so we get this method
|
||||||
|
export function getAssociatedTokenAddressSync(
|
||||||
|
mint: string,
|
||||||
|
owner: string,
|
||||||
|
programId = TOKEN_PROGRAM_ID,
|
||||||
|
associatedTokenProgramId = ASSOCIATED_TOKEN_PROGRAM_ID
|
||||||
|
): PublicKey {
|
||||||
|
const [address] = PublicKey.findProgramAddressSync(
|
||||||
|
[new PublicKey(owner).toBuffer(), programId.toBuffer(), new PublicKey(mint).toBuffer()],
|
||||||
|
associatedTokenProgramId
|
||||||
|
);
|
||||||
|
|
||||||
|
return address;
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
export function convertListToMap<T>(fetchedData: T[], addresses: string[]) {
|
||||||
|
const result: Record<string, T> = {};
|
||||||
|
fetchedData.forEach((data, index) => {
|
||||||
|
if (data) {
|
||||||
|
const addr = addresses[index];
|
||||||
|
result[addr] = data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
import { Percentage, TransactionBuilder } from "@orca-so/common-sdk";
|
import { Percentage, TransactionBuilder } from "@orca-so/common-sdk";
|
||||||
import { Address } from "@project-serum/anchor";
|
import { Address } from "@project-serum/anchor";
|
||||||
import { u64 } from "@solana/spl-token";
|
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKey } from "@solana/web3.js";
|
||||||
import { WhirlpoolContext } from "./context";
|
import { WhirlpoolContext } from "./context";
|
||||||
import { WhirlpoolClientImpl } from "./impl/whirlpool-client-impl";
|
import { WhirlpoolClientImpl } from "./impl/whirlpool-client-impl";
|
||||||
|
@ -10,7 +9,7 @@ import {
|
||||||
DecreaseLiquidityInput,
|
DecreaseLiquidityInput,
|
||||||
IncreaseLiquidityInput,
|
IncreaseLiquidityInput,
|
||||||
PositionData,
|
PositionData,
|
||||||
WhirlpoolData,
|
WhirlpoolData
|
||||||
} from "./types/public";
|
} from "./types/public";
|
||||||
import { TokenAccountInfo, TokenInfo, WhirlpoolRewardInfo } from "./types/public/client-types";
|
import { TokenAccountInfo, TokenInfo, WhirlpoolRewardInfo } from "./types/public/client-types";
|
||||||
|
|
||||||
|
@ -68,6 +67,19 @@ export interface WhirlpoolClient {
|
||||||
refresh?: boolean
|
refresh?: boolean
|
||||||
) => Promise<Record<string, Position | null>>;
|
) => Promise<Record<string, Position | null>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect all fees and rewards from a list of positions.
|
||||||
|
* @experimental
|
||||||
|
* @param positionAddress the addresses of the Position accounts to collect fee & rewards from.
|
||||||
|
* @param refresh true to always request newest data from chain with this request
|
||||||
|
* @returns A set of transaction-builders to resolve ATA for affliated tokens, collect fee & rewards for all positions.
|
||||||
|
* The first transaction should always be processed as it contains all the resolve ATA instructions to receive tokens.
|
||||||
|
*/
|
||||||
|
collectFeesAndRewardsForPositions: (
|
||||||
|
positionAddresses: Address[],
|
||||||
|
refresh?: boolean
|
||||||
|
) => Promise<TransactionBuilder[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a Whirlpool account for a group of token A, token B and tick spacing
|
* Create a Whirlpool account for a group of token A, token B and tick spacing
|
||||||
* @param whirlpoolConfig the address of the whirlpool config
|
* @param whirlpoolConfig the address of the whirlpool config
|
||||||
|
|
Loading…
Reference in New Issue