✨重磅!盹猫的个人小站正式上线啦~诚邀各位技术大佬前来探秘!✨
这里有:
- 硬核技术干货:编程技巧、开发经验、踩坑指南,带你解锁技术新姿势!
- 趣味开发日常:代码背后的脑洞故事、工具测评,让技术圈不再枯燥~
- 独家资源分享:开源项目、学习资料包,助你打怪升级快人一步!
👉 点击直达→ 盹猫猫的个人小站 👈
🌟 来逛逛吧,说不定能挖到你正在找的技术宝藏哦~
目录
Welcome to Code Block's blog
本篇文章主要介绍了
[SOL链swap程序]
❤博主广交技术好友,喜欢文章的可以关注一下❤
一、编写目的
本篇文章是为了记录自己通过jupiter swap Api接口实现简单的自动化的swap交换程序的过程,记录相关步骤方便查阅,同时希望可以帮助到有实现相关功能的朋友.
二、jupiter
Jupiter 的核心是DEX 和AMM,为多个已部署的合约程序,提供交换功能.这里使用其提供的API接口直接进行交互.swap接口文档
三、实现功能
程序实现的功能为使用SOL和Token进行交易,通过记录每次交易完成后使用SOL数量(固定买入数量),并通过不断的请求最新价格信息,当发现卖出兑换大于买入(1+利率),执行卖出操作,该程序实现以下功能:
1.当未包含历史数据时,进行第一次买入交易,并记录下买入数量.
2.当包含历史数据时,确认下步操作为买入、卖出操作 ,当为卖出操作时应符合套利条件(1+利率),当为买入操作时应小于历史的买入价格.
四、代码解析
config.ts记录一些配置信息,RPC_URL(区块链节点),DATA_POOL为数据文件路径,这里包含token(记录Token mint地址),process记录过程数据,log记录交易日志,andmin为wallet的公私钥文件,用于签名交易信息.代码如下:
config.ts
import { PublicKey } from "@solana/web3.js"; //-----------------------SOLANA RPC URL---------------------------------- //mainnet RPC_URL export const RPC_URL = "https://api.mainnet-beta.solana.com"; export const DATA_POOL = { token: "./data/swap-token.json", process: "./data/process/", log: "./data/swap-log.json", admin: "./data/swap-wallet.json", };
pool.ts
pool.ts进行文件(DATA_POOL内)的读取、写入更新的操作,包括对token、process、log、wallet的文件操作.以及交易时间段间隔的计算。代码如下:
import { promises as fs } from "fs"; import { DATA_POOL } from "../config/config"; import { join } from "path"; /** * swapData为交易进行时存储的数据 */ type SwapData = { //时间(用于时间检测) time: number; //类型 (交易的类型,ExactIn时已进行买入操作,在条件满足时可进行卖出操作,ExactOut为卖出操作,在条件满足时可进行买入操作) swapMode: String; //交易token数量 (token数据,在买入时记录,在卖出时不变) tokenAmount: number; //交易sol数量(sol数据,在买入时记录,在卖出时不变) solAmount: number; //tokenMint地址(代币mint地址记录) tokenMint: String; //交易Hash txId: String; }; /** * 读取数据 * @param filename file文件 * @returns */ export async function readJsonData(filename: string): Promise<any> { const data = await fs.readFile(filename, "utf-8"); return JSON.parse(data); } /** * 写入数据 * @param filename * @param data */ export async function writeJsonData( filename: string, data: any ): Promise<void> { await fs.writeFile(filename, JSON.stringify(data, null, 2)); } /** * 更新数据 * @param filename 文件名 * @param key * @param newValue */ export async function updateJsonData( filename: string, key: string, newValue: any ): Promise<void> { const data = await readJsonData(filename); data[key] = newValue; await writeJsonData(filename, data); } /** * 添加交换token * @param token_program * @returns */ export async function addSwapToken(tokenMint: string): Promise<boolean> { console.log(tokenMint); let swap_list: string[] = await readJsonData(DATA_POOL.token); if (!swap_list.includes(tokenMint)) { swap_list.push(tokenMint); } console.log(swap_list); await writeJsonData(DATA_POOL.token, swap_list); return true; } /** * 获取交易列表 * @returns */ export async function getSwapTokenList() { let swap_list = await readJsonData(DATA_POOL.token); return swap_list; } /** * 是否存在某个文件 * @param path string * @returns */ export async function hasFile(path: string): Promise<boolean> { try { await fs.access(path); return true; } catch { return false; } } /** * 获取交易的账户(Admin)账户 * @returns */ export async function getAdminAccount(): Promise<[string, string]> { const data = await readJsonData(DATA_POOL.admin); return [data.publicKey, data.privateKey]; } /** * 添加swapData,在买入时添加 * @param swapData */ export async function addSwapData(swapData: SwapData) { await writeJsonData( join(DATA_POOL.process, `${swapData.tokenMint}.json`), swapData ); return true; } /** * * @param tokenMint token地址 * @param tagDay 目标天数 * @returns 是否超过天数 */ export async function checkSwapDataInFewDays( tokenMint: String, tagDay: number ): Promise<boolean> { const swapData = await readJsonData( join(DATA_POOL.process, `${tokenMint}.json`) ); const now = Date.now(); const swapTime = swapData["time"]; const msInOneDay = 24 * 60 * 60 * 1000; // 一天的毫秒数 const diffInMs = Math.abs(now - swapTime); // 时间差的绝对值 console.log("已监测天数:", Math.floor(diffInMs / msInOneDay)); return Math.floor(diffInMs / msInOneDay) > tagDay; } /** * 在第一次交易时检查是否包含已记录数据 * @param tokenMint * @returns */ export async function checkHasSwapData(tokenMint: String) { return hasFile(join(DATA_POOL.process, `${tokenMint}.json`)); } /** * 更新交易类型和交易时间用于执行相反的交易 * @param tokenMint * @param swapMode * @param time */ export async function updateSwapDataModeAndDate( tokenMint: String, swapMode: String, time: number ) { const swapData = await readJsonData( join(DATA_POOL.process, `${tokenMint}.json`) ); swapData["swapMode"] = swapMode; swapData["time"] = time; await writeJsonData(join(DATA_POOL.process, `${tokenMint}.json`), swapData); } /** * 获取交易的token数量和交易类型 * @param tokenMint * @returns */ export async function getSwapTokenAmount(tokenMint: String) { const swapData = await readJsonData( join(DATA_POOL.process, `${tokenMint}.json`) ); return [swapData["tokenAmount"], swapData["swapMode"]]; } /** * 获取交易的sol数量和交易类型 * @param tokenMint tokenMint * @returns */ export async function getSwapSolAmount(tokenMint: String) { const swapData = await readJsonData( join(DATA_POOL.process, `${tokenMint}.json`) ); return [swapData["solAmount"], swapData["swapMode"]]; }
swap.ts
swap.ts实现主要的逻辑操作,在swapToken方法内会不断的根据预设定的条件:[**swapAmount 0.001**:表示每次交易0.001
**tagPrice 0.1**:为目标价格,0.1表示高于买入的价格10%时卖出.
**tagDay 3**:为交易间隔天数为3.]
不断通过**quoteResponse**获取最新的交易价格并进行判断,当满足条件时获取transaction并签名然后发送到链上.
具体代码如下:
import { Connection, Keypair, VersionedTransaction } from "@solana/web3.js"; import { addSwapData, checkHasSwapData, checkSwapDataInFewDays, getAdminAccount, getSwapSolAmount, getSwapTokenAmount, getSwapTokenList, updateSwapDataModeAndDate, } from "../pool/pool"; import { RPC_URL } from "../config/config"; import * as bs58 from "bs58"; let isRunning = false; // 控制变量 const connection = new Connection(RPC_URL, "confirmed"); /** * * @param variables */ export function startSwapBot(variables: Record<string, number>) { let { swapAmount, tagPrice, tagDay } = variables; if (!swapAmount) { swapAmount = 0.001; } if (!tagPrice) { tagPrice = 0.1; } if (!tagDay) { tagDay = 3; } console.log("swapAmount:", swapAmount); console.log("tagPrice:", tagPrice); console.log("tagDay", tagDay); startLoop(swapAmount, tagPrice, tagDay); } /** * * @param swapAmount 交易的sol数量 * @param tagPrice 目标价格 * @param tagDay 目标天数 */ // 启动循环的函数 function startLoop(swapAmount: number, tagPrice: number, tagDay: number) { isRunning = true; const loop = async () => { const [publicKey, privateKey] = await getAdminAccount(); const wallet = Keypair.fromSecretKey(bs58.default.decode(privateKey)); while (isRunning) { const tokenList = await getSwapTokenList(); for (const tokenMint of tokenList) { await swapToken(tokenMint, swapAmount, tagPrice, tagDay, wallet); await new Promise((resolve) => setTimeout(resolve, 10000)); // 等待2秒 } } console.log("Loop has stopped."); }; loop(); } /** * * @param tokenMint token的mint(铸造)地址 * @param swapAmount 交易sol数量 eg:0.01 则持续的以0.01SOL为基准进行交易 * @param tagPrice 目标价格 * @param tagDay 目标天数 * @param wallet 签名钱包 */ export const swapToken = async ( tokenMint: String, swapAmount: number, tagPrice: number, tagDay: number, wallet: Keypair ): Promise<any> => { //获取交换tokenMint地址 const solMint = "So11111111111111111111111111111111111111112"; swapAmount = swapAmount * 10e8; const slippage = 50; const quoteResponse = await ( await fetch( `https://quote-api.jup.ag/v6/quote?inputMint=${solMint}&outputMint=${tokenMint}&amount=${swapAmount}&slippageBps=${slippage}` ) ).json(); const outAmount = quoteResponse["outAmount"]; const swapMode = quoteResponse["swapMode"]; const solAmount = quoteResponse["inAmount"]; if (await checkNowCanBuy(tokenMint, outAmount, tagDay)) { const txId = await jupiterSwapToken(quoteResponse, wallet); if (txId != "error") { console.log("交易已完成-txId:", txId); await addSwapData({ time: Date.now(), swapMode: swapMode, tokenAmount: outAmount, solAmount: solAmount, tokenMint: tokenMint, txId: txId, }); console.log("swapData已记录:", txId); } } else { const [tokenAmount, swapMode] = await getSwapTokenAmount(tokenMint); const quoteResponse = await ( await fetch( `https://quote-api.jup.ag/v6/quote?inputMint=${tokenMint}&outputMint=${solMint}&amount=${tokenAmount}&slippageBps=${slippage}` ) ).json(); const outSolAmount = quoteResponse["outAmount"]; if (await checkNowCanSell(tokenMint, outSolAmount, tagPrice)) { const txId = await jupiterSwapToken(quoteResponse, wallet); if (txId != "error") { console.log("交易已完成-txId:", txId); await updateSwapDataModeAndDate(tokenMint, "ExactOut", Date.now()); console.log("swapData已更新:", txId); } } } }; /** * * @param tokenMint token的mint地址 * @param outTokenAmount 获取的tokenAmount * @param tagDay 目标天数 eg: 1 在间隔1天后且满足小于记录的值时执行买入操作 * @returns bool 是否可以买入 */ export const checkNowCanBuy = async ( tokenMint: String, outTokenAmount: number, tagDay: number ): Promise<boolean> => { //未包含历史数据时 if (!(await checkHasSwapData(tokenMint))) { console.log("buy:未包含历史价格数据!"); return true; } else { //小于历史数据买入值 const [tokenAmount, swapMode] = await getSwapTokenAmount(tokenMint); if (swapMode != "ExactIn" && outTokenAmount > tokenAmount) { console.log("buy:小于历史数据买入值!"); return true; } //连续n天内未跌破相关价格 if ( swapMode != "ExactIn" && (await checkSwapDataInFewDays(tokenMint, tagDay)) && tokenAmount < outTokenAmount ) { console.log("buy:连续几天内未跌破相关价格!"); return true; } return false; } }; /** * * @param tokenMint token的mint地址 * @param outSolAmount 响应的outSolAmount * @param tagPrice 目标价格比例 eg:0.1表示比买入价格高出10%时执行卖出操作 * @returns bool 表示是否可以卖出 */ export const checkNowCanSell = async ( tokenMint: String, outSolAmount: number, tagPrice: number ): Promise<boolean> => { //大于历史买入数据(1+利率变量)*tokenAmount if (!(await checkHasSwapData(tokenMint))) { console.log("未记录历史买入数据!"); return false; } const [solAmount, swapMode] = await getSwapSolAmount(tokenMint); console.log("tokenMint", tokenMint); console.log("outSolAmount", outSolAmount); console.log("tagAmount", solAmount * (1 + tagPrice)); if (swapMode == "ExactIn" && outSolAmount > solAmount * (1 + tagPrice)) { console.log("大于历史买入数据(1+利率变量)*tokenAmount"); return true; } console.log("不满足限定卖出条件,等待中....."); return false; }; /** * * @param transaction 已签名的transaction * @returns txId 交易ID */ export async function sendVerTransaction( transaction: VersionedTransaction ): Promise<String> { /** * 获取最新的区块hash */ const latestBlockHash = await connection.getLatestBlockhash(); /** * transaction的序列化 */ const rawTransaction = transaction.serialize(); /** * 发送RawTransaction到链上 */ const txid = await connection.sendRawTransaction(rawTransaction, { skipPreflight: true, maxRetries: 2, preflightCommitment: "processed", }); /** * 通过txId去监听并确认该笔交易的状态 */ await connection.confirmTransaction({ blockhash: latestBlockHash.blockhash, lastValidBlockHeight: latestBlockHash.lastValidBlockHeight, signature: txid, }); return txid; } /** * * @param quoteResponse 路由报价响应 * @param wallet 签名钱包 * @returns swapTransaction 要发送到链上的trasaction */ async function jupiterSwapToken( quoteResponse: any, wallet: Keypair ): Promise<any> { const { swapTransaction } = await ( await fetch("https://quote-api.jup.ag/v6/swap", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ // quoteResponse from /quote api quoteResponse, // user public key to be used for the swap userPublicKey: wallet.publicKey.toBase58(), // auto wrap and unwrap SOL. default is true wrapAndUnwrapSol: true, // feeAccount is optional. Use if you want to charge a fee. feeBps must have been passed in /quote API. // feeAccount: "fee_account_public_key" dynamicComputeUnitLimit: true, // allow dynamic compute limit instead of max 1,400,000 // custom priority fee prioritizationFeeLamports: { autoMultiplier: 1, }, // or custom lamports: 1000 }), }) ).json(); const swapTransactionBuf = Buffer.from(swapTransaction, "base64"); var transaction = VersionedTransaction.deserialize(swapTransactionBuf); transaction.sign([wallet]); try { const txId = await sendVerTransaction(transaction); return txId; } catch { return "error"; } }
注:这里的下述代码提取到一个获取方法后,代码运行发生报错,所以这边直接写在代码里.
const quoteResponse = await ( await fetch( `https://quote-api.jup.ag/v6/quote?inputMint=${tokenMint}&outputMint=${solMint}&amount=${tokenAmount}&slippageBps=${slippage}` ) ).json();
在实验环境下进行,不涉及任何投资方面的建议~
感谢您的关注和收藏!!!!!!
编辑