mirror of
https://github.com/fdundjer/solana-sniper-bot.git
synced 2025-11-12 15:52:37 +10:00
Merge remote-tracking branch 'origin/HEAD'
This commit is contained in:
160
buy.ts
160
buy.ts
@ -3,7 +3,6 @@ import {
|
||||
LIQUIDITY_STATE_LAYOUT_V4,
|
||||
LiquidityPoolKeys,
|
||||
LiquidityStateV4,
|
||||
MARKET_STATE_LAYOUT_V2,
|
||||
MARKET_STATE_LAYOUT_V3,
|
||||
MarketStateV3,
|
||||
Token,
|
||||
@ -25,14 +24,13 @@ import {
|
||||
Commitment,
|
||||
} from '@solana/web3.js';
|
||||
import {
|
||||
getAllAccountsV4,
|
||||
getTokenAccounts,
|
||||
RAYDIUM_LIQUIDITY_PROGRAM_ID_V4,
|
||||
OPENBOOK_PROGRAM_ID,
|
||||
createPoolKeys,
|
||||
} from './liquidity';
|
||||
import { retrieveEnvVariable } from './utils';
|
||||
import { getAllMarketsV3, MinimalMarketLayoutV3 } from './market';
|
||||
import { getMinimalMarketV3, MinimalMarketLayoutV3 } from './market';
|
||||
import pino from 'pino';
|
||||
import bs58 from 'bs58';
|
||||
import * as fs from 'fs';
|
||||
@ -146,75 +144,6 @@ 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()}`,
|
||||
);
|
||||
|
||||
|
||||
let message = {
|
||||
embeds: [
|
||||
{
|
||||
title: `Script will buy all new tokens using ${QUOTE_MINT}. Amount that will be used to buy each token is: ${quoteAmount.toFixed().toString()}`,
|
||||
color: 1127128,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// post it to discord
|
||||
const DISCORD_WEBHOOK = retrieveEnvVariable('DISCORD_WEBHOOK', logger);
|
||||
// use native fetch to post to discord
|
||||
fetch(DISCORD_WEBHOOK, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(message),
|
||||
});
|
||||
|
||||
// get all existing liquidity pools
|
||||
const allLiquidityPools = await getAllAccountsV4(
|
||||
solanaConnection,
|
||||
quoteToken.mint,
|
||||
commitment,
|
||||
);
|
||||
existingLiquidityPools = new Set(
|
||||
allLiquidityPools.map((p) => p.id.toString()),
|
||||
);
|
||||
|
||||
// get all open-book markets
|
||||
const allMarkets = await getAllMarketsV3(
|
||||
solanaConnection,
|
||||
quoteToken.mint,
|
||||
commitment,
|
||||
);
|
||||
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}`,
|
||||
);
|
||||
|
||||
// post to discord webhook
|
||||
message = {
|
||||
embeds: [
|
||||
{
|
||||
title: `Total ${quoteToken.symbol} markets ${existingOpenBookMarkets.size}`,
|
||||
color: 1127128,
|
||||
},
|
||||
{
|
||||
title: `Total ${quoteToken.symbol} pools ${existingLiquidityPools.size}`,
|
||||
color: 14177041,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// post it to discord
|
||||
fetch(DISCORD_WEBHOOK, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(message),
|
||||
});
|
||||
|
||||
// check existing wallet for associated token account of quote mint
|
||||
const tokenAccounts = await getTokenAccounts(
|
||||
solanaConnection,
|
||||
@ -246,33 +175,34 @@ async function init(): Promise<void> {
|
||||
// load tokens to snipe
|
||||
loadSnipeList();
|
||||
}
|
||||
// Auto sell if enabled in .env file
|
||||
const AUTO_SELL = retrieveEnvVariable('AUTO_SELL', logger);
|
||||
export async function processRaydiumPool(updatedAccountInfo: KeyedAccountInfo) {
|
||||
let accountData: LiquidityStateV4 | undefined;
|
||||
try {
|
||||
accountData = LIQUIDITY_STATE_LAYOUT_V4.decode(
|
||||
updatedAccountInfo.accountInfo.data,
|
||||
);
|
||||
|
||||
if (!shouldBuy(accountData.baseMint.toString())) {
|
||||
function saveTokenAccount(mint: PublicKey, accountData: MinimalMarketLayoutV3) {
|
||||
const ata = getAssociatedTokenAddressSync(mint, wallet.publicKey);
|
||||
const tokenAccount = <MinimalTokenAccountData>{
|
||||
address: ata,
|
||||
mint: mint,
|
||||
market: <MinimalMarketLayoutV3>{
|
||||
bids: accountData.bids,
|
||||
asks: accountData.asks,
|
||||
eventQueue: accountData.eventQueue,
|
||||
},
|
||||
};
|
||||
existingTokenAccounts.set(mint.toString(), tokenAccount);
|
||||
return tokenAccount;
|
||||
}
|
||||
|
||||
export async function processRaydiumPool(
|
||||
id: PublicKey,
|
||||
poolState: LiquidityStateV4,
|
||||
) {
|
||||
try {
|
||||
if (!shouldBuy(poolState.baseMint.toString())) {
|
||||
return;
|
||||
}
|
||||
|
||||
await buy(updatedAccountInfo.accountId, accountData);
|
||||
|
||||
|
||||
if (AUTO_SELL === 'true') {
|
||||
const SELL_DELAY = Number(retrieveEnvVariable('SELL_DELAY', logger));
|
||||
await new Promise((resolve) => setTimeout(resolve, SELL_DELAY));
|
||||
await sell(updatedAccountInfo.accountId, accountData);
|
||||
} else {
|
||||
logger.info(
|
||||
`Auto sell is disabled. To enable it, set AUTO_SELL=true in .env file`,
|
||||
);
|
||||
}
|
||||
await buy(id, poolState);
|
||||
} catch (e) {
|
||||
logger.error({ ...accountData, error: e }, `Failed to process pool`);
|
||||
logger.error({ ...poolState, error: e }, `Failed to process pool`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -290,21 +220,7 @@ export async function processOpenBookMarket(
|
||||
return;
|
||||
}
|
||||
|
||||
const ata = getAssociatedTokenAddressSync(
|
||||
accountData.baseMint,
|
||||
wallet.publicKey,
|
||||
);
|
||||
existingTokenAccounts.set(accountData.baseMint.toString(), <
|
||||
MinimalTokenAccountData
|
||||
>{
|
||||
address: ata,
|
||||
mint: accountData.baseMint,
|
||||
market: <MinimalMarketLayoutV3>{
|
||||
bids: accountData.bids,
|
||||
asks: accountData.asks,
|
||||
eventQueue: accountData.eventQueue,
|
||||
},
|
||||
});
|
||||
saveTokenAccount(accountData.baseMint, accountData);
|
||||
} catch (e) {
|
||||
logger.error({ ...accountData, error: e }, `Failed to process market`);
|
||||
}
|
||||
@ -314,12 +230,16 @@ async function buy(
|
||||
accountId: PublicKey,
|
||||
accountData: LiquidityStateV4,
|
||||
): Promise<void> {
|
||||
const tokenAccount = existingTokenAccounts.get(
|
||||
accountData.baseMint.toString(),
|
||||
);
|
||||
let tokenAccount = existingTokenAccounts.get(accountData.baseMint.toString());
|
||||
|
||||
if (!tokenAccount) {
|
||||
return;
|
||||
// it's possible that we didn't have time to fetch open book data
|
||||
const market = await getMinimalMarketV3(
|
||||
solanaConnection,
|
||||
accountData.marketId,
|
||||
commitment,
|
||||
);
|
||||
tokenAccount = saveTokenAccount(accountData.baseMint, market);
|
||||
}
|
||||
|
||||
tokenAccount.poolKeys = createPoolKeys(
|
||||
@ -546,14 +466,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,
|
||||
@ -592,10 +518,10 @@ const runListener = async () => {
|
||||
},
|
||||
commitment,
|
||||
[
|
||||
{ dataSize: MARKET_STATE_LAYOUT_V2.span },
|
||||
{ dataSize: MARKET_STATE_LAYOUT_V3.span },
|
||||
{
|
||||
memcmp: {
|
||||
offset: MARKET_STATE_LAYOUT_V2.offsetOf('quoteMint'),
|
||||
offset: MARKET_STATE_LAYOUT_V3.offsetOf('quoteMint'),
|
||||
bytes: quoteToken.mint.toBase58(),
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user