目录
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();
在实验环境下进行,不涉及任何投资方面的建议~
感谢您的关注和收藏!!!!!!