Skip to content

Commit

Permalink
Chore/replace tokeninfo with mint public key (#122)
Browse files Browse the repository at this point in the history
* chore: replace tokeninfo with mint public key

* chore: update naming

* fix: test case

* update pnpm setup version

* fix: affialate test case

---------

Co-authored-by: McSam <[email protected]>
  • Loading branch information
00xSam and McSam94 authored Jul 30, 2024
1 parent dc51f1b commit 0d5acb7
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 62 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci_ts_test_on_pull.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- uses: pnpm/action-setup@v2.2.2
- uses: pnpm/action-setup@v4
with:
version: 6.0.2
- name: Install dependencies
Expand Down
2 changes: 1 addition & 1 deletion ts-client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mercurial-finance/vault-sdk",
"version": "0.5.3",
"version": "1.0.0",
"description": "Mercurial Vault SDK is a typescript library that allows you to interact with Mercurial v2's vault.",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
Expand Down
78 changes: 38 additions & 40 deletions ts-client/src/vault/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
SystemProgram,
} from '@solana/web3.js';
import { MintLayout, TOKEN_PROGRAM_ID, u64, NATIVE_MINT } from '@solana/spl-token';
import { TokenInfo } from '@solana/spl-token-registry';

import { AffiliateInfo, AffiliateVaultProgram, VaultImplementation, VaultProgram, VaultState } from './types';
import {
Expand All @@ -30,10 +29,10 @@ import { IDL, Vault as VaultIdl } from './idl';
import { IDL as AffiliateIDL, AffiliateVault as AffiliateVaultIdl } from './affiliate-idl';
import { calculateWithdrawableAmount } from './helper';

type TokenInfoPda = { info: TokenInfo; vaultPda: PublicKey; tokenVaultPda: PublicKey; lpMintPda: PublicKey };
type PdaInfo = { tokenMint: PublicKey; vaultPda: PublicKey; tokenVaultPda: PublicKey; lpMintPda: PublicKey };

type VaultDetails = {
tokenInfo: TokenInfo;
tokenMint: PublicKey;
vaultPda: PublicKey;
tokenVaultPda: PublicKey;
lpMintPda: PublicKey;
Expand All @@ -50,15 +49,15 @@ type WithdrawOpt = {
};
};

const getAllVaultState = async (tokenInfos: Array<TokenInfo>, program: VaultProgram, seedBaseKey?: PublicKey) => {
const vaultAccountPdas = tokenInfos.map((tokenInfo) =>
getVaultPdas(new PublicKey(tokenInfo.address), new PublicKey(program.programId), seedBaseKey),
const getAllVaultState = async (tokensMint: Array<PublicKey>, program: VaultProgram, seedBaseKey?: PublicKey) => {
const vaultAccountPdas = tokensMint.map((tokenMint) =>
getVaultPdas(tokenMint, new PublicKey(program.programId), seedBaseKey),
);

const vaultPdas = vaultAccountPdas.map(({ vaultPda }) => vaultPda);
const vaultsState = (await chunkedFetchMultipleVaultAccount(program, vaultPdas)) as Array<VaultState>;

if (vaultsState.length !== tokenInfos.length) {
if (vaultsState.length !== tokensMint.length) {
throw new Error('Some of the vault state cannot be fetched');
}

Expand All @@ -76,19 +75,19 @@ const getAllVaultState = async (tokenInfos: Array<TokenInfo>, program: VaultProg
});
};

const getAllVaultStateByPda = async (tokensInfoPda: Array<TokenInfoPda>, program: VaultProgram) => {
const vaultPdas = tokensInfoPda.map(({ vaultPda }) => vaultPda);
const getAllVaultStateByPda = async (vaultsPdaInfo: Array<PdaInfo>, program: VaultProgram) => {
const vaultPdas = vaultsPdaInfo.map(({ vaultPda }) => vaultPda);
const vaultsState = (await chunkedFetchMultipleVaultAccount(program, vaultPdas)) as Array<VaultState>;

if (vaultsState.length !== tokensInfoPda.length) {
if (vaultsState.length !== vaultsPdaInfo.length) {
throw new Error('Some of the vault state cannot be fetched');
}

const vaultLpMints = vaultsState.map((vaultState) => vaultState.lpMint);
const vaultLpAccounts = await chunkedGetMultipleAccountInfos(program.provider.connection, vaultLpMints);

return vaultsState.map((vaultState, index) => {
const vaultAccountPda = tokensInfoPda[index];
const vaultAccountPda = vaultsPdaInfo[index];
if (!vaultAccountPda) throw new Error('Missing vault account pda');
const vaultLpAccount = vaultLpAccounts[index];
if (!vaultLpAccount) throw new Error('Missing vault lp account');
Expand All @@ -98,9 +97,9 @@ const getAllVaultStateByPda = async (tokensInfoPda: Array<TokenInfoPda>, program
});
};

const getVaultState = async (vaultParams: TokenInfo, program: VaultProgram, seedBaseKey?: PublicKey) => {
const getVaultState = async (vaultPublicKey: PublicKey, program: VaultProgram, seedBaseKey?: PublicKey) => {
const { vaultPda, tokenVaultPda } = getVaultPdas(
new PublicKey(vaultParams.address),
vaultPublicKey,
new PublicKey(program.programId),
seedBaseKey,
);
Expand All @@ -115,16 +114,16 @@ const getVaultState = async (vaultParams: TokenInfo, program: VaultProgram, seed
return { vaultPda, tokenVaultPda, vaultState, lpSupply, lpMintPda: vaultState.lpMint };
};

const getVaultStateByPda = async (tokenInfoPda: TokenInfoPda, program: VaultProgram) => {
const vaultState = (await program.account.vault.fetchNullable(tokenInfoPda.vaultPda)) as VaultState;
const getVaultStateByPda = async (pdaInfo: PdaInfo, program: VaultProgram) => {
const vaultState = (await program.account.vault.fetchNullable(pdaInfo.vaultPda)) as VaultState;

if (!vaultState) {
throw 'Cannot get vault state';
}

const lpSupply = await getLpSupply(program.provider.connection, tokenInfoPda.lpMintPda);
const lpSupply = await getLpSupply(program.provider.connection, pdaInfo.lpMintPda);

return { ...tokenInfoPda, vaultState, lpSupply };
return { ...pdaInfo, vaultState, lpSupply };
};

const getVaultLiquidity = async (connection: Connection, tokenVaultPda: PublicKey): Promise<string | null> => {
Expand All @@ -147,7 +146,7 @@ export default class VaultImpl implements VaultImplementation {
private allowOwnerOffCurve?: boolean;
public seedBaseKey?: PublicKey;

public tokenInfo: TokenInfo;
public tokenMint: PublicKey;
public vaultPda: PublicKey;
public tokenVaultPda: PublicKey;
public lpMintPda: PublicKey;
Expand All @@ -168,13 +167,13 @@ export default class VaultImpl implements VaultImplementation {
this.connection = program.provider.connection;
this.cluster = opt?.cluster ?? 'mainnet-beta';

this.tokenInfo = vaultDetails.tokenInfo;
this.program = program;
this.affiliateProgram = opt?.affiliateProgram;
this.affiliateId = opt?.affiliateId;

this.allowOwnerOffCurve = opt?.allowOwnerOffCurve;

this.tokenMint = vaultDetails.tokenMint;
this.vaultPda = vaultDetails.vaultPda;
this.tokenVaultPda = vaultDetails.tokenVaultPda;
this.lpMintPda = vaultDetails.lpMintPda;
Expand All @@ -185,7 +184,7 @@ export default class VaultImpl implements VaultImplementation {
public static async createPermissionlessVaultInstruction(
connection: Connection,
payer: PublicKey,
tokenInfo: TokenInfo,
tokenMint: PublicKey,
opt?: {
cluster?: Cluster;
programId?: string;
Expand All @@ -194,7 +193,6 @@ export default class VaultImpl implements VaultImplementation {
const provider = new AnchorProvider(connection, {} as any, AnchorProvider.defaultOptions());
const program = new Program<VaultIdl>(IDL as VaultIdl, opt?.programId || PROGRAM_ID, provider);

const tokenMint = new PublicKey(tokenInfo.address);
const {
vaultPda: vault,
tokenVaultPda: tokenVault,
Expand Down Expand Up @@ -237,7 +235,7 @@ export default class VaultImpl implements VaultImplementation {

public static async createMultiple(
connection: Connection,
tokenInfos: Array<TokenInfo>,
tokenMints: Array<PublicKey>,
opt?: {
seedBaseKey?: PublicKey;
allowOwnerOffCurve?: boolean;
Expand All @@ -250,13 +248,13 @@ export default class VaultImpl implements VaultImplementation {
const provider = new AnchorProvider(connection, {} as any, AnchorProvider.defaultOptions());
const program = new Program<VaultIdl>(IDL as VaultIdl, opt?.programId || PROGRAM_ID, provider);

const vaultsStateInfo = await getAllVaultState(tokenInfos, program);
const vaultsStateInfo = await getAllVaultState(tokenMints, program);

return vaultsStateInfo.map(({ vaultPda, tokenVaultPda, lpMintPda, vaultState, lpSupply }, index) => {
const tokenInfo = tokenInfos[index];
const tokenMint = tokenMints[index];
return new VaultImpl(
program,
{ tokenInfo, vaultPda, tokenVaultPda, vaultState, lpSupply, lpMintPda },
{ tokenMint, vaultPda, tokenVaultPda, vaultState, lpSupply, lpMintPda },
{
...opt,
affiliateId: opt?.affiliateId,
Expand All @@ -274,7 +272,7 @@ export default class VaultImpl implements VaultImplementation {

public static async createMultipleWithPda(
connection: Connection,
tokensInfoPda: Array<TokenInfoPda>,
pdaInfos: Array<PdaInfo>,
opt?: {
seedBaseKey?: PublicKey;
allowOwnerOffCurve?: boolean;
Expand All @@ -287,13 +285,13 @@ export default class VaultImpl implements VaultImplementation {
const provider = new AnchorProvider(connection, {} as any, AnchorProvider.defaultOptions());
const program = new Program<VaultIdl>(IDL as VaultIdl, opt?.programId || PROGRAM_ID, provider);

const vaultsStateInfo = await getAllVaultStateByPda(tokensInfoPda, program);
const vaultsStateInfo = await getAllVaultStateByPda(pdaInfos, program);

return vaultsStateInfo.map(({ vaultPda, tokenVaultPda, lpMintPda, vaultState, lpSupply }, index) => {
const tokenInfo = tokensInfoPda[index].info;
const { tokenMint } = pdaInfos[index];
return new VaultImpl(
program,
{ tokenInfo, vaultPda, tokenVaultPda, vaultState, lpSupply, lpMintPda },
{ tokenMint, vaultPda, tokenVaultPda, vaultState, lpSupply, lpMintPda },
{
...opt,
affiliateId: opt?.affiliateId,
Expand All @@ -311,7 +309,7 @@ export default class VaultImpl implements VaultImplementation {

public static async create(
connection: Connection,
tokenInfo: TokenInfo,
tokenMint: PublicKey,
opt?: {
seedBaseKey?: PublicKey;
allowOwnerOffCurve?: boolean;
Expand All @@ -324,10 +322,10 @@ export default class VaultImpl implements VaultImplementation {
const provider = new AnchorProvider(connection, {} as any, AnchorProvider.defaultOptions());
const program = new Program<VaultIdl>(IDL as VaultIdl, opt?.programId || PROGRAM_ID, provider);

const { vaultPda, tokenVaultPda, lpMintPda, vaultState, lpSupply } = await getVaultState(tokenInfo, program);
const { vaultPda, tokenVaultPda, lpMintPda, vaultState, lpSupply } = await getVaultState(tokenMint, program);
return new VaultImpl(
program,
{ tokenInfo, vaultPda, tokenVaultPda, vaultState, lpSupply, lpMintPda },
{ tokenMint, vaultPda, tokenVaultPda, vaultState, lpSupply, lpMintPda },
{
...opt,
affiliateId: opt?.affiliateId,
Expand Down Expand Up @@ -385,7 +383,7 @@ export default class VaultImpl implements VaultImplementation {

public async refreshVaultState() {
const { vaultState, lpSupply } = await getVaultStateByPda(
{ info: this.tokenInfo, lpMintPda: this.lpMintPda, tokenVaultPda: this.tokenVaultPda, vaultPda: this.vaultPda },
{ tokenMint: this.tokenMint, lpMintPda: this.lpMintPda, tokenVaultPda: this.tokenVaultPda, vaultPda: this.vaultPda },
this.program,
);
this.vaultState = vaultState;
Expand All @@ -395,7 +393,7 @@ export default class VaultImpl implements VaultImplementation {
private async createATAPreInstructions(owner: PublicKey) {
let preInstructions: TransactionInstruction[] = [];
const [userToken, createUserTokenIx] = await getOrCreateATAInstruction(
new PublicKey(this.tokenInfo.address),
this.tokenMint,
owner,
this.connection,
);
Expand All @@ -422,7 +420,7 @@ export default class VaultImpl implements VaultImplementation {
if (!this.affiliateId || !this.affiliateProgram) throw new Error('Affiliate ID or program not found');

const partner = this.affiliateId;
const partnerToken = await getAssociatedTokenAccount(new PublicKey(this.tokenInfo.address), partner);
const partnerToken = await getAssociatedTokenAccount(this.tokenMint, partner);

const [partnerAddress, _nonce] = PublicKey.findProgramAddressSync(
[this.vaultPda.toBuffer(), partnerToken.toBuffer()],
Expand All @@ -435,7 +433,7 @@ export default class VaultImpl implements VaultImplementation {

let preInstructions: TransactionInstruction[] = [];
const [userToken, createUserTokenIx] = await getOrCreateATAInstruction(
new PublicKey(this.tokenInfo.address),
this.tokenMint,
owner,
this.connection,
);
Expand Down Expand Up @@ -502,7 +500,7 @@ export default class VaultImpl implements VaultImplementation {
}

// If it's SOL vault, wrap desired amount of SOL
if (this.tokenInfo.address === NATIVE_MINT.toString()) {
if (this.tokenMint.equals(NATIVE_MINT)) {
preInstructions = preInstructions.concat(wrapSOLInstruction(owner, userToken, baseTokenAmount));
}

Expand Down Expand Up @@ -674,7 +672,7 @@ export default class VaultImpl implements VaultImplementation {

// Unwrap SOL
const postInstruction: Array<TransactionInstruction> = [];
if (this.tokenInfo.address === NATIVE_MINT.toString()) {
if (this.tokenMint.equals(NATIVE_MINT)) {
const closeWrappedSOLIx = await unwrapSOLInstruction(owner);
if (closeWrappedSOLIx) {
postInstruction.push(closeWrappedSOLIx);
Expand Down Expand Up @@ -718,7 +716,7 @@ export default class VaultImpl implements VaultImplementation {
): Promise<Transaction> {
// Unwrap SOL
const postInstruction: Array<TransactionInstruction> = [];
if (this.tokenInfo.address === NATIVE_MINT.toString()) {
if (this.tokenMint.equals(NATIVE_MINT)) {
const closeWrappedSOLIx = await unwrapSOLInstruction(owner);
if (closeWrappedSOLIx) {
postInstruction.push(closeWrappedSOLIx);
Expand Down Expand Up @@ -768,7 +766,7 @@ export default class VaultImpl implements VaultImplementation {
if (!this.affiliateId || !this.affiliateProgram) throw new Error('No affiliateId or affiliate program found');

const partner = this.affiliateId;
const partnerToken = await getAssociatedTokenAccount(new PublicKey(this.tokenInfo.address), partner);
const partnerToken = await getAssociatedTokenAccount(this.tokenMint, partner);

const [partnerAddress, _nonce] = PublicKey.findProgramAddressSync(
[this.vaultPda.toBuffer(), partnerToken.toBuffer()],
Expand Down
8 changes: 2 additions & 6 deletions ts-client/src/vault/tests/affiliate.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import { StaticTokenListResolutionStrategy, TokenInfo } from '@solana/spl-token-registry';
import { Wallet, AnchorProvider, BN } from '@project-serum/anchor';

import VaultImpl from '..';
import { airDropSol } from './utils';
import { NATIVE_MINT } from '@solana/spl-token';

const mockWallet = new Wallet(new Keypair());
// devnet ATA creation and reading must use confirmed.
const devnetConnection = new Connection('https://api.devnet.solana.com/', { commitment: 'confirmed' });

const tokenMap = new StaticTokenListResolutionStrategy().resolve();
const SOL_TOKEN_INFO = tokenMap.find((token) => token.symbol === 'SOL') as TokenInfo;
const USDC_TOKEN_INFO = tokenMap.find((token) => token.symbol === 'USDC') as TokenInfo;
const USDT_TOKEN_INFO = tokenMap.find((token) => token.symbol === 'USDT') as TokenInfo;

// TODO: Remove this fake partner ID
const TEMPORARY_PARTNER_PUBLIC_KEY = new PublicKey('7236FoaWTXJyzbfFPZcrzg3tBpPhGiTgXsGWvjwrYfiF');
Expand All @@ -24,7 +20,7 @@ describe('Interact with Vault in devnet', () => {
let vaultImpl: VaultImpl;
beforeAll(async () => {
await airDropSol(devnetConnection, mockWallet.publicKey);
vaultImpl = await VaultImpl.create(devnetConnection, SOL_TOKEN_INFO, {
vaultImpl = await VaultImpl.create(devnetConnection, NATIVE_MINT, {
cluster: 'devnet',
affiliateId: TEMPORARY_PARTNER_PUBLIC_KEY,
});
Expand Down
22 changes: 8 additions & 14 deletions ts-client/src/vault/tests/vault.test.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,32 @@
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import { StaticTokenListResolutionStrategy, TokenInfo } from '@solana/spl-token-registry';
import { Wallet, AnchorProvider, BN } from '@project-serum/anchor';

import VaultImpl from '..';
import { airDropSol } from './utils';
import { getVaultPdas } from '../utils';
import { PROGRAM_ID } from '../constants';
import { PROGRAM_ID, USDC_MINT, USDT_MINT } from '../constants';
import { NATIVE_MINT } from '@solana/spl-token';

const mockWallet = new Wallet(new Keypair());
const mainnetConnection = new Connection('https://api.mainnet-beta.solana.com');
// devnet ATA creation and reading must use confirmed.
const devnetConnection = new Connection('https://api.devnet.solana.com/', { commitment: 'confirmed' });

// Prevent importing directly from .json, causing slowdown on Intellisense
const tokenMap = new StaticTokenListResolutionStrategy().resolve();
const SOL_TOKEN_INFO = tokenMap.find((token) => token.symbol === 'SOL') as TokenInfo;
const USDC_TOKEN_INFO = tokenMap.find((token) => token.symbol === 'USDC') as TokenInfo;
const USDT_TOKEN_INFO = tokenMap.find((token) => token.symbol === 'USDT') as TokenInfo;

describe('Get Mainnet vault state', () => {
let vaults: VaultImpl[] = [];
let vaultsForPool: VaultImpl[] = [];

// Make sure all vaults can be initialized
beforeAll(async () => {
const tokensInfo = [SOL_TOKEN_INFO, USDC_TOKEN_INFO, USDT_TOKEN_INFO];
const tokensInfoPda = tokensInfo.map((tokenInfo) => {
const vaultPdas = getVaultPdas(new PublicKey(tokenInfo.address), new PublicKey(PROGRAM_ID));
const tokensMint = [NATIVE_MINT, USDC_MINT, USDT_MINT];
const tokensInfoPda = tokensMint.map((tokenMint) => {
const vaultPdas = getVaultPdas(tokenMint, new PublicKey(PROGRAM_ID));
return {
info: tokenInfo,
tokenMint,
...vaultPdas,
};
});
vaults = await VaultImpl.createMultiple(mainnetConnection, tokensInfo);
vaults = await VaultImpl.createMultiple(mainnetConnection, tokensMint);
vaultsForPool = await VaultImpl.createMultipleWithPda(mainnetConnection, tokensInfoPda);
});

Expand Down Expand Up @@ -78,7 +72,7 @@ describe('Interact with Vault in devnet', () => {
let vault: VaultImpl;
beforeAll(async () => {
await airDropSol(devnetConnection, mockWallet.publicKey);
vault = await VaultImpl.create(devnetConnection, SOL_TOKEN_INFO, { cluster: 'devnet' });
vault = await VaultImpl.create(devnetConnection, NATIVE_MINT, { cluster: 'devnet' });
});

test('Deposit, check balance, withdraw', async () => {
Expand Down

0 comments on commit 0d5acb7

Please sign in to comment.