mirror of
https://github.com/fdundjer/solana-sniper-bot.git
synced 2025-11-13 00:02:36 +10:00
feat: use pool open time for pool detection
This commit is contained in:
52
buy.ts
52
buy.ts
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 [];
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user