Skip to content

Commit b5ecdb9

Browse files
g1nt0kiquanghuy219
andauthored
Krystal (DefiLlama#13409)
Co-authored-by: Huy Pham <[email protected]>
1 parent d4d0dfa commit b5ecdb9

File tree

5 files changed

+174
-3
lines changed

5 files changed

+174
-3
lines changed

projects/helper/utils/solana/layout.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
const { PublicKey } = require("@solana/web3.js");
33
const { parseLido, parseLidoValidatorList } = require('./layouts/lido')
44
const { parsePhoenix } = require('./layouts/phoenix-dex')
5-
const { RAYDIUM_LIQUIDITY_STATE_LAYOUT_CLMM, RAYDIUM_STABLE_STATE_LAYOUT_V1, } = require('./layouts/raydium-layout')
5+
const { RAYDIUM_LIQUIDITY_STATE_LAYOUT_CLMM, RAYDIUM_STABLE_STATE_LAYOUT_V1, RAYDIUM_POSITION_INFO_LAYOUT } = require('./layouts/raydium-layout')
66
const { INVESTIN_FUND_DATA, } = require('./layouts/investin-layout')
77
const { MARKET_STATE_LAYOUT_V3, OPEN_ORDERS_LAYOUT_V2, MARKET_STATE_LAYOUT_V3_MINIMAL } = require('./layouts/openbook-layout')
88
const { ReserveLayout, ReserveLayoutLarix, MintLayout, AccountLayout, TokenSwapLayout, ESOLStakePoolLayout, PARLAY_LAYOUT_PARTIAL, HH_PARI_LAYOUT_PARTIAL, ACCESS_LAYOUT,
@@ -57,6 +57,7 @@ const customDecoders = {
5757
openbookOpenOrders: defaultParseLayout(OPEN_ORDERS_LAYOUT_V2),
5858
// raydiumLPv4: defaultParseLayout(RAYDIUM_LIQUIDITY_STATE_LAYOUT_V4),
5959
raydiumCLMM: defaultParseLayout(RAYDIUM_LIQUIDITY_STATE_LAYOUT_CLMM),
60+
raydiumPositionInfo: defaultParseLayout(RAYDIUM_POSITION_INFO_LAYOUT),
6061
raydiumLPStable: defaultParseLayout(RAYDIUM_STABLE_STATE_LAYOUT_V1),
6162
scnStakePool: defaultParseLayout(SCN_STAKE_POOL),
6263
fluxbeam: defaultParseLayout(TokenSwapLayout),

projects/helper/utils/solana/layouts/raydium-layout.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,28 @@ const TokenAmountLayout = struct([
196196
u64('amount'),
197197
])
198198

199+
const PositionRewardInfoLayout = struct([u128("growthInsideLastX64"), u64("rewardAmountOwed")]);
200+
const RAYDIUM_POSITION_INFO_LAYOUT = struct([
201+
blob(8),
202+
u8("bump"),
203+
publicKey("nftMint"),
204+
publicKey("poolId"),
205+
206+
s32("tickLower"),
207+
s32("tickUpper"),
208+
u128("liquidity"),
209+
u128("feeGrowthInsideLastX64A"),
210+
u128("feeGrowthInsideLastX64B"),
211+
u64("tokenFeesOwedA"),
212+
u64("tokenFeesOwedB"),
213+
214+
seq(PositionRewardInfoLayout, 3, "rewardInfos"),
215+
216+
seq(u64(), 8, ""),
217+
]);
218+
199219

200220
module.exports = {
201-
RAYDIUM_LIQUIDITY_STATE_LAYOUT_CLMM, RAYDIUM_STABLE_STATE_LAYOUT_V1, KeyLayoutv4, TokenAmountLayout,
202-
}
221+
RAYDIUM_LIQUIDITY_STATE_LAYOUT_CLMM, RAYDIUM_STABLE_STATE_LAYOUT_V1,
222+
KeyLayoutv4, TokenAmountLayout, RAYDIUM_POSITION_INFO_LAYOUT
223+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"address": "6tgjvHkFUUUbbacEWg225H6AazxoSTso8ix9vkXFScTU",
3+
"metadata": {"name": "krystal_auto_vault", "version": "0.1.0", "spec": "0.1.0", "description": "Created with Anchor"},
4+
"instructions": [],
5+
"accounts": [
6+
{"name": "GlobalState", "discriminator": [163, 46, 74, 168, 216, 123, 133, 98]},
7+
{"name": "UserVault", "discriminator": [23, 76, 96, 159, 210, 10, 5, 22]}
8+
],
9+
"errors": [],
10+
"types": [
11+
{
12+
"name": "GlobalState",
13+
"type": {
14+
"kind": "struct",
15+
"fields": [{"name": "admin", "type": "pubkey"}, {"name": "bump", "type": "u8"}, {"name": "operators", "type": {"vec": "pubkey"}}]
16+
}
17+
},
18+
{"name": "UserVault", "type": {"kind": "struct", "fields": [{"name": "owner", "type": "pubkey"}, {"name": "bump", "type": "u8"}]}}
19+
],
20+
"events": []
21+
}

projects/krystal/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const { tvl: solanaTvl } = require("./solana.js");
2+
3+
module.exports = {
4+
solana: {
5+
tvl: solanaTvl,
6+
},
7+
isHeavyProtocol: true,
8+
methodology: "TVL: Sum of all positions' value in every vaults",
9+
};

projects/krystal/solana.js

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
const { Program } = require("@coral-xyz/anchor");
2+
const { PublicKey } = require("@solana/web3.js");
3+
const {
4+
getConnection,
5+
decodeAccount,
6+
TOKEN_PROGRAM_ID,
7+
TOKEN_2022_PROGRAM_ID,
8+
} = require("../helper/solana");
9+
const idl = require("./idl/krystal_auto_vault.json");
10+
const { addUniV3LikePosition } = require("../helper/unwrapLPs.js");
11+
const { getUniqueAddresses } = require("../helper/tokenMapping.js");
12+
const { get } = require("../helper/http.js");
13+
14+
const CLMM_PROGRAM_ID = new PublicKey(
15+
"CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK"
16+
);
17+
const POSITION_SEED = Buffer.from("position", "utf8");
18+
19+
async function tvl(api) {
20+
const connection = getConnection();
21+
22+
const program = new Program(idl, { connection });
23+
const pools = new Map();
24+
const pdaPersonalPositionAddressesAll = []
25+
26+
// Load all the vaults in the program
27+
const vaults = await program.account.userVault.all();
28+
29+
const positions = [];
30+
for (const account of vaults) {
31+
const vault = account.publicKey;
32+
await findClmmPositionsByOwner(connection, vault);
33+
}
34+
35+
const positionAccountInfos = await connection.getMultipleAccountsInfo(pdaPersonalPositionAddressesAll);
36+
37+
positionAccountInfos.map((account) => {
38+
if (!account) return;
39+
40+
positions.push(decodeAccount("raydiumPositionInfo", account));
41+
});
42+
43+
const poolIds = getUniqueAddresses(positions.map((position) => position.poolId.toBase58()), "solana");
44+
const poolAccounts = await connection.getMultipleAccountsInfo(poolIds.map(i => new PublicKey(i)));
45+
46+
for (let i = 0; i < poolIds.length; i++) {
47+
const poolId = poolIds[i];
48+
const poolAccount = poolAccounts[i];
49+
const poolInfo = decodeAccount("raydiumCLMM", poolAccount);
50+
pools.set(poolId, poolInfo);
51+
}
52+
53+
for (const position of positions) {
54+
const poolId = position.poolId;
55+
const poolKey = poolId.toBase58();
56+
57+
let poolInfo = pools.get(poolKey);
58+
59+
addUniV3LikePosition({
60+
api,
61+
token0: poolInfo.mintA.toBase58(),
62+
token1: poolInfo.mintB.toBase58(),
63+
liquidity: position.liquidity.toNumber(),
64+
tickLower: position.tickLower,
65+
tickUpper: position.tickUpper,
66+
tick: poolInfo.tickCurrent,
67+
});
68+
}
69+
70+
71+
async function findClmmPositionsByOwner(connection, owner) {
72+
const [tokenAccounts, token2022Accounts] = await Promise.all([
73+
connection.getParsedTokenAccountsByOwner(owner, {
74+
programId: TOKEN_PROGRAM_ID,
75+
}),
76+
connection.getParsedTokenAccountsByOwner(owner, {
77+
programId: TOKEN_2022_PROGRAM_ID,
78+
}),
79+
]);
80+
81+
const allTokenAccounts = [];
82+
if (tokenAccounts?.value) {
83+
allTokenAccounts.push(...tokenAccounts.value);
84+
}
85+
if (token2022Accounts?.value) {
86+
allTokenAccounts.push(...token2022Accounts.value);
87+
}
88+
89+
const tokenNftMints = [];
90+
allTokenAccounts.forEach((tokenAccount) => {
91+
const info = tokenAccount.account.data.parsed.info;
92+
if (info.tokenAmount.amount == "1" && info.tokenAmount.decimals == 0) {
93+
tokenNftMints.push(new PublicKey(info.mint));
94+
} else {
95+
api.add(info.mint, info.tokenAmount.amount)
96+
}
97+
});
98+
99+
const pdaPersonalPositionAddresses = tokenNftMints.map(getPdaPersonalPositionAddress)
100+
pdaPersonalPositionAddressesAll.push(...pdaPersonalPositionAddresses)
101+
}
102+
}
103+
104+
105+
function getPdaPersonalPositionAddress(nftMint) {
106+
const [pda] = PublicKey.findProgramAddressSync(
107+
[POSITION_SEED, nftMint.toBuffer()],
108+
CLMM_PROGRAM_ID
109+
);
110+
111+
return pda;
112+
}
113+
114+
async function tvlApi(api) {
115+
const res = await get('https://api.krystal.app/solana/v1/lp/tvl')
116+
api.addUSDValue(+res.tvl)
117+
}
118+
119+
module.exports = { tvl: tvlApi };

0 commit comments

Comments
 (0)