watcher.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. // sellWorker.ts
  2. import { parentPort, workerData } from 'worker_threads';
  3. import { PublicKey, TokenAmount, Connection, Commitment } from '@solana/web3.js';
  4. import { LIQUIDITY_STATE_LAYOUT_V4, Liquidity, SPL_ACCOUNT_LAYOUT, TOKEN_PROGRAM_ID, TokenAccount } from '@raydium-io/raydium-sdk';
  5. import { retrieveEnvVariable } from '../utils';
  6. import BN from 'bn.js';
  7. import pino from 'pino';
  8. const transport = pino.transport({
  9. targets: [
  10. {
  11. level: 'trace',
  12. target: 'pino-pretty',
  13. options: {},
  14. },
  15. ],
  16. });
  17. export const logger = pino(
  18. {
  19. redact: ['poolKeys'],
  20. serializers: {
  21. error: pino.stdSerializers.err,
  22. },
  23. base: undefined,
  24. },
  25. transport,
  26. );
  27. async function getTokenAccounts(connection: Connection, owner: PublicKey) {
  28. const tokenResp = await connection.getTokenAccountsByOwner(owner, {
  29. programId: TOKEN_PROGRAM_ID
  30. });
  31. const accounts: TokenAccount[] = [];
  32. for (const { pubkey, account } of tokenResp.value) {
  33. accounts.push({
  34. pubkey,
  35. accountInfo: SPL_ACCOUNT_LAYOUT.decode(account.data),
  36. programId: new PublicKey(account.owner.toBase58())
  37. });
  38. }
  39. return accounts;
  40. }
  41. const SOL_SDC_POOL_ID = "58oQChx4yWmvKdwLLZzBi4ChoCc2fqCUWBkwMihLYQo2";
  42. const OPENBOOK_PROGRAM_ID = new PublicKey(
  43. "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX"
  44. );
  45. async function parsePoolInfo() {
  46. const network = 'mainnet-beta';
  47. const RPC_ENDPOINT = retrieveEnvVariable('RPC_ENDPOINT', logger);
  48. const RPC_WEBSOCKET_ENDPOINT = retrieveEnvVariable(
  49. 'RPC_WEBSOCKET_ENDPOINT',
  50. logger,
  51. );
  52. const connection = new Connection(RPC_ENDPOINT, {
  53. wsEndpoint: RPC_WEBSOCKET_ENDPOINT,
  54. });
  55. const owner = new PublicKey("VnxDzsZ7chE88e9rB6UKztCt2HUwrkgCTx8WieWf5mM");
  56. const tokenAccounts = await getTokenAccounts(connection, owner);
  57. // example to get pool info
  58. const info = await connection.getAccountInfo(new PublicKey(SOL_SDC_POOL_ID));
  59. if (!info) {
  60. throw new Error("Pool not found");
  61. }
  62. const poolState = LIQUIDITY_STATE_LAYOUT_V4.decode(info.data);
  63. const baseDecimal = 10 ** poolState.baseDecimal.toNumber();
  64. const quoteDecimal = 10 ** poolState.quoteDecimal.toNumber();
  65. const baseTokenAmount = await connection.getTokenAccountBalance(
  66. poolState.baseVault
  67. )
  68. const quoteTokenAmount = await connection.getTokenAccountBalance(
  69. poolState.quoteVault
  70. )
  71. const basePnl = poolState.baseNeedTakePnl.toNumber() / baseDecimal;
  72. const quotePnl = poolState.quoteNeedTakePnl.toNumber() / quoteDecimal;
  73. const base = (baseTokenAmount.value?.uiAmount || 0) - basePnl;
  74. const quote = (quoteTokenAmount.value?.uiAmount || 0) - quotePnl;
  75. const denominator = new BN(10).pow(poolState.baseDecimal);
  76. const addedLpAccount = tokenAccounts.find((a) => a.accountInfo.mint.equals(poolState.lpMint));
  77. const message = `
  78. SOL - USDC Pool Info:
  79. Pool total base: ${base},
  80. Pool total quote: ${quote},
  81. Base vault balance: ${baseTokenAmount.value.uiAmount},
  82. Quote vault balance: ${quoteTokenAmount.value.uiAmount},
  83. Base token decimals: ${poolState.baseDecimal.toNumber()},
  84. Quote token decimals: ${poolState.quoteDecimal.toNumber()},
  85. Total LP: ${poolState.lpReserve.div(denominator).toString()},
  86. Added LP amount: ${(addedLpAccount?.accountInfo.amount.toNumber() || 0) / baseDecimal},
  87. `;
  88. logger.info(message);
  89. // send message to discord (embed)
  90. // post to discord webhook
  91. let embed = {
  92. embeds: [
  93. {
  94. title: "SOL - USDC Pool Info",
  95. description: `
  96. Pool total base: **${base}**,
  97. Pool total quote: **${quote}**,
  98. Base vault balance: **${baseTokenAmount.value.uiAmount}**,
  99. Quote vault balance: **${quoteTokenAmount.value.uiAmount}**,
  100. Base token decimals:** ${poolState.baseDecimal.toNumber()}**,
  101. Quote token decimals:** ${poolState.quoteDecimal.toNumber()}**,
  102. Total LP: **${poolState.lpReserve.div(denominator).toString()}**,
  103. Added LP amount: **${(addedLpAccount?.accountInfo.amount.toNumber() || 0) / baseDecimal}**
  104. Happy trading! 🚀
  105. `
  106. }
  107. ]
  108. };
  109. const DISCORD_WEBHOOK = retrieveEnvVariable('DISCORD_WEBHOOK', logger);
  110. // use native fetch to post to discord
  111. fetch(DISCORD_WEBHOOK, {
  112. method: 'POST',
  113. headers: {
  114. 'Content-Type': 'application/json',
  115. },
  116. body: JSON.stringify(embed),
  117. });
  118. logger.info("Message sent to Discord");
  119. }
  120. // Function to periodically check the pool
  121. async function checkPoolPeriodically(interval: number) {
  122. while (true) {
  123. await parsePoolInfo();
  124. await new Promise(resolve => setTimeout(resolve, interval));
  125. }
  126. }
  127. // Check pool periodically with a specified interval
  128. checkPoolPeriodically(60000); // 1 minute