首页 > 编程语言 >小试牛刀-SOL链swap程序

小试牛刀-SOL链swap程序

时间:2024-12-14 10:00:08浏览次数:5  
标签:const log tokenMint await SOL param swap return 小试牛刀

目录

一、编写目的

二、jupiter

三、实现功能

四、代码解析


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();

在实验环境下进行,不涉及任何投资方面的建议~

感谢您的关注和收藏!!!!!! 


 

标签:const,log,tokenMint,await,SOL,param,swap,return,小试牛刀
From: https://blog.csdn.net/2202_75618418/article/details/144418790

相关文章

  • 鸿蒙开发中console.log和hilog的区别
    在日常开发中打印日志是调试程序非常常用的操作,在鸿蒙的官方文档中介绍了hilog这种方式,有些前端转过来的友友发现console.log也可以进行日志打印。有一段时候幽蓝君也非常喜欢使用console.log,因为它看起来好像更加简单方便。那么今天幽蓝君就来和大家说一说console.log和hilog有......
  • 手动创建swap扩大内存
    1.创建swap文件夹#在根目录创建/swap文件夹并切换目录到/swapmkdir/swap&&cd/swap2.创建要作为swap分区的文件:增加1GB大小的交换分区,则命令写法如下,其中的count等于想要的块的数量(bs*count=文件大小)。一般swap的大小为物理内存的1.5-2倍。内存<=4g:Swap至少4G......
  • C#,.net 8 console call MessageBox of System.Windows.Forms, and via MessageBox of
    usingSystem.Runtime.InteropServices;namespaceConsoleApp6{internalclassProgram{//copyfrom,https://gist.github.com/6rube/34b561827f0805f73742541b8b8bb770[DllImport("user32.dll",CharSet=CharSet.Unicode)]......
  • msfconsole攻击指令
    Post后渗透模块sysinfo#查看目标主机系统信息runscraper#查看目标主机详细信息runhashdump#导出密码的哈希loadkiwi#加载ps#查看目标主机进程信息pwd#查看目标当前目录(windows)getlwd......
  • SOLIDWORKS 二次开发:提升设计效率与创新能力的利器
    在当今竞争激烈的制造业环境中,产品设计的效率和质量对于企业的成功至关重要。SOLIDWORKS作为一款广泛应用的三维机械设计软件,其强大的功能为工程师们提供了丰富的设计工具。然而,随着设计需求的日益复杂和多样化,单纯依靠SOLIDWORKS软件本身的标准功能有时难以满足特定的设计任务......
  • js 中的console使用详解
    console是JavaScript提供的一个全局对象,常用于调试和日志记录。它包含一组方法,用于在控制台中打印消息、显示数据以及调试程序。以下是console常见方法的详细介绍和用法。1.常用方法1.1console.log()作用:打印普通消息,最常用的日志方法。用法:console.log("Hello,wo......
  • SOLIDWORKS二次开发参数化
    如今企业开发新产品时,零件模型的建立及出图的速度是决定整个产品开发效率的关键。在企业的产品的开发到一定时期,很多的设计经过实际验证分析后,一些产品的大致持征已经确定,这时企业就希望能将该类产品系列化、参数化及标准化。于是,将模型设计中定量化的参数变量化就成了一个有效的......
  • 如何使用SOLIDWORKS绘制圣诞树讲座预约
     最新一期的线上讲座预约开始啦,本期内容将为大家演示使用SOLIDWORKS绘制圣诞树的操作流程,扫描页尾二维码即可预约,免费观看,欢迎参与。讲座方式:视频号直播报名方式:扫描页尾二维码预约讲座时间:12月20日14:00有奖活动圣诞节将至,为此SoldKits推出一期有奖互动小活动,12月13日......
  • OpenAPI 与 国产 Solon 框架支持,Fast Request 2024.1.9 发布
    FastRequest是一个类似于Postman的IDEA插件。它是一个强大的restfulapi工具包插件,可以根据已有的方法帮助您快速、自动生成url和params。RestfulFastRequest=API调试工具+API管理工具+API搜索工具。它有一个漂亮的界面来完成请求、检查服务器响应、存储你......
  • vue3中vconsole使用方法
    1.安装npmivconsole-D 2.在Vue项目的入口文件(通常是main.js或main.ts)中导入VConsole请注意,在生产环境下,你应该避免将VConsole包含在你的项目中。你可以使用条件语句来仅在开发环境下引入VConsole//处理是开启H5调试importVConsolefrom"vconsole";//在dev环境使......