diff --git a/ts-client/package.json b/ts-client/package.json index 4c01d321..b33acce2 100644 --- a/ts-client/package.json +++ b/ts-client/package.json @@ -1,6 +1,6 @@ { "name": "@mercurial-finance/dynamic-amm-sdk", - "version": "0.4.8", + "version": "0.4.9", "description": "Mercurial Vaults SDK is a typescript library that allows you to interact with Mercurial v2's AMM.", "main": "dist/cjs/index.js", "module": "dist/esm/index.js", diff --git a/ts-client/src/amm/index.ts b/ts-client/src/amm/index.ts index d0b2009b..d09f51fb 100644 --- a/ts-client/src/amm/index.ts +++ b/ts-client/src/amm/index.ts @@ -844,9 +844,16 @@ export default class AmmImpl implements AmmImplementation { * @param {PublicKey} inTokenMint - The mint of the token you're swapping from. * @param {BN} inAmountLamport - The amount of the input token you want to swap. * @param {BN} outAmountLamport - The minimum amount of the output token you want to receive. + * @param {PublicKey} [referrerToken] - The referrer fee token account. The mint of the token account must matches inTokenMint. 20% of admin trade fee. * @returns A transaction object */ - public async swap(owner: PublicKey, inTokenMint: PublicKey, inAmountLamport: BN, outAmountLamport: BN) { + public async swap( + owner: PublicKey, + inTokenMint: PublicKey, + inAmountLamport: BN, + outAmountLamport: BN, + referrerToken?: PublicKey, + ) { const [sourceToken, destinationToken] = this.tokenA.address === inTokenMint.toBase58() ? [this.poolState.tokenAMint, this.poolState.tokenBMint] @@ -874,6 +881,15 @@ export default class AmmImpl implements AmmImplementation { unwrapSOLIx && postInstructions.push(unwrapSOLIx); } + const remainingAccounts = this.swapCurve.getRemainingAccounts(); + if (referrerToken) { + remainingAccounts.push({ + isSigner: false, + isWritable: true, + pubkey: referrerToken, + }); + } + const swapTx = await this.program.methods .swap(inAmountLamport, outAmountLamport) .accounts({ @@ -893,7 +909,7 @@ export default class AmmImpl implements AmmImplementation { tokenProgram: TOKEN_PROGRAM_ID, vaultProgram: this.vaultProgram.programId, }) - .remainingAccounts(this.swapCurve.getRemainingAccounts()) + .remainingAccounts(remainingAccounts) .preInstructions(preInstructions) .postInstructions(postInstructions) .transaction(); diff --git a/ts-client/src/amm/tests/index.test.ts b/ts-client/src/amm/tests/index.test.ts index dfe22a29..1e8367dc 100644 --- a/ts-client/src/amm/tests/index.test.ts +++ b/ts-client/src/amm/tests/index.test.ts @@ -1,10 +1,11 @@ -import { Cluster, PublicKey } from '@solana/web3.js'; -import AmmImpl from '../index'; -import { DEFAULT_SLIPPAGE, MAINNET_POOL, DEVNET_POOL, DEVNET_COIN } from '../constants'; import { AnchorProvider, BN } from '@project-serum/anchor'; -import { TokenListProvider, TokenInfo } from '@solana/spl-token-registry'; +import { TokenInfo, TokenListProvider } from '@solana/spl-token-registry'; +import { Cluster, Keypair, PublicKey } from '@solana/web3.js'; +import { DEFAULT_SLIPPAGE, DEVNET_COIN, DEVNET_POOL, MAINNET_POOL } from '../constants'; +import AmmImpl from '../index'; import { calculateSwapQuote, getOnchainTime } from '../utils'; -import { DEVNET, MAINNET, airDropSol, mockWallet } from './utils'; +import { DEVNET, MAINNET, airDropSol, getOrCreateATA, mockWallet } from './utils'; +import { NATIVE_MINT } from '@solana/spl-token'; describe('Interact with Devnet pool', () => { const provider = new AnchorProvider(DEVNET.connection, mockWallet, { @@ -17,6 +18,8 @@ describe('Interact with Devnet pool', () => { let currentDepegPoolBalance: BN; let currentStablePoolBalance: BN; + let referrer = Keypair.generate(); + beforeAll(async () => { await airDropSol(DEVNET.connection, mockWallet.publicKey); @@ -88,6 +91,45 @@ describe('Interact with Devnet pool', () => { } }); + test('Swap SOL → USDT with referrer fee', async () => { + const referrerSolAta = await getOrCreateATA(provider.connection, NATIVE_MINT, referrer.publicKey, mockWallet.payer); + const inAmountLamport = new BN(0.1 * 10 ** cpPool.tokenB.decimals); + + const { swapOutAmount, minSwapOutAmount } = cpPool.getSwapQuote( + new PublicKey(cpPool.tokenB.address), + inAmountLamport, + DEFAULT_SLIPPAGE, + ); + expect(swapOutAmount.toNumber()).toBeGreaterThan(0); + + const swapTx = await cpPool.swap( + mockWallet.publicKey, + new PublicKey(cpPool.tokenB.address), + inAmountLamport, + minSwapOutAmount, + referrerSolAta, + ); + + try { + const beforeReferrerTokenBalance = await provider.connection + .getTokenAccountBalance(referrerSolAta) + .then((r) => r.value.uiAmount); + + const swapResult = await provider.sendAndConfirm(swapTx); + console.log('Swap Result of SOL → USDT', swapResult); + expect(typeof swapResult).toBe('string'); + + const afterReferrerTokenBalance = await provider.connection + .getTokenAccountBalance(referrerSolAta) + .then((r) => r.value.uiAmount); + + expect(afterReferrerTokenBalance!).toBeGreaterThan(beforeReferrerTokenBalance!); + } catch (error: any) { + console.trace(error); + throw new Error(error.message); + } + }); + test('Swap USDT → SOL', async () => { const inAmountLamport = new BN(0.1 * 10 ** cpPool.tokenA.decimals); @@ -115,7 +157,7 @@ describe('Interact with Devnet pool', () => { } }); - test('SWAP USDT -> USDC', async () => { + test('Swap USDT -> USDC', async () => { const inAmountLamport = new BN(0.1 * 10 ** stablePool.tokenA.decimals); const { swapOutAmount, minSwapOutAmount } = stablePool.getSwapQuote( new PublicKey(stablePool.tokenA.address), @@ -141,7 +183,7 @@ describe('Interact with Devnet pool', () => { } }); - test('SWAP USDC -> USDT', async () => { + test('Swap USDC -> USDT', async () => { const inAmountLamport = new BN(0.1 * 10 ** stablePool.tokenB.decimals); const { swapOutAmount, minSwapOutAmount } = stablePool.getSwapQuote( new PublicKey(stablePool.tokenB.address), @@ -194,6 +236,45 @@ describe('Interact with Devnet pool', () => { } }); + test('Swap SOL → mSOL with referrer fee', async () => { + const referrerSolAta = await getOrCreateATA(provider.connection, NATIVE_MINT, referrer.publicKey, mockWallet.payer); + const inAmountLamport = new BN(0.01 * 10 ** depegPool.tokenA.decimals); + + const { swapOutAmount, minSwapOutAmount } = depegPool.getSwapQuote( + new PublicKey(depegPool.tokenA.address), + inAmountLamport, + DEFAULT_SLIPPAGE, + ); + expect(swapOutAmount.toNumber()).toBeGreaterThan(0); + + const swapTx = await depegPool.swap( + mockWallet.publicKey, + new PublicKey(depegPool.tokenA.address), + inAmountLamport, + minSwapOutAmount, + referrerSolAta, + ); + + try { + const beforeReferrerTokenBalance = await provider.connection + .getTokenAccountBalance(referrerSolAta) + .then((r) => r.value.uiAmount); + + const swapResult = await provider.sendAndConfirm(swapTx); + console.log('Swap Result of SOL → mSOL', swapResult); + expect(typeof swapResult).toBe('string'); + + const afterReferrerTokenBalance = await provider.connection + .getTokenAccountBalance(referrerSolAta) + .then((r) => r.value.uiAmount); + + expect(afterReferrerTokenBalance!).toBeGreaterThan(beforeReferrerTokenBalance!); + } catch (error: any) { + console.trace(error); + throw new Error(error.message); + } + }); + test('Swap mSOL → SOL', async () => { const inAmountLamport = new BN(0.01 * 10 ** depegPool.tokenB.decimals); diff --git a/ts-client/src/amm/tests/utils/index.ts b/ts-client/src/amm/tests/utils/index.ts index 7d125967..80728a41 100644 --- a/ts-client/src/amm/tests/utils/index.ts +++ b/ts-client/src/amm/tests/utils/index.ts @@ -1,5 +1,6 @@ import { Wallet } from '@project-serum/anchor'; import { bs58 } from '@project-serum/anchor/dist/cjs/utils/bytes'; +import { TOKEN_PROGRAM_ID, Token } from '@solana/spl-token'; import { Connection, Keypair, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js'; export const airDropSol = async (connection: Connection, publicKey: PublicKey, amount = 1) => { @@ -17,6 +18,13 @@ export const airDropSol = async (connection: Connection, publicKey: PublicKey, a } }; +export const getOrCreateATA = async (connection: Connection, mint: PublicKey, owner: PublicKey, payer: Keypair) => { + const token = new Token(connection, mint, TOKEN_PROGRAM_ID, payer); + const ata = await token.getOrCreateAssociatedAccountInfo(owner); + + return ata.address; +}; + export const mockWallet = new Wallet( process.env.WALLET_PRIVATE_KEY ? Keypair.fromSecretKey(bs58.decode(process.env.WALLET_PRIVATE_KEY)) : new Keypair(), );