feat: use pool open time for pool detection

This commit is contained in:
Filip Dunder
2024-02-09 14:56:18 +01:00
parent 0c89f0d5e0
commit ee3e252de7
3 changed files with 47 additions and 167 deletions

52
buy.ts
View File

@ -25,7 +25,6 @@ import {
Commitment,
} from '@solana/web3.js';
import {
getAllAccountsV4,
getTokenAccounts,
RAYDIUM_LIQUIDITY_PROGRAM_ID_V4,
OPENBOOK_PROGRAM_ID,
@ -145,31 +144,17 @@ async function init(): Promise<void> {
`Script will buy all new tokens using ${QUOTE_MINT}. Amount that will be used to buy each token is: ${quoteAmount.toFixed().toString()}`,
);
// get all existing liquidity pools
const allLiquidityPools = await getAllAccountsV4(
quoteToken.mint,
);
existingLiquidityPools = new Set(
allLiquidityPools.map((p) => p.id.toString()),
);
logger.info(`Loading existing markets...`);
// get all open-book markets
const allMarkets = await getAllMarketsV3();
existingOpenBookMarkets = new Set(allMarkets.map((p) => p.id.toString()));
logger.info(
`Total ${quoteToken.symbol} markets ${existingOpenBookMarkets.size}`,
);
logger.info(
`Total ${quoteToken.symbol} pools ${existingLiquidityPools.size}`,
);
const tokenAccounts = await getTokenAccounts(
const allMarkets = await getAllMarketsV3(
solanaConnection,
wallet.publicKey,
quoteToken.mint,
commitment,
);
existingOpenBookMarkets = new Set(allMarkets.map((p) => p.id.toString()));
logger.info(
`Loaded ${existingOpenBookMarkets.size} ${quoteToken.symbol} markets`,
);
// check existing wallet for associated token account of quote mint
const tokenAccounts = await getTokenAccounts(
solanaConnection,
@ -202,18 +187,17 @@ async function init(): Promise<void> {
loadSnipeList();
}
export async function processRaydiumPool(updatedAccountInfo: KeyedAccountInfo) {
export async function processRaydiumPool(
id: PublicKey,
poolState: LiquidityStateV4,
) {
let accountData: LiquidityStateV4 | undefined;
try {
accountData = LIQUIDITY_STATE_LAYOUT_V4.decode(
updatedAccountInfo.accountInfo.data,
);
if (!shouldBuy(accountData.baseMint.toString())) {
if (!shouldBuy(poolState.baseMint.toString())) {
return;
}
await buy(updatedAccountInfo.accountId, accountData);
await buy(id, poolState);
} catch (e) {
logger.error({ ...accountData, error: e }, `Failed to process pool`);
}
@ -343,14 +327,20 @@ function shouldBuy(key: string): boolean {
const runListener = async () => {
await init();
const runTimestamp = Math.floor(new Date().getTime() / 1000);
const raydiumSubscriptionId = solanaConnection.onProgramAccountChange(
RAYDIUM_LIQUIDITY_PROGRAM_ID_V4,
async (updatedAccountInfo) => {
const key = updatedAccountInfo.accountId.toString();
const poolState = LIQUIDITY_STATE_LAYOUT_V4.decode(
updatedAccountInfo.accountInfo.data,
);
const poolOpenTime = parseInt(poolState.poolOpenTime.toString());
const existing = existingLiquidityPools.has(key);
if (!existing) {
if (poolOpenTime > runTimestamp && !existing) {
existingLiquidityPools.add(key);
const _ = processRaydiumPool(updatedAccountInfo);
const _ = processRaydiumPool(updatedAccountInfo.accountId, poolState);
}
},
commitment,

View File

@ -12,26 +12,6 @@ import {
} from '@raydium-io/raydium-sdk';
import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
import { MinimalMarketLayoutV3 } from '../market';
import bs58 from 'bs58';
import axios from 'axios';
interface LiquidityPool {
id: string;
baseMint: string;
quoteMint: string;
// ... autres propriétés
}
interface LiquidityJsonResponse {
official: LiquidityPool[];
unOfficial: LiquidityPool[];
}
interface MinimalLiquidityAccountData {
id: string;
version: number;
programId: string;
}
export const RAYDIUM_LIQUIDITY_PROGRAM_ID_V4 = MAINNET_PROGRAM_ID.AmmV4;
export const OPENBOOK_PROGRAM_ID = MAINNET_PROGRAM_ID.OPENBOOK_MARKET;
@ -42,33 +22,6 @@ export const MINIMAL_MARKET_STATE_LAYOUT_V3 = struct([
publicKey('asks'),
]);
export async function getAllAccountsV4(
quoteMint: PublicKey,
): Promise<{ id: string; version: number; programId: PublicKey }[]> {
const url = 'https://api.raydium.io/v2/sdk/liquidity/mainnet.json';
try {
const response = await axios.get<LiquidityJsonResponse>(url);
// @ts-ignore
const json = response.data;
const filteredPools = json.official.concat(json.unOfficial)
.filter(pool => {
if (!pool) {
console.log('Pool undefined:', pool);
return false;
}
return pool.quoteMint && pool.quoteMint === quoteMint.toBase58();
});
return filteredPools.map(pool => ({
id: pool.id,
version: 4,
programId: RAYDIUM_LIQUIDITY_PROGRAM_ID_V4, // Assurez-vous que cette constante est définie
}));
} catch (error) {
console.error('Error during data retrieval:', error);
return [];
}
}
export function createPoolKeys(
id: PublicKey,
accountData: LiquidityStateV4,
@ -109,59 +62,6 @@ export function createPoolKeys(
};
}
export async function getAccountPoolKeysFromAccountDataV4(
connection: Connection,
id: PublicKey,
accountData: LiquidityStateV4,
commitment?: Commitment,
): Promise<LiquidityPoolKeys> {
const marketInfo = await connection.getAccountInfo(accountData.marketId, {
commitment: commitment,
dataSlice: {
offset: 253, // eventQueue
length: 32 * 3,
},
});
const minimalMarketData = MINIMAL_MARKET_STATE_LAYOUT_V3.decode(
marketInfo!.data,
);
return {
id,
baseMint: accountData.baseMint,
quoteMint: accountData.quoteMint,
lpMint: accountData.lpMint,
baseDecimals: accountData.baseDecimal.toNumber(),
quoteDecimals: accountData.quoteDecimal.toNumber(),
lpDecimals: 5,
version: 4,
programId: RAYDIUM_LIQUIDITY_PROGRAM_ID_V4,
authority: Liquidity.getAssociatedAuthority({
programId: RAYDIUM_LIQUIDITY_PROGRAM_ID_V4,
}).publicKey,
openOrders: accountData.openOrders,
targetOrders: accountData.targetOrders,
baseVault: accountData.baseVault,
quoteVault: accountData.quoteVault,
marketVersion: 3,
marketProgramId: accountData.marketProgramId,
marketId: accountData.marketId,
marketAuthority: Market.getAssociatedAuthority({
programId: accountData.marketProgramId,
marketId: accountData.marketId,
}).publicKey,
marketBaseVault: accountData.baseVault,
marketQuoteVault: accountData.quoteVault,
marketBids: minimalMarketData.bids,
marketAsks: minimalMarketData.asks,
marketEventQueue: minimalMarketData.eventQueue,
withdrawQueue: accountData.withdrawQueue,
lpVault: accountData.lpVault,
lookupTableAccount: PublicKey.default,
};
}
export async function getTokenAccounts(
connection: Connection,
owner: PublicKey,

View File

@ -1,31 +1,12 @@
import {PublicKey } from '@solana/web3.js';
import { Commitment, Connection, PublicKey } from '@solana/web3.js';
import {
GetStructureSchema,
MARKET_STATE_LAYOUT_V3,
} from '@raydium-io/raydium-sdk';
import {
MINIMAL_MARKET_STATE_LAYOUT_V3,
OPENBOOK_PROGRAM_ID,
} from '../liquidity';
import axios from 'axios';
interface AccountData {
data: string[];
executable: boolean;
lamports: number;
owner: string;
rentEpoch: number;
space: number;
}
interface MarketAccount {
account: AccountData;
pubkey: string;
}
interface JsonResponse {
jsonrpc: string;
result: MarketAccount[];
}
export type MinimalOpenBookAccountData = {
id: PublicKey;
@ -36,21 +17,30 @@ export type MinimalMarketLayoutV3 =
GetStructureSchema<MinimalMarketStateLayoutV3>;
export async function getAllMarketsV3(
): Promise<{ id: string; programId: PublicKey }[]> {
const url = 'https://cache.prism.ag/openbook.json';
connection: Connection,
quoteMint: PublicKey,
commitment?: Commitment,
): Promise<MinimalOpenBookAccountData[]> {
const { span } = MARKET_STATE_LAYOUT_V3;
const accounts = await connection.getProgramAccounts(OPENBOOK_PROGRAM_ID, {
dataSlice: { offset: 0, length: 0 },
commitment: commitment,
filters: [
{ dataSize: span },
{
memcmp: {
offset: MARKET_STATE_LAYOUT_V3.offsetOf('quoteMint'),
bytes: quoteMint.toBase58(),
},
},
],
});
try {
const response = await axios.get<JsonResponse>(url);
// @ts-ignore
const json: JsonResponse = response.data;
return json.result
.map(account => ({
id: account.pubkey,
return accounts.map(
(info) =>
<MinimalOpenBookAccountData>{
id: info.pubkey,
programId: OPENBOOK_PROGRAM_ID,
}));
} catch (error) {
console.error('Error during data retrieval:', error);
return [];
}
},
);
}