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

TokenExtensions based position NFT #341

Merged
merged 32 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e5badf8
initial impl position 2022
yugure-orca Sep 25, 2024
d83b703
allow TE mint at position ops
yugure-orca Sep 25, 2024
5209fa1
rename verify_position_authority fn
yugure-orca Sep 25, 2024
1b8d35c
fix warnings, initialize MetadataPointer if metadata required
yugure-orca Sep 26, 2024
1f803ec
instruction name change
yugure-orca Sep 26, 2024
da5cc61
add test cases for open_position_with_token_extensions
yugure-orca Sep 26, 2024
a537c13
add test cases for close_position_with_token_extensions
yugure-orca Sep 27, 2024
ea5b402
fix build error
yugure-orca Sep 27, 2024
2089fc1
add test cases (life cycle test)
yugure-orca Sep 28, 2024
166a3e3
refactor
yugure-orca Sep 28, 2024
5c7b66a
cargo fmt
yugure-orca Sep 28, 2024
f71973f
Merge branch 'main' into yugure/token-extensions-based-position-reboot
yugure-orca Sep 28, 2024
494703e
update rich client functions
yugure-orca Sep 28, 2024
4037669
add test cases for rich client
yugure-orca Sep 30, 2024
7af4ff2
add test cases for rich client
yugure-orca Sep 30, 2024
bab297a
fix lint error
yugure-orca Sep 30, 2024
b797f93
add getAllPositionAccountsByOwner
yugure-orca Oct 1, 2024
c533e97
add test cases for getAllPositionAccountsByOwner
yugure-orca Oct 1, 2024
8a6e1bb
update position NFT metadata URI
yugure-orca Oct 2, 2024
07f7c7e
fix lint error
yugure-orca Oct 2, 2024
0ed74d1
Merge branch 'main' into yugure/token-extensions-based-position-reboot
yugure-orca Oct 3, 2024
ab7e0e3
address review comments
yugure-orca Oct 3, 2024
f0cf34a
fix getAllPositionAccountsByOwner test case (isolate test env)
yugure-orca Oct 3, 2024
4466187
increase accrue reward time to avoid accidental failure
yugure-orca Oct 3, 2024
52b3a4f
eliminate fixed length account init by removing WP_2022_METADATA_MAX_LEN
yugure-orca Oct 3, 2024
d0b0d76
fix lint error
yugure-orca Oct 3, 2024
0ecfecc
reserve FreezeAuthority
yugure-orca Oct 3, 2024
5fbe681
switch withTokenExtensions to tokenProgramId on client
yugure-orca Oct 4, 2024
39e9ff9
fix: not skip simulation on test
yugure-orca Oct 4, 2024
04e8e31
bump versions (contract & sdk)
yugure-orca Oct 4, 2024
d1ebc3e
Merge branch 'main' into yugure/token-extensions-based-position-reboot
yugure-orca Oct 8, 2024
db5d63f
bump sdk version 0.13.8
yugure-orca Oct 8, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion legacy-sdk/whirlpool/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@orca-so/whirlpools-sdk",
"version": "0.13.7",
"version": "0.13.8",
"description": "Typescript SDK to interact with Orca's Whirlpool program.",
"license": "Apache-2.0",
"main": "dist/index.js",
Expand Down
9 changes: 9 additions & 0 deletions legacy-sdk/whirlpool/src/impl/position-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export class PositionImpl implements Position {
whirlpoolData: WhirlpoolData,
lowerTickArrayData: TickArrayData,
upperTickArrayData: TickArrayData,
readonly positionMintTokenProgramId: PublicKey,
) {
this.data = data;
this.whirlpoolData = whirlpoolData;
Expand All @@ -74,6 +75,10 @@ export class PositionImpl implements Position {
return this.address;
}

getPositionMintTokenProgramId(): PublicKey {
return this.positionMintTokenProgramId;
}

getData(): PositionData {
return this.data;
}
Expand Down Expand Up @@ -188,6 +193,7 @@ export class PositionImpl implements Position {
this.data.positionMint,
positionWalletKey,
this.ctx.accountResolverOpts.allowPDAOwnerAddress,
this.positionMintTokenProgramId,
);

const baseParams = {
Expand Down Expand Up @@ -324,6 +330,7 @@ export class PositionImpl implements Position {
this.data.positionMint,
positionWalletKey,
this.ctx.accountResolverOpts.allowPDAOwnerAddress,
this.positionMintTokenProgramId,
),
tokenOwnerAccountA,
tokenOwnerAccountB,
Expand Down Expand Up @@ -457,6 +464,7 @@ export class PositionImpl implements Position {
this.data.positionMint,
positionWalletKey,
this.ctx.accountResolverOpts.allowPDAOwnerAddress,
this.positionMintTokenProgramId,
);

if (updateFeesAndRewards && !this.data.liquidity.isZero()) {
Expand Down Expand Up @@ -578,6 +586,7 @@ export class PositionImpl implements Position {
this.data.positionMint,
positionWalletKey,
this.ctx.accountResolverOpts.allowPDAOwnerAddress,
this.positionMintTokenProgramId,
);

if (updateFeesAndRewards && !this.data.liquidity.isZero()) {
Expand Down
12 changes: 12 additions & 0 deletions legacy-sdk/whirlpool/src/impl/whirlpool-client-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,13 @@ export class WhirlpoolClientImpl implements WhirlpoolClient {
);
}

const positionMint = await this.ctx.fetcher.getMintInfo(account.positionMint, opts);
if (!positionMint) {
throw new Error(
`Unable to fetch Mint for Position at address at ${positionAddress}`,
);
}

const [lowerTickArray, upperTickArray] = await getTickArrayDataForPosition(
this.ctx,
account,
Expand All @@ -178,6 +185,7 @@ export class WhirlpoolClientImpl implements WhirlpoolClient {
whirlAccount,
lowerTickArray,
upperTickArray,
positionMint.tokenProgram,
);
}

Expand All @@ -193,6 +201,10 @@ export class WhirlpoolClientImpl implements WhirlpoolClient {
.map((position) => position?.whirlpool.toBase58())
.flatMap((x) => (!!x ? x : []));
await this.ctx.fetcher.getPools(whirlpoolAddrs, opts);
const positionMintAddrs = positions
.map((position) => position?.positionMint.toBase58())
.flatMap((x) => (!!x ? x : []));
await this.ctx.fetcher.getMintInfos(positionMintAddrs, opts);
const tickArrayAddresses: Set<string> = new Set();
await Promise.all(
positions.map(async (pos) => {
Expand Down
57 changes: 47 additions & 10 deletions legacy-sdk/whirlpool/src/impl/whirlpool-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
ZERO,
resolveOrCreateATAs,
} from "@orca-so/common-sdk";
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
import { getAssociatedTokenAddressSync, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from "@solana/spl-token";
import type { PublicKey } from "@solana/web3.js";
import { Keypair } from "@solana/web3.js";
import invariant from "tiny-invariant";
Expand All @@ -20,11 +20,13 @@ import type {
} from "../instructions";
import {
closePositionIx,
closePositionWithTokenExtensionsIx,
increaseLiquidityIx,
increaseLiquidityV2Ix,
initTickArrayIx,
openPositionIx,
openPositionWithMetadataIx,
openPositionWithTokenExtensionsIx,
swapAsync,
} from "../instructions";
import { WhirlpoolIx } from "../ix";
Expand Down Expand Up @@ -111,6 +113,7 @@ export class WhirlpoolImpl implements Whirlpool {
wallet?: Address,
funder?: Address,
positionMint?: PublicKey,
tokenProgramId?: PublicKey,
) {
await this.refresh();
return this.getOpenPositionWithOptMetadataTx(
Expand All @@ -119,6 +122,8 @@ export class WhirlpoolImpl implements Whirlpool {
liquidityInput,
!!wallet ? AddressUtil.toPubKey(wallet) : this.ctx.wallet.publicKey,
!!funder ? AddressUtil.toPubKey(funder) : this.ctx.wallet.publicKey,
// TOKEN_PROGRAM_ID for v0.13.x, TOKEN_2022_PROGRAM_ID for future releases
tokenProgramId ?? TOKEN_PROGRAM_ID,
false,
positionMint,
);
Expand All @@ -131,6 +136,7 @@ export class WhirlpoolImpl implements Whirlpool {
sourceWallet?: Address,
funder?: Address,
positionMint?: PublicKey,
tokenProgramId?: PublicKey,
) {
await this.refresh();
return this.getOpenPositionWithOptMetadataTx(
Expand All @@ -141,6 +147,8 @@ export class WhirlpoolImpl implements Whirlpool {
? AddressUtil.toPubKey(sourceWallet)
: this.ctx.wallet.publicKey,
!!funder ? AddressUtil.toPubKey(funder) : this.ctx.wallet.publicKey,
// TOKEN_PROGRAM_ID for v0.13.x, TOKEN_2022_PROGRAM_ID for future releases
tokenProgramId ?? TOKEN_PROGRAM_ID,
true,
positionMint,
);
Expand Down Expand Up @@ -294,6 +302,7 @@ export class WhirlpoolImpl implements Whirlpool {
liquidityInput: IncreaseLiquidityInput,
wallet: PublicKey,
funder: PublicKey,
tokenProgramId: PublicKey,
withMetadata: boolean = false,
positionMint?: PublicKey,
): Promise<{ positionMint: PublicKey; tx: TransactionBuilder }> {
Expand All @@ -305,6 +314,10 @@ export class WhirlpoolImpl implements Whirlpool {
TickUtil.checkTickInBounds(tickUpper),
"tickUpper is out of bounds.",
);
invariant(
tokenProgramId.equals(TOKEN_PROGRAM_ID) || tokenProgramId.equals(TOKEN_2022_PROGRAM_ID),
"tokenProgramId must be either TOKEN_PROGRAM_ID or TOKEN_2022_PROGRAM_ID",
);

const { liquidityAmount: liquidity, tokenMaxA, tokenMaxB } = liquidityInput;

Expand Down Expand Up @@ -347,6 +360,7 @@ export class WhirlpoolImpl implements Whirlpool {
positionMintPubkey,
wallet,
this.ctx.accountResolverOpts.allowPDAOwnerAddress,
tokenProgramId,
);

const txBuilder = new TransactionBuilder(
Expand All @@ -355,20 +369,28 @@ export class WhirlpoolImpl implements Whirlpool {
this.ctx.txBuilderOpts,
);

const positionIx = (
withMetadata ? openPositionWithMetadataIx : openPositionIx
)(this.ctx.program, {
const params = {
funder,
owner: wallet,
positionPda,
metadataPda,
positionMintAddress: positionMintPubkey,
positionTokenAccount: positionTokenAccountAddress,
whirlpool: this.address,
tickLowerIndex: tickLower,
tickUpperIndex: tickUpper,
});
};
const positionIx = tokenProgramId.equals(TOKEN_2022_PROGRAM_ID)
? openPositionWithTokenExtensionsIx(this.ctx.program, {
...params,
positionMint: positionMintPubkey,
withTokenMetadataExtension: withMetadata,
})
: (withMetadata ? openPositionWithMetadataIx : openPositionIx)(this.ctx.program, {
...params,
positionMintAddress: positionMintPubkey,
metadataPda,
});
txBuilder.addInstruction(positionIx);

if (positionMint === undefined) {
txBuilder.addSigner(positionMintKeypair);
}
Expand Down Expand Up @@ -466,6 +488,15 @@ export class WhirlpoolImpl implements Whirlpool {
throw new Error(`Position not found: ${positionAddress.toBase58()}`);
}

const positionMint = await this.ctx.fetcher.getMintInfo(
positionData.positionMint,
);
if (!positionMint) {
throw new Error(
`Position mint not found: ${positionData.positionMint.toBase58()}`,
);
}

const whirlpool = this.data;

invariant(
Expand All @@ -477,6 +508,7 @@ export class WhirlpoolImpl implements Whirlpool {
positionData.positionMint,
positionWallet,
this.ctx.accountResolverOpts.allowPDAOwnerAddress,
positionMint.tokenProgram,
);

const accountExemption = await this.ctx.fetcher.getAccountRentExempt();
Expand Down Expand Up @@ -527,6 +559,7 @@ export class WhirlpoolImpl implements Whirlpool {
whirlpool,
tickArrayLowerData,
tickArrayUpperData,
positionMint.tokenProgram,
);

const tickLower = position.getLowerTickData();
Expand Down Expand Up @@ -769,15 +802,19 @@ export class WhirlpoolImpl implements Whirlpool {

/* Close position */
await builder.addInstructions(async () => {
const ix = closePositionIx(this.ctx.program, {
const closePositionParams = {
positionAuthority: positionWallet,
receiver: destinationWallet,
positionTokenAccount,
position: positionAddress,
positionMint: positionData.positionMint,
});
};

return [ix];
if (positionMint.tokenProgram.equals(TOKEN_2022_PROGRAM_ID)) {
return [closePositionWithTokenExtensionsIx(this.ctx.program, closePositionParams)]
} else {
return [closePositionIx(this.ctx.program, closePositionParams)];
}
});

return builder.build();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { Program } from "@coral-xyz/anchor";
import type { Instruction } from "@orca-so/common-sdk";
import { TOKEN_2022_PROGRAM_ID } from "@solana/spl-token";
import type { PublicKey } from "@solana/web3.js";
import type { Whirlpool } from "../artifacts/whirlpool";

/**
* Parameters to close a position (based on Token-2022) in a Whirlpool.
*
* @category Instruction Types
* @param receiver - PublicKey for the wallet that will receive the rented lamports.
* @param position - PublicKey for the position.
* @param positionMint - PublicKey for the mint token for the Position token.
* @param positionTokenAccount - The associated token address for the position token in the owners wallet.
* @param positionAuthority - Authority that owns the position token.
*/
export type ClosePositionWithTokenExtensionsParams = {
receiver: PublicKey;
position: PublicKey;
positionMint: PublicKey;
positionTokenAccount: PublicKey;
positionAuthority: PublicKey;
};

/**
* Close a position in a Whirlpool. Burns the position token in the owner's wallet.
* Mint and TokenAccount are based on Token-2022. And Mint accout will be also closed.
*
* @category Instructions
* @param context - Context object containing services required to generate the instruction
* @param params - ClosePositionWithTokenExtensionsParams object
* @returns - Instruction to perform the action.
*/
export function closePositionWithTokenExtensionsIx(
program: Program<Whirlpool>,
params: ClosePositionWithTokenExtensionsParams,
): Instruction {
const ix = program.instruction.closePositionWithTokenExtensions({
accounts: {
...params,
token2022Program: TOKEN_2022_PROGRAM_ID,
},
});

return {
instructions: [ix],
cleanupInstructions: [],
signers: [],
};
}
Loading