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,
|
Commitment,
|
||||||
} from '@solana/web3.js';
|
} from '@solana/web3.js';
|
||||||
import {
|
import {
|
||||||
getAllAccountsV4,
|
|
||||||
getTokenAccounts,
|
getTokenAccounts,
|
||||||
RAYDIUM_LIQUIDITY_PROGRAM_ID_V4,
|
RAYDIUM_LIQUIDITY_PROGRAM_ID_V4,
|
||||||
OPENBOOK_PROGRAM_ID,
|
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()}`,
|
`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
|
logger.info(`Loading existing markets...`);
|
||||||
const allLiquidityPools = await getAllAccountsV4(
|
|
||||||
quoteToken.mint,
|
|
||||||
);
|
|
||||||
existingLiquidityPools = new Set(
|
|
||||||
allLiquidityPools.map((p) => p.id.toString()),
|
|
||||||
);
|
|
||||||
|
|
||||||
// get all open-book markets
|
// get all open-book markets
|
||||||
const allMarkets = await getAllMarketsV3();
|
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(
|
|
||||||
solanaConnection,
|
solanaConnection,
|
||||||
wallet.publicKey,
|
quoteToken.mint,
|
||||||
commitment,
|
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
|
// check existing wallet for associated token account of quote mint
|
||||||
const tokenAccounts = await getTokenAccounts(
|
const tokenAccounts = await getTokenAccounts(
|
||||||
solanaConnection,
|
solanaConnection,
|
||||||
@ -202,18 +187,17 @@ async function init(): Promise<void> {
|
|||||||
loadSnipeList();
|
loadSnipeList();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function processRaydiumPool(updatedAccountInfo: KeyedAccountInfo) {
|
export async function processRaydiumPool(
|
||||||
|
id: PublicKey,
|
||||||
|
poolState: LiquidityStateV4,
|
||||||
|
) {
|
||||||
let accountData: LiquidityStateV4 | undefined;
|
let accountData: LiquidityStateV4 | undefined;
|
||||||
try {
|
try {
|
||||||
accountData = LIQUIDITY_STATE_LAYOUT_V4.decode(
|
if (!shouldBuy(poolState.baseMint.toString())) {
|
||||||
updatedAccountInfo.accountInfo.data,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!shouldBuy(accountData.baseMint.toString())) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await buy(updatedAccountInfo.accountId, accountData);
|
await buy(id, poolState);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error({ ...accountData, error: e }, `Failed to process pool`);
|
logger.error({ ...accountData, error: e }, `Failed to process pool`);
|
||||||
}
|
}
|
||||||
@ -343,14 +327,20 @@ function shouldBuy(key: string): boolean {
|
|||||||
|
|
||||||
const runListener = async () => {
|
const runListener = async () => {
|
||||||
await init();
|
await init();
|
||||||
|
const runTimestamp = Math.floor(new Date().getTime() / 1000);
|
||||||
const raydiumSubscriptionId = solanaConnection.onProgramAccountChange(
|
const raydiumSubscriptionId = solanaConnection.onProgramAccountChange(
|
||||||
RAYDIUM_LIQUIDITY_PROGRAM_ID_V4,
|
RAYDIUM_LIQUIDITY_PROGRAM_ID_V4,
|
||||||
async (updatedAccountInfo) => {
|
async (updatedAccountInfo) => {
|
||||||
const key = updatedAccountInfo.accountId.toString();
|
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);
|
const existing = existingLiquidityPools.has(key);
|
||||||
if (!existing) {
|
|
||||||
|
if (poolOpenTime > runTimestamp && !existing) {
|
||||||
existingLiquidityPools.add(key);
|
existingLiquidityPools.add(key);
|
||||||
const _ = processRaydiumPool(updatedAccountInfo);
|
const _ = processRaydiumPool(updatedAccountInfo.accountId, poolState);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
commitment,
|
commitment,
|
||||||
|
|||||||
@ -12,26 +12,6 @@ import {
|
|||||||
} from '@raydium-io/raydium-sdk';
|
} from '@raydium-io/raydium-sdk';
|
||||||
import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
|
import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
|
||||||
import { MinimalMarketLayoutV3 } from '../market';
|
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 RAYDIUM_LIQUIDITY_PROGRAM_ID_V4 = MAINNET_PROGRAM_ID.AmmV4;
|
||||||
export const OPENBOOK_PROGRAM_ID = MAINNET_PROGRAM_ID.OPENBOOK_MARKET;
|
export const OPENBOOK_PROGRAM_ID = MAINNET_PROGRAM_ID.OPENBOOK_MARKET;
|
||||||
@ -42,33 +22,6 @@ export const MINIMAL_MARKET_STATE_LAYOUT_V3 = struct([
|
|||||||
publicKey('asks'),
|
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(
|
export function createPoolKeys(
|
||||||
id: PublicKey,
|
id: PublicKey,
|
||||||
accountData: LiquidityStateV4,
|
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(
|
export async function getTokenAccounts(
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
owner: PublicKey,
|
owner: PublicKey,
|
||||||
|
|||||||
@ -1,31 +1,12 @@
|
|||||||
import {PublicKey } from '@solana/web3.js';
|
import { Commitment, Connection, PublicKey } from '@solana/web3.js';
|
||||||
import {
|
import {
|
||||||
GetStructureSchema,
|
GetStructureSchema,
|
||||||
|
MARKET_STATE_LAYOUT_V3,
|
||||||
} from '@raydium-io/raydium-sdk';
|
} from '@raydium-io/raydium-sdk';
|
||||||
import {
|
import {
|
||||||
MINIMAL_MARKET_STATE_LAYOUT_V3,
|
MINIMAL_MARKET_STATE_LAYOUT_V3,
|
||||||
OPENBOOK_PROGRAM_ID,
|
OPENBOOK_PROGRAM_ID,
|
||||||
} from '../liquidity';
|
} 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 = {
|
export type MinimalOpenBookAccountData = {
|
||||||
id: PublicKey;
|
id: PublicKey;
|
||||||
@ -36,21 +17,30 @@ export type MinimalMarketLayoutV3 =
|
|||||||
GetStructureSchema<MinimalMarketStateLayoutV3>;
|
GetStructureSchema<MinimalMarketStateLayoutV3>;
|
||||||
|
|
||||||
export async function getAllMarketsV3(
|
export async function getAllMarketsV3(
|
||||||
): Promise<{ id: string; programId: PublicKey }[]> {
|
connection: Connection,
|
||||||
const url = 'https://cache.prism.ag/openbook.json';
|
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 {
|
return accounts.map(
|
||||||
const response = await axios.get<JsonResponse>(url);
|
(info) =>
|
||||||
// @ts-ignore
|
<MinimalOpenBookAccountData>{
|
||||||
const json: JsonResponse = response.data;
|
id: info.pubkey,
|
||||||
|
|
||||||
return json.result
|
|
||||||
.map(account => ({
|
|
||||||
id: account.pubkey,
|
|
||||||
programId: OPENBOOK_PROGRAM_ID,
|
programId: OPENBOOK_PROGRAM_ID,
|
||||||
}));
|
},
|
||||||
} catch (error) {
|
);
|
||||||
console.error('Error during data retrieval:', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user