@kamino-finance/klend-sdk 7.3.0 → 7.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -46,6 +46,9 @@ import {
46
46
  addUpdateWhitelistedReserve,
47
47
  AddUpdateWhitelistedReserveAccounts,
48
48
  AddUpdateWhitelistedReserveArgs,
49
+ buy,
50
+ BuyAccounts,
51
+ BuyArgs,
49
52
  deposit,
50
53
  DepositAccounts,
51
54
  DepositArgs,
@@ -59,6 +62,9 @@ import {
59
62
  InvestAccounts,
60
63
  removeAllocation,
61
64
  RemoveAllocationAccounts,
65
+ sell,
66
+ SellAccounts,
67
+ SellArgs,
62
68
  updateAdmin,
63
69
  UpdateAdminAccounts,
64
70
  updateReserveAllocation,
@@ -1270,6 +1276,92 @@ export class KaminoVaultClient {
1270
1276
  return depositIxs;
1271
1277
  }
1272
1278
 
1279
+ // todo (silviu): after all tx indexing works for buy/sell ixs remove this function and use the buyIx in the deposit function above
1280
+ async buySharesIxs(
1281
+ user: TransactionSigner,
1282
+ vault: KaminoVault,
1283
+ tokenAmount: Decimal,
1284
+ vaultReservesMap?: Map<Address, KaminoReserve>,
1285
+ farmState?: FarmState
1286
+ ): Promise<DepositIxs> {
1287
+ const vaultState = await vault.getState();
1288
+
1289
+ const tokenProgramID = vaultState.tokenProgram;
1290
+ const userTokenAta = await getAssociatedTokenAddress(vaultState.tokenMint, user.address, tokenProgramID);
1291
+ const createAtasIxs: Instruction[] = [];
1292
+ const closeAtasIxs: Instruction[] = [];
1293
+ if (vaultState.tokenMint === WRAPPED_SOL_MINT) {
1294
+ const [{ ata: wsolAta, createAtaIx: createWsolAtaIxn }] = await createAtasIdempotent(user, [
1295
+ {
1296
+ mint: WRAPPED_SOL_MINT,
1297
+ tokenProgram: TOKEN_PROGRAM_ADDRESS,
1298
+ },
1299
+ ]);
1300
+ createAtasIxs.push(createWsolAtaIxn);
1301
+ const transferWsolIxs = getTransferWsolIxs(
1302
+ user,
1303
+ wsolAta,
1304
+ lamports(
1305
+ BigInt(numberToLamportsDecimal(tokenAmount, vaultState.tokenMintDecimals.toNumber()).ceil().toString())
1306
+ )
1307
+ );
1308
+ createAtasIxs.push(...transferWsolIxs);
1309
+ }
1310
+
1311
+ const [{ ata: userSharesAta, createAtaIx: createSharesAtaIxs }] = await createAtasIdempotent(user, [
1312
+ {
1313
+ mint: vaultState.sharesMint,
1314
+ tokenProgram: TOKEN_PROGRAM_ADDRESS,
1315
+ },
1316
+ ]);
1317
+ createAtasIxs.push(createSharesAtaIxs);
1318
+
1319
+ const eventAuthority = await getEventAuthorityPda(this._kaminoVaultProgramId);
1320
+ const buyAccounts: BuyAccounts = {
1321
+ user: user,
1322
+ vaultState: vault.address,
1323
+ tokenVault: vaultState.tokenVault,
1324
+ tokenMint: vaultState.tokenMint,
1325
+ baseVaultAuthority: vaultState.baseVaultAuthority,
1326
+ sharesMint: vaultState.sharesMint,
1327
+ userTokenAta: userTokenAta,
1328
+ userSharesAta: userSharesAta,
1329
+ tokenProgram: tokenProgramID,
1330
+ klendProgram: this._kaminoLendProgramId,
1331
+ sharesTokenProgram: TOKEN_PROGRAM_ADDRESS,
1332
+ eventAuthority: eventAuthority,
1333
+ program: this._kaminoVaultProgramId,
1334
+ };
1335
+
1336
+ const buyArgs: BuyArgs = {
1337
+ maxAmount: new BN(
1338
+ numberToLamportsDecimal(tokenAmount, vaultState.tokenMintDecimals.toNumber()).floor().toString()
1339
+ ),
1340
+ };
1341
+
1342
+ let buyIx = buy(buyArgs, buyAccounts, undefined, this._kaminoVaultProgramId);
1343
+
1344
+ const vaultReserves = this.getVaultReserves(vaultState);
1345
+
1346
+ const vaultReservesState = vaultReservesMap ? vaultReservesMap : await this.loadVaultReserves(vaultState);
1347
+ buyIx = this.appendRemainingAccountsForVaultReserves(buyIx, vaultReserves, vaultReservesState);
1348
+
1349
+ const depositIxs: DepositIxs = {
1350
+ depositIxs: [...createAtasIxs, buyIx, ...closeAtasIxs],
1351
+ stakeInFarmIfNeededIxs: [],
1352
+ };
1353
+
1354
+ // if there is no farm, we can return the deposit instructions, otherwise include the stake ix in the response
1355
+ if (!(await vault.hasFarm())) {
1356
+ return depositIxs;
1357
+ }
1358
+
1359
+ // if there is a farm, stake the shares
1360
+ const stakeSharesIxs = await this.stakeSharesIxs(user, vault, undefined, farmState);
1361
+ depositIxs.stakeInFarmIfNeededIxs = stakeSharesIxs;
1362
+ return depositIxs;
1363
+ }
1364
+
1273
1365
  /**
1274
1366
  * This function creates instructions to stake the shares in the vault farm if the vault has a farm
1275
1367
  * @param user - user to stake
@@ -1439,6 +1531,135 @@ export class KaminoVaultClient {
1439
1531
  return withdrawIxs;
1440
1532
  }
1441
1533
 
1534
+ async sellSharesIxs(
1535
+ user: TransactionSigner,
1536
+ vault: KaminoVault,
1537
+ shareAmountToWithdraw: Decimal,
1538
+ slot: Slot,
1539
+ vaultReservesMap?: Map<Address, KaminoReserve>,
1540
+ farmState?: FarmState
1541
+ ): Promise<WithdrawIxs> {
1542
+ const vaultState = await vault.getState();
1543
+ const hasFarm = await vault.hasFarm();
1544
+
1545
+ const withdrawIxs: WithdrawIxs = {
1546
+ unstakeFromFarmIfNeededIxs: [],
1547
+ withdrawIxs: [],
1548
+ postWithdrawIxs: [],
1549
+ };
1550
+
1551
+ // compute the total shares the user has (in ATA + in farm) and check if they want to withdraw everything or just a part
1552
+ let userSharesAtaBalance = new Decimal(0);
1553
+ const userSharesAta = await getAssociatedTokenAddress(vaultState.sharesMint, user.address);
1554
+ const userSharesAtaState = await fetchMaybeToken(this.getConnection(), userSharesAta);
1555
+ if (userSharesAtaState.exists) {
1556
+ const userSharesAtaBalanceInLamports = getTokenBalanceFromAccountInfoLamports(userSharesAtaState);
1557
+ userSharesAtaBalance = userSharesAtaBalanceInLamports.div(
1558
+ new Decimal(10).pow(vaultState.sharesMintDecimals.toString())
1559
+ );
1560
+ }
1561
+
1562
+ let userSharesInFarm = new Decimal(0);
1563
+ if (hasFarm) {
1564
+ userSharesInFarm = await getUserSharesInTokensStakedInFarm(
1565
+ this.getConnection(),
1566
+ user.address,
1567
+ vaultState.vaultFarm,
1568
+ vaultState.sharesMintDecimals.toNumber()
1569
+ );
1570
+ }
1571
+
1572
+ let sharesToWithdraw = shareAmountToWithdraw;
1573
+ const totalUserShares = userSharesAtaBalance.add(userSharesInFarm);
1574
+ let withdrawAllShares = false;
1575
+ if (sharesToWithdraw.gt(totalUserShares)) {
1576
+ sharesToWithdraw = new Decimal(U64_MAX.toString()).div(
1577
+ new Decimal(10).pow(vaultState.sharesMintDecimals.toString())
1578
+ );
1579
+ withdrawAllShares = true;
1580
+ }
1581
+
1582
+ // if not enough shares in ATA unstake from farm
1583
+ const sharesInAtaAreEnoughForWithdraw = sharesToWithdraw.lte(userSharesAtaBalance);
1584
+ if (hasFarm && !sharesInAtaAreEnoughForWithdraw && userSharesInFarm.gt(0)) {
1585
+ // if we need to unstake we need to make sure share ata is created
1586
+ const [{ createAtaIx }] = await createAtasIdempotent(user, [
1587
+ {
1588
+ mint: vaultState.sharesMint,
1589
+ tokenProgram: TOKEN_PROGRAM_ADDRESS,
1590
+ },
1591
+ ]);
1592
+ withdrawIxs.unstakeFromFarmIfNeededIxs.push(createAtaIx);
1593
+ let shareLamportsToWithdraw = new Decimal(U64_MAX.toString());
1594
+ if (!withdrawAllShares) {
1595
+ const sharesToWithdrawFromFarm = sharesToWithdraw.sub(userSharesAtaBalance);
1596
+ shareLamportsToWithdraw = collToLamportsDecimal(
1597
+ sharesToWithdrawFromFarm,
1598
+ vaultState.sharesMintDecimals.toNumber()
1599
+ );
1600
+ }
1601
+ const unstakeAndWithdrawFromFarmIxs = await getFarmUnstakeAndWithdrawIxs(
1602
+ this.getConnection(),
1603
+ user,
1604
+ shareLamportsToWithdraw,
1605
+ vaultState.vaultFarm,
1606
+ farmState
1607
+ );
1608
+ withdrawIxs.unstakeFromFarmIfNeededIxs.push(unstakeAndWithdrawFromFarmIxs.unstakeIx);
1609
+ withdrawIxs.unstakeFromFarmIfNeededIxs.push(unstakeAndWithdrawFromFarmIxs.withdrawIx);
1610
+ }
1611
+
1612
+ // if the vault has allocations withdraw otherwise wtihdraw from available ix
1613
+ const vaultAllocation = vaultState.vaultAllocationStrategy.find(
1614
+ (allocation) => allocation.reserve !== DEFAULT_PUBLIC_KEY
1615
+ );
1616
+
1617
+ if (vaultAllocation) {
1618
+ const withdrawFromVaultIxs = await this.sellSharesWithReserveIxs(
1619
+ user,
1620
+ vault,
1621
+ sharesToWithdraw,
1622
+ totalUserShares,
1623
+ slot,
1624
+ vaultReservesMap
1625
+ );
1626
+ withdrawIxs.withdrawIxs = withdrawFromVaultIxs;
1627
+ } else {
1628
+ const withdrawFromVaultIxs = await this.withdrawFromAvailableIxs(user, vault, sharesToWithdraw);
1629
+ withdrawIxs.withdrawIxs = withdrawFromVaultIxs;
1630
+ }
1631
+
1632
+ // if the vault is for SOL return the ix to unwrap the SOL
1633
+ if (vaultState.tokenMint === WRAPPED_SOL_MINT) {
1634
+ const userWsolAta = await getAssociatedTokenAddress(WRAPPED_SOL_MINT, user.address);
1635
+ const unwrapIx = getCloseAccountInstruction(
1636
+ {
1637
+ account: userWsolAta,
1638
+ owner: user,
1639
+ destination: user.address,
1640
+ },
1641
+ { programAddress: TOKEN_PROGRAM_ADDRESS }
1642
+ );
1643
+ withdrawIxs.postWithdrawIxs.push(unwrapIx);
1644
+ }
1645
+
1646
+ // if we burn all of user's shares close its shares ATA
1647
+ const burnAllUserShares = sharesToWithdraw.gt(totalUserShares);
1648
+ if (burnAllUserShares) {
1649
+ const closeAtaIx = getCloseAccountInstruction(
1650
+ {
1651
+ account: userSharesAta,
1652
+ owner: user,
1653
+ destination: user.address,
1654
+ },
1655
+ { programAddress: TOKEN_PROGRAM_ADDRESS }
1656
+ );
1657
+ withdrawIxs.postWithdrawIxs.push(closeAtaIx);
1658
+ }
1659
+
1660
+ return withdrawIxs;
1661
+ }
1662
+
1442
1663
  private async withdrawFromAvailableIxs(
1443
1664
  user: TransactionSigner,
1444
1665
  vault: KaminoVault,
@@ -1467,6 +1688,115 @@ export class KaminoVaultClient {
1467
1688
  return [createAtaIx, withdrawFromAvailableIxn];
1468
1689
  }
1469
1690
 
1691
+ private async sellSharesWithReserveIxs(
1692
+ user: TransactionSigner,
1693
+ vault: KaminoVault,
1694
+ shareAmount: Decimal,
1695
+ allUserShares: Decimal,
1696
+ slot: Slot,
1697
+ vaultReservesMap?: Map<Address, KaminoReserve>
1698
+ ): Promise<Instruction[]> {
1699
+ const vaultState = await vault.getState();
1700
+
1701
+ const vaultReservesState = vaultReservesMap ? vaultReservesMap : await this.loadVaultReserves(vaultState);
1702
+ const userSharesAta = await getAssociatedTokenAddress(vaultState.sharesMint, user.address);
1703
+ const [{ ata: userTokenAta, createAtaIx }] = await createAtasIdempotent(user, [
1704
+ {
1705
+ mint: vaultState.tokenMint,
1706
+ tokenProgram: vaultState.tokenProgram,
1707
+ },
1708
+ ]);
1709
+
1710
+ const withdrawAllShares = shareAmount.gte(allUserShares);
1711
+ const actualSharesToWithdraw = shareAmount.lte(allUserShares) ? shareAmount : allUserShares;
1712
+ const shareLamportsToWithdraw = collToLamportsDecimal(
1713
+ actualSharesToWithdraw,
1714
+ vaultState.sharesMintDecimals.toNumber()
1715
+ );
1716
+ const tokensPerShare = await this.getTokensPerShareSingleVault(vault, slot);
1717
+ const sharesPerToken = new Decimal(1).div(tokensPerShare);
1718
+ const tokensToWithdraw = shareLamportsToWithdraw.mul(tokensPerShare);
1719
+ let tokenLeftToWithdraw = tokensToWithdraw;
1720
+ const availableTokens = new Decimal(vaultState.tokenAvailable.toString());
1721
+ tokenLeftToWithdraw = tokenLeftToWithdraw.sub(availableTokens);
1722
+
1723
+ type ReserveWithTokensToWithdraw = { reserve: Address; shares: Decimal };
1724
+
1725
+ const reserveWithSharesAmountToWithdraw: ReserveWithTokensToWithdraw[] = [];
1726
+ let isFirstWithdraw = true;
1727
+
1728
+ if (tokenLeftToWithdraw.lte(0)) {
1729
+ // Availabe enough to withdraw all - using the first existent reserve
1730
+ const firstReserve = vaultState.vaultAllocationStrategy.find((reserve) => reserve.reserve !== DEFAULT_PUBLIC_KEY);
1731
+ if (withdrawAllShares) {
1732
+ reserveWithSharesAmountToWithdraw.push({
1733
+ reserve: firstReserve!.reserve,
1734
+ shares: new Decimal(U64_MAX.toString()),
1735
+ });
1736
+ } else {
1737
+ reserveWithSharesAmountToWithdraw.push({
1738
+ reserve: firstReserve!.reserve,
1739
+ shares: shareLamportsToWithdraw,
1740
+ });
1741
+ }
1742
+ } else {
1743
+ // Get decreasing order sorted available liquidity to withdraw from each reserve allocated to
1744
+ const reserveAllocationAvailableLiquidityToWithdraw = await this.getReserveAllocationAvailableLiquidityToWithdraw(
1745
+ vault,
1746
+ slot,
1747
+ vaultReservesState
1748
+ );
1749
+ // sort
1750
+ const reserveAllocationAvailableLiquidityToWithdrawSorted = [
1751
+ ...reserveAllocationAvailableLiquidityToWithdraw.entries(),
1752
+ ].sort((a, b) => b[1].sub(a[1]).toNumber());
1753
+
1754
+ reserveAllocationAvailableLiquidityToWithdrawSorted.forEach(([key, availableLiquidityToWithdraw], _) => {
1755
+ if (tokenLeftToWithdraw.gt(0)) {
1756
+ let tokensToWithdrawFromReserve = Decimal.min(tokenLeftToWithdraw, availableLiquidityToWithdraw);
1757
+ if (isFirstWithdraw) {
1758
+ tokensToWithdrawFromReserve = tokensToWithdrawFromReserve.add(availableTokens);
1759
+ isFirstWithdraw = false;
1760
+ }
1761
+ if (withdrawAllShares) {
1762
+ reserveWithSharesAmountToWithdraw.push({ reserve: key, shares: new Decimal(U64_MAX.toString()) });
1763
+ } else {
1764
+ // round up to the nearest integer the shares to withdraw
1765
+ const sharesToWithdrawFromReserve = tokensToWithdrawFromReserve.mul(sharesPerToken).floor();
1766
+ reserveWithSharesAmountToWithdraw.push({ reserve: key, shares: sharesToWithdrawFromReserve });
1767
+ }
1768
+
1769
+ tokenLeftToWithdraw = tokenLeftToWithdraw.sub(tokensToWithdrawFromReserve);
1770
+ }
1771
+ });
1772
+ }
1773
+
1774
+ const withdrawIxs: Instruction[] = [];
1775
+ withdrawIxs.push(createAtaIx);
1776
+ for (let reserveIndex = 0; reserveIndex < reserveWithSharesAmountToWithdraw.length; reserveIndex++) {
1777
+ const reserveWithTokens = reserveWithSharesAmountToWithdraw[reserveIndex];
1778
+ const reserveState = vaultReservesState.get(reserveWithTokens.reserve);
1779
+ if (reserveState === undefined) {
1780
+ throw new Error(`Reserve ${reserveWithTokens.reserve} not found in vault reserves map`);
1781
+ }
1782
+ const marketAddress = reserveState.state.lendingMarket;
1783
+
1784
+ const withdrawFromReserveIx = await this.sellIx(
1785
+ user,
1786
+ vault,
1787
+ vaultState,
1788
+ marketAddress,
1789
+ { address: reserveWithTokens.reserve, state: reserveState.state },
1790
+ userSharesAta,
1791
+ userTokenAta,
1792
+ reserveWithTokens.shares,
1793
+ vaultReservesState
1794
+ );
1795
+ withdrawIxs.push(withdrawFromReserveIx);
1796
+ }
1797
+
1798
+ return withdrawIxs;
1799
+ }
1470
1800
  private async withdrawWithReserveIxs(
1471
1801
  user: TransactionSigner,
1472
1802
  vault: KaminoVault,
@@ -1788,6 +2118,65 @@ export class KaminoVaultClient {
1788
2118
  }
1789
2119
  }
1790
2120
 
2121
+ private async sellIx(
2122
+ user: TransactionSigner,
2123
+ vault: KaminoVault,
2124
+ vaultState: VaultState,
2125
+ marketAddress: Address,
2126
+ reserve: ReserveWithAddress,
2127
+ userSharesAta: Address,
2128
+ userTokenAta: Address,
2129
+ shareAmountLamports: Decimal,
2130
+ vaultReservesState: Map<Address, KaminoReserve>
2131
+ ): Promise<Instruction> {
2132
+ const [lendingMarketAuth] = await lendingMarketAuthPda(marketAddress, this._kaminoLendProgramId);
2133
+
2134
+ const globalConfig = await getKvaultGlobalConfigPda(this._kaminoVaultProgramId);
2135
+ const eventAuthority = await getEventAuthorityPda(this._kaminoVaultProgramId);
2136
+ const sellAccounts: SellAccounts = {
2137
+ withdrawFromAvailable: {
2138
+ user,
2139
+ vaultState: vault.address,
2140
+ globalConfig: globalConfig,
2141
+ tokenVault: vaultState.tokenVault,
2142
+ baseVaultAuthority: vaultState.baseVaultAuthority,
2143
+ userTokenAta: userTokenAta,
2144
+ tokenMint: vaultState.tokenMint,
2145
+ userSharesAta: userSharesAta,
2146
+ sharesMint: vaultState.sharesMint,
2147
+ tokenProgram: vaultState.tokenProgram,
2148
+ sharesTokenProgram: TOKEN_PROGRAM_ADDRESS,
2149
+ klendProgram: this._kaminoLendProgramId,
2150
+ eventAuthority: eventAuthority,
2151
+ program: this._kaminoVaultProgramId,
2152
+ },
2153
+ withdrawFromReserveAccounts: {
2154
+ vaultState: vault.address,
2155
+ reserve: reserve.address,
2156
+ ctokenVault: await getCTokenVaultPda(vault.address, reserve.address, this._kaminoVaultProgramId),
2157
+ lendingMarket: marketAddress,
2158
+ lendingMarketAuthority: lendingMarketAuth,
2159
+ reserveLiquiditySupply: reserve.state.liquidity.supplyVault,
2160
+ reserveCollateralMint: reserve.state.collateral.mintPubkey,
2161
+ reserveCollateralTokenProgram: TOKEN_PROGRAM_ADDRESS,
2162
+ instructionSysvarAccount: SYSVAR_INSTRUCTIONS_ADDRESS,
2163
+ },
2164
+ eventAuthority: eventAuthority,
2165
+ program: this._kaminoVaultProgramId,
2166
+ };
2167
+
2168
+ const sellArgs: SellArgs = {
2169
+ sharesAmount: new BN(shareAmountLamports.floor().toString()),
2170
+ };
2171
+
2172
+ let sellIxn = sell(sellArgs, sellAccounts, undefined, this._kaminoVaultProgramId);
2173
+
2174
+ const vaultReserves = this.getVaultReserves(vaultState);
2175
+ sellIxn = this.appendRemainingAccountsForVaultReserves(sellIxn, vaultReserves, vaultReservesState);
2176
+
2177
+ return sellIxn;
2178
+ }
2179
+
1791
2180
  private async withdrawIx(
1792
2181
  user: TransactionSigner,
1793
2182
  vault: KaminoVault,