Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ meteora-invent/
│ │ │ ├── close_position.ts
│ │ │ ├── create_balanced_pool.ts
│ │ │ ├── create_one_sided_pool.ts
│ │ │ ├── refresh_vesting.ts
│ │ │ ├── remove_liquidity.ts
│ │ │ └── split_position.ts
│ │ ├── dbc
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,15 @@ command to remove the liquidity and close the position.
pnpm studio damm-v2-remove-liquidity --poolAddress <YOUR_POOL_ADDRESS>
```

##### Refresh Vesting

If you already have an existing position in a DAMM v2 pool with vested liquidity, you can run the
following command to refresh the vesting of the position if the vesting of the LP has already ended.

```bash
pnpm studio damm-v2-refresh-vesting --poolAddress <YOUR_POOL_ADDRESS>
```

##### Close Position

If you already have an existing position in a DAMM v2 pool without liquidity, you can run the
Expand Down Expand Up @@ -475,6 +484,7 @@ meteora-invent/
│ │ │ ├── close_position.ts
│ │ │ ├── create_balanced_pool.ts
│ │ │ ├── create_one_sided_pool.ts
│ │ │ ├── refresh_vesting.ts
│ │ │ ├── remove_liquidity.ts
│ │ │ └── split_position.ts
│ │ ├── dbc
Expand Down
1 change: 1 addition & 0 deletions studio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"damm-v2-claim-position-fee": "tsx src/actions/damm_v2/claim_position_fee.ts",
"damm-v2-add-liquidity": "tsx src/actions/damm_v2/add_liquidity.ts",
"damm-v2-remove-liquidity": "tsx src/actions/damm_v2/remove_liquidity.ts",
"damm-v2-refresh-vesting": "tsx src/actions/damm_v2/refresh_vesting.ts",
"damm-v2-close-position": "tsx src/actions/damm_v2/close_position.ts",
"damm-v1-create-pool": "tsx src/actions/damm_v1/create_pool.ts",
"damm-v1-lock-liquidity": "tsx src/actions/damm_v1/lock_liquidity.ts",
Expand Down
36 changes: 36 additions & 0 deletions studio/src/actions/damm_v2/refresh_vesting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Connection, PublicKey } from '@solana/web3.js';
import { Wallet } from '@coral-xyz/anchor';
import { DEFAULT_COMMITMENT_LEVEL } from '../../utils/constants';
import { getDammV2Config, parseCliArguments, safeParseKeypairFromFile } from '../../helpers';
import { refreshVesting } from '../../lib/damm_v2';

async function main() {
const config = await getDammV2Config();

console.log(`> Using keypair file path ${config.keypairFilePath}`);
const keypair = await safeParseKeypairFromFile(config.keypairFilePath);

console.log('\n> Initializing configuration...');
console.log(`- Using RPC URL ${config.rpcUrl}`);
console.log(`- Dry run = ${config.dryRun}`);
console.log(`- Using payer ${keypair.publicKey} to execute commands`);

const connection = new Connection(config.rpcUrl, DEFAULT_COMMITMENT_LEVEL);
const wallet = new Wallet(keypair);

const { poolAddress: poolKey } = parseCliArguments();
if (!poolKey) {
throw new Error('Please provide --poolAddress flag to do this action');
}
const poolAddress = new PublicKey(poolKey);

console.log(`- Using pool address ${poolAddress.toString()}`);

if (config) {
await refreshVesting(config, connection, wallet, poolAddress);
} else {
throw new Error('Must provide Dynamic V2 configuration');
}
}

main();
153 changes: 153 additions & 0 deletions studio/src/lib/damm_v2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,159 @@ export async function claimPositionFee(
}
}

/**
* Refresh vesting to unlock available liquidity
* @param config - The DAMM V2 config
* @param connection - The connection to the network
* @param wallet - The wallet to use for the transaction
* @param poolAddress - The pool address
*/
export async function refreshVesting(
config: DammV2Config,
connection: Connection,
wallet: Wallet,
poolAddress: PublicKey
) {
if (!poolAddress) {
throw new Error('Pool address is required');
}

console.log('\n> Refreshing vesting...');

const cpAmmInstance = new CpAmm(connection);

const userPositions = await cpAmmInstance.getUserPositionByPool(poolAddress, wallet.publicKey);

if (userPositions.length === 0) {
console.log('> No position found');
return;
}

console.log(`\n> Pool address: ${poolAddress.toString()}`);
console.log(`\n> Found ${userPositions.length} position(s) in this pool`);

const positionDataArray = [];
for (const userPosition of userPositions) {
const positionState = await cpAmmInstance.fetchPositionState(userPosition.position);
const vestings = await cpAmmInstance.getAllVestingsByPosition(userPosition.position);
positionDataArray.push({
userPosition,
positionState,
vestings,
});
}

let selectedPositionData;

if (userPositions.length === 1) {
selectedPositionData = positionDataArray[0];
console.log('> Only one position found, refreshing vesting for that position...');
} else {
const positionOptions = positionDataArray.map((data, index) => {
const { positionState, vestings } = data;
const positionAddress = data.userPosition.position.toString().slice(0, 8) + '...';

return [
`Position ${index + 1} (${positionAddress})`,
` - Unlocked Liquidity: ${positionState.unlockedLiquidity.toString()}`,
` - Vested Liquidity: ${positionState.vestedLiquidity.toString()}`,
` - Permanent Locked Liquidity: ${positionState.permanentLockedLiquidity.toString()}`,
` - Vesting Accounts: ${vestings.length}`,
].join('\n');
});

const selectedIndex = await promptForSelection(
positionOptions,
'Which position would you like to refresh vesting for?'
);

selectedPositionData = positionDataArray[selectedIndex];
console.log(`\n> Selected position ${selectedIndex + 1} for refreshing vesting...`);
}

if (!selectedPositionData) {
throw new Error('No position selected');
}
const { userPosition, positionState, vestings } = selectedPositionData;

console.log('\n> Position Vesting Information:');
console.log(`- Position Address: ${userPosition.position.toString()}`);
console.log(`- Unlocked Liquidity: ${positionState.unlockedLiquidity.toString()}`);
console.log(`- Vested Liquidity: ${positionState.vestedLiquidity.toString()}`);
console.log(`- Permanent Locked Liquidity: ${positionState.permanentLockedLiquidity.toString()}`);
console.log(`- Number of Vesting Accounts: ${vestings.length}`);

if (vestings.length === 0) {
console.log('\n> No vesting accounts found for this position. Nothing to refresh.');
return;
}

console.log('\n> Vesting Accounts:');
for (const [i, vesting] of vestings.entries()) {
console.log(` Vesting ${i + 1}:`);
console.log(` - Address: ${vesting.publicKey.toString()}`);
console.log(` - Cliff Unlock Liquidity: ${vesting.account.cliffUnlockLiquidity.toString()}`);
console.log(` - Liquidity Per Period: ${vesting.account.liquidityPerPeriod.toString()}`);
console.log(` - Cliff Point: ${vesting.account.cliffPoint.toString()}`);
console.log(` - Number of Periods: ${vesting.account.numberOfPeriod.toString()}`);
console.log(
` - Total Released Liquidity: ${vesting.account.totalReleasedLiquidity.toString()}`
);
}

const refreshVestingTx = await cpAmmInstance.refreshVesting({
owner: wallet.publicKey,
position: userPosition.position,
positionNftAccount: userPosition.positionNftAccount,
pool: poolAddress,
vestingAccounts: vestings.map((v) => v.publicKey),
});

modifyComputeUnitPriceIx(refreshVestingTx, config.computeUnitPriceMicroLamports ?? 0);

if (config.dryRun) {
console.log(`\n> Simulating refresh vesting transaction...`);
await runSimulateTransaction(connection, [wallet.payer], wallet.publicKey, [refreshVestingTx]);
console.log('> Refresh vesting simulation successful');
} else {
console.log(`\n>> Sending refresh vesting transaction...`);

const refreshVestingTxHash = await sendAndConfirmTransaction(
connection,
refreshVestingTx,
[wallet.payer],
{
commitment: connection.commitment,
maxRetries: DEFAULT_SEND_TX_MAX_RETRIES,
}
).catch((err) => {
console.error(`Failed to refresh vesting:`, err);
throw err;
});

console.log(`>>> Vesting refreshed successfully with tx hash: ${refreshVestingTxHash}`);

const updatedPositionState = await cpAmmInstance.fetchPositionState(userPosition.position);
console.log('\n> Updated Position State:');
console.log(`- Unlocked Liquidity: ${updatedPositionState.unlockedLiquidity.toString()}`);
console.log(`- Vested Liquidity: ${updatedPositionState.vestedLiquidity.toString()}`);
console.log(
`- Permanent Locked Liquidity: ${updatedPositionState.permanentLockedLiquidity.toString()}`
);

const liquidityUnlocked = updatedPositionState.unlockedLiquidity.sub(
positionState.unlockedLiquidity
);
if (liquidityUnlocked.gtn(0)) {
console.log(`\n> Successfully unlocked ${liquidityUnlocked.toString()} liquidity units!`);
} else {
console.log(
'\n> No additional liquidity was unlocked. The vesting schedule may not have progressed yet.'
);
}
}
}

/**
* Add liquidity to a position
* @param config - The DAMM V2 config
Expand Down