当前位置: 首页 > news >正文

Uniswap价格批量查询与ws订阅行情

Uniswap价格批量查询与ws订阅行情

由于 Uniswap V1 版本必须包含 ETH 所以两个 token 之间交换必须先换成 ETH 去中转效率很低已经弃用了

由于 V3 版本 CLMM 和 V4 版本的 DLMM 数学模型过于复杂,还是先从 AMM 模型的 V2 进行入门和学习

Uniswap 三种合约

Uniswap V2 的运转涉及三种智能合约

  • IUniswapV2Router 类似于网关通过输入两个 token 地址从而找到 Pair 合约地址进行交易

  • IUniswapV2Factory 包含所有 Pair 信息 检索交易对、上架交易对

  • IUniswapV2Pair 进行两个 token 之间交易

常用智能合约函数

  • IUniswapV2Router: factory 获取关联的 factor 地址

  • IUniswapV2Factory: allPairsLength 获取交易对(Pair)总数; allPairs(i) 获取第 i 个交易对地址

  • IUniswapV2Pair: getReserves 获取交易对两种 token 数量根据 AMM 算法计算出价格

本文重点聚焦在如何跟 Pair 合约进行交互获取价格行情,对应的合约源码在 https://github.com/Uniswap/v2-core/blob/master/contracts/interfaces/IUniswapV2Pair.sol

初始化 go 查询价格项目


go mod init uniswapgo get github.com/ethereum/go-ethereumgo get github.com/ethereum/go-ethereum/ethclient#go get github.com/ethereum/go-ethereum/rpc

embed 集成 ABI 文件

go embed 类似 Rust 的 include_str!

由于 IUniswapV2Pair.sol 的 ABI json (可在 etherscan 下载) 太长了,写死在代码中不利于代码阅读和逻辑解耦

可用 //go:embed IUniswapV2Pair.abi.json 的方式读取 abi 文件内容集成到可执行文件种

价格换算代码

我们暂时只关心 ETH 跟 USDC 之间的 Pair, getReserve 返回的两个 token 数量,除以各自的 10**decimals 如此就得到真实数量

最后根据 AMM 模型拿 USDC 数量除以 ETH 数量就得到了 ETH 的价格了


type Pair struct {addr       common.Addresstoken0Addr common.Addresstoken1Addr common.AddressdecimalsMul0 *big.Int // e.g. 1e18decimalsMul1 *big.Intreserve      Reserves// e.g. quote_coin/token1 is USDC so price is reserve0/reserve1, Vice versaquoteIsStableCoin bool}func (pair *Pair) amount0() float64 {reserve := new(big.Int).Set(pair.reserve.Reserve0)reserve.Div(reserve, pair.decimalsMul0)amount := new(big.Float).SetInt(reserve)float, _ := amount.Float64()return float}func (pair *Pair) amount1() float64 {reserve := new(big.Int).Set(pair.reserve.Reserve1)reserve.Div(reserve, pair.decimalsMul1)amount := new(big.Float).SetInt(reserve)float, _ := amount.Float64()return float}func (pair *Pair) price() float64 {amount0 := pair.amount0()amount1 := pair.amount1()if pair.quoteIsStableCoin {return amount1 / amount0} else {return amount0 / amount1}}

为什么不用 decimal 类型进行数量除法换算

由于 uint112 位数太多浮点数没法精确表示,为什么不用 例如 rust_decimal, python decimal, big.Float 进行更精确的浮点数相除呢?

原因是性能和准确性二者不可兼得,牺牲一点点误差 trade-off 取舍换得更好性能

我们看以下测试数据 price 用 big.Int 换算 decimals, priceF 用 big.Float 换算 decimals 二者几乎没有误差


price= 3.820039 amount0= 83231.000000 amount1= 21788.000000priceF=3.820073 amountF0=83231.921203 amountF1=21788.047366price= 0.520731 amount0= 1271582.000000 amount1= 2441917.000000priceF=0.520731 amountF0=1271582.547983 amountF1=2441917.863439price= 0.520929 amount0= 2461380.000000 amount1= 1282203.000000priceF=0.520928 amountF0=2461380.624467 amountF1=1282203.123785price= 0.520714 amount0= 2637122.000000 amount1= 1373186.000000priceF=0.520714 amountF0=2637122.261482 amountF1=1373186.008633

整数除法算出的价格和用 big.Float 换算出的价格,误差小于 1e-8 基本可以忽略

rpc 请求价格


func queryReserves(contract *abi.ABI, client *ethclient.Client, pairAddress common.Address) {callData, err := contract.Pack("getReserves")if err != nil {log.Fatalf("Failed to pack call data: %v", err)}msg := ethereum.CallMsg{To:   &pairAddress,Data: callData,}res, err := client.CallContract(context.Background(), msg, nil)if err != nil {log.Fatalf("Failed to call contract: %v", err)}outputs, err := contract.Unpack("getReserves", res)if err != nil {log.Fatalf("Failed to unpack call result: %v", err)}var reserve Reservesmethod.Outputs.Copy(&reserve, values)pair := pairs[pairAddress]// pair.reserve = Reserves{//  Reserve0:           outputs[0].(*big.Int),//  Reserve1:           outputs[1].(*big.Int),//  BlockTimestampLast: outputs[2].(uint32),// }price := pair.price()}

假如有 100 个交易对,就要调用 100 次 queryReserves 请求,公共免费的 rpc 节点通常限制 1s 请求 5 次 怎样批量请求呢?

方案一是调用自己部署的 multicall 智能合约里面批量请求,方案二是使用 rpc.BatchElem 批量请求

批量 rpc 请求价格


func queryReserves(pairAbi *abi.ABI, client *rpc.Client) {method, exists := pairAbi.Methods["getReserves"]if !exists {log.Fatal("pairAbi.Methods")}methodIdSignature := hexutil.Encode(hexutil.Bytes(method.ID))log.Println("method.Sig", method.Sig, "methodIdSignature", methodIdSignature, "method.ID")batch := make([]rpc.BatchElem, len(pairAddresses))for i, addr := range pairAddresses {_ = addrbatch[i] = rpc.BatchElem{Method: "eth_call",Args: []interface{}{map[string]string{"to": addr.Hex(),"data": methodIdSignature,},"latest",},// You are using []byte for the Result, but it’s often safer to use a hexutil.Bytes type or directly handle it as string to avoid encoding issuesResult: new(hexutil.Bytes),// Result: &Reserves{},}}err := client.BatchCall(batch)if err != nil {log.Fatalf("Batch call failed: %v", err)}for i, elem := range batch {pairAddress := pairAddresses[i]if elem.Error != nil {log.Fatalf("Error fetching reserves for pair %s: %v", pairAddress, elem.Error, )continue}reserveData := (*elem.Result.(*hexutil.Bytes))outputs, err := method.Outputs.UnpackValues(reserveData)if err != nil {log.Fatalln(err)}reserve0 := outputs[0].(*big.Int)reserve1 := outputs[1].(*big.Int)blockTimestampLast := outputs[2].(uint32)// ...}}

注意踩坑的点是 rpc.BatchElem.result 不能定义成 []byte 去反序列化,AI 可能会骗你用 []byte ,会报错的

eth json rpc 返回的格式是 "result":"0x0000000" 也就是 go-ethereum/rlp 编码格式所有数据按字段格式编码成十六进制拼接起来

eth 的 hexutil.Bytes类型也是个 []byte 的 newtype 设计模式,但是兼容的

在 sui 的交易数据签名中也有类似 ETH 的 RLP 编码格式

在 eth 的 types.Block 类型中,自行实现了特殊的 json/rlp marshal 处理,所以可以直接直接作为"类型参数"放在 result 中反序列化

ws 订阅 Uniswap 行情

由于免费的 rpc 节点大多不提供 ws 服务,这部分内容就简要概述下

Pair 有六个 Event 其中 Approval 不会发生数量变化就不订阅

eventSignature 的概念就类似于 Topic


func subscribeEvents(contract abi.ABI, wsClient *rpc.Client, pairAddresses []common.Address) {ethClient := ethclient.NewClient(wsClient)query := ethereum.FilterQuery{Addresses: pairAddresses,Topics: [][]common.Hash{{contract.Events["swap"].ID,contract.Events["sync"].ID,contract.Events["burn"].ID,contract.Events["mint"].ID,contract.Events["transfer"].ID,}},}abiCtx := AbiCtx {swap: newEvtCtx(&contract, "swap"),sync: newEvtCtx(&contract, "sync"),burn: newEvtCtx(&contract, "burn"),mint: newEvtCtx(&contract, "mint"),transfer: newEvtCtx(&contract, "transfer"),}logs := make(chan types.Log)sub, err := ethClient.SubscribeFilterLogs(context.Background(), query, logs)if err != nil {log.Fatalf("Failed to subscribe to logs: %v", err)}for {select {case err := <-sub.Err():log.Fatalf("Subscription error: %v", err)case vLog := <-logs:handleLog(&abiCtx, vLog)}}}

以下是 ws log event handler 部分代码


func handleLog(abiCtx *AbiCtx, logEvt types.Log) {pairAddress := logEvt.Addresspair := pairs[pairAddress]switch logEvt.Topics[0] {case abiCtx.sync.id: // EventSignaturevalues, err := abiCtx.sync.arg.UnpackValues(logEvt.Data)if err != nil {log.Fatalf("Failed to unpack Sync event: %v", err)}var reserve Reserveserr = abiCtx.sync.arg.Copy(&reserve, values)if err != nil {log.Fatalln(err)}pair.reserve = reservelog.Printf("ws_event Sync %s price %f\n", pair.name, pair.price())}}

ws 为什么会收到多个 Topic

Received log: {Address:0x2D0Ed226891E256d94F1071E2F94FBcDC9060E14 Topics:[0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822 0x0000000000000000000000005023882f4d1ec10544fcb2066abe9c1645e95aa0 0x0000000000000000000000002c846bcb8aa71a7f90cc5c7731c7a7716a51616e] Data:[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 21 173 145 185 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 37 242 115 147 61 181 112 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] BlockNumber:86354646 TxHash:0x534d7d16b35bf078fb681a54794ed51fafdb88993df76e9c93b9e1b242513540 TxIndex:1 BlockHash:0x0004801c00001dcfd0982594eccebf02fec83d1bd34a5a5f3326f9f7540e3983 Index:3 Removed:false}

其实 Topics[0] 才是事件名字 后面都是事件的参数

原贴地址:Uniswap价格批量查询与ws订阅行情 - 苏慕白的博客


相关文章:

Uniswap价格批量查询与ws订阅行情

Uniswap价格批量查询与ws订阅行情 由于 Uniswap V1 版本必须包含 ETH 所以两个 token 之间交换必须先换成 ETH 去中转效率很低已经弃用了 由于 V3 版本 CLMM 和 V4 版本的 DLMM 数学模型过于复杂&#xff0c;还是先从 AMM 模型的 V2 进行入门和学习 Uniswap 三种合约 Unisw…...

vue 实战 区域内小组件元素拖拽 示例

<template><div><el-button type"primary" click"showDialog true">快捷布局</el-button><el-dialog title"快捷布局配置" :visible.sync"showDialog"><el-row :gutter"20"><el-co…...

C++多线程编程中的锁详解

在现代软件开发中&#xff0c;多线程编程是提升应用程序性能和响应能力的重要手段。然而&#xff0c;多线程编程也带来了数据竞争和死锁等复杂问题。为了确保线程间的同步和共享数据的一致性&#xff0c;C标准库提供了多种锁机制。 1. std::mutex std::mutex是最基础的互斥锁…...

van-dialog 组件调用报错

报错截图 报错原因 这个警告表明 vue 在渲染页面时遇到了一个未知的自定义组件 <van-dialog>&#xff0c;并且提示可能是由于未正确注册该组件导致的。在 vue 中&#xff0c;当我们使用自定义组件时&#xff0c;需要先在 vue 实例中注册这些组件&#xff0c;以便 vue 能…...

【Django】在vscode中运行调试Django项目(命令及图形方式)

文章目录 命令方式图形方式默认8000端口设置自定义端口 命令方式 python manage.py runserver图形方式 默认8000端口 设置自定义端口...

麦田物语第十三天

系列文章目录 麦田物语第十三天 文章目录 系列文章目录一、实现根据物品详情显示 ItemTooltip1.ItemTooltips脚本编写二、制作 Player 的动画一、实现根据物品详情显示 ItemTooltip 1.ItemTooltips脚本编写 首先创建Scripts->Inventory->UI->ItemTooltip脚本,然后…...

【Git多人协作开发】不同的分支下的多人协作开发模式

目录 0.前言背景 1.开发者1☞完成准备工作&协作开发 1.1查看分支情况 1.2创建本地分支feature-1 1.3三板斧 1.4push推本地分支feature-1到远程仓库 2.开发者2☞完成准备工作&协作开发 2.1创建本地分支feature-2 2.2三板斧 2.2push推送本地feature-2到远程仓库…...

Lua 复数计算器

Lua复数计算器 主要包括复数的加减乘除操作&#xff0c;以及打印 编写复数类 -- ***** 元类 ***** Complex {real 0, imag 0}-- 构造函数 function Complex:new(real, imag)local o o or {}o.real real or 0o.imag imag or 0setmetatable(o, self)self.__index selfr…...

深入MySQL中的IF和IFNULL函数

在数据库查询中&#xff0c;我们经常需要根据条件来决定数据的显示方式。MySQL提供了多种内置函数来帮助我们实现这种条件逻辑&#xff0c;其中IF和IFNULL是两个非常有用的函数。在这篇博客中&#xff0c;我们将深入探讨这两个函数的用法和它们在实际查询中的应用。 IF函数 I…...

AI多模态实战教程:面壁智能MiniCPM-V多模态大模型问答交互、llama.cpp模型量化和推理

一、项目简介 MiniCPM-V 系列是专为视觉-语⾔理解设计的多模态⼤型语⾔模型&#xff08;MLLMs&#xff09;&#xff0c;提供⾼质量的⽂本输出&#xff0c;已发布4个版本。 1.1 主要模型及特性 &#xff08;1&#xff09;MiniCPM-Llama3-V 2.5&#xff1a; 参数规模: 8B性能…...

Docker 搭建Elasticsearch详细步骤

本章教程使用Docker搭建Elasticsearch环境。 一、拉取镜像 docker pull docker.elastic.co/elasticsearch/elasticsearch:8.8.2二、运行容器 docker run -d --name elasticsearch -p 9200:9200 -p 9300:9300 -e "discovery.type=single-n...

mysql中提供的函数

文章目录 1.聚合函数2.字符串函数3.数值函数4.日期函数5.流程函数 MySQL 是一个功能强大的关系型数据库管理系统&#xff0c;其中包含了丰富的内置函数&#xff0c;用于处理各种数据操作和查询。这些函数可以分为多种类型&#xff0c;包括字符串函数、数值函数、日期和时间函数…...

加速下载,揭秘Internet Download Manager2024下载器的威力!

1. Internet Download Manager&#xff08;IDM&#xff09;是一款广受欢迎的下载管理软件&#xff0c;以其强大的下载加速功能和用户友好的界面著称。 IDM马丁正版下载如下: https://wm.makeding.com/iclk/?zoneid34275 idm最新绿色版一键安装包链接&#xff1a;抓紧保存以…...

oracle 宽表设计

Oracle宽表设计主要涉及到数据库表或视图中字段&#xff08;列&#xff09;数量较多的情况。在Oracle 23c及以后的版本中&#xff0c;数据库表或视图中允许的最大列数已增加到4096&#xff0c;这为宽表设计提供了更大的灵活性。以下是对Oracle宽表设计的详细分析&#xff1a; …...

winrar安装好后,鼠标右键没有弹出解压的选项

本来安装挺好的&#xff0c;可以正常使用&#xff0c;有天我把winrar相关的文件挪了个位置&#xff0c;就不能正常使用了。 然后我去应用里面找&#xff0c;找到应用标识了&#xff0c;但是找不到对应的文件夹&#xff08;因为我挪到另外一个文件夹里了&#xff09;。 于是我找…...

数字图像处理笔记(一)---- 图像数字化与显示

系列文章目录 数字图像处理学习笔记&#xff08;一&#xff09;---- 图像数字化与显示 数字图像处理笔记&#xff08;二&#xff09;---- 像素加图像统计特征 数字图像处理笔记&#xff08;三) ---- 傅里叶变换的基本原理 文章目录 系列文章目录前言一、数字图像处理二、图像数…...

Unity UGUI 之 事件接口

本文仅作学习笔记与交流&#xff0c;不作任何商业用途 本文包括但不限于unity官方手册&#xff0c;唐老狮&#xff0c;麦扣教程知识&#xff0c;引用会标记&#xff0c;如有不足还请斧正 本文在发布时间选用unity 2022.3.8稳定版本&#xff0c;请注意分别 1.什么是事件接口&…...

Hadoop、HDFS、MapReduce 大数据解决方案

本心、输入输出、结果 文章目录 Hadoop、HDFS、MapReduce 大数据解决方案前言HadoopHadoop 主要组件的Web UI端口和一些基本信息MapReduceMapReduce的核心思想MapReduce的工作流程MapReduce的优缺点Hadoop、HDFS、MapReduce 大数据解决方案 编辑 | 简简单单 Online zuozuo 地址…...

Dubbo SPI 之负载均衡

1. 背景介绍 在分布式系统中&#xff0c;负载均衡是一项核心技术&#xff0c;旨在将请求合理地分配到多个服务实例上&#xff0c;以提高系统的性能和可靠性。Dubbo 作为一个高性能的 Java RPC 框架&#xff0c;提供了多种负载均衡策略来满足不同的业务需求。本文将深入探讨 Du…...

规范:前后端接口规范

1、前言 随着互联网的高速发展&#xff0c;前端页面的展示、交互体验越来越灵活、炫丽&#xff0c;响应体验也要求越来越高&#xff0c;后端服务的高并发、高可用、高性能、高扩展等特性的要求也愈加苛刻&#xff0c;从而导致前后端研发各自专注于自己擅长的领域深耕细作。 然…...

OpenClaw+GLM-4.7-Flash数据助手:Excel报表自动生成与分析

OpenClawGLM-4.7-Flash数据助手&#xff1a;Excel报表自动生成与分析 1. 为什么需要自动化数据助手 作为一位经常与Excel报表打交道的分析师&#xff0c;我每天要花大量时间重复执行数据清洗、格式转换和基础分析。最痛苦的是每月底需要手动合并十几个分公司的销售数据&#…...

【SoC】【ESP32】从零到一:VSCode+ESP-IDF环境下的高效开发工作流构建

1. 为什么选择VSCodeESP-IDF开发ESP32&#xff1f; 第一次接触ESP32开发时&#xff0c;我尝试过各种开发环境&#xff1a;Arduino IDE、PlatformIO、Eclipse...直到遇到VSCodeESP-IDF的组合&#xff0c;才发现这才是嵌入式开发的"完全体"。ESP-IDF作为乐鑫官方的开发…...

Arduino Nano与SSD1306实战:从静态位图到动态动画的完整实现

1. Arduino Nano与SSD1306 OLED屏入门指南 如果你手头正好有一块Arduino Nano开发板和SSD1306驱动的OLED屏幕&#xff0c;想要实现从静态图片显示到动态动画的效果&#xff0c;那这篇文章就是为你准备的。我最近在做一个智能家居项目时&#xff0c;正好用到了这个组合&#xff…...

TSL2561光传感器Arduino库原理与低功耗工程实践

1. TSL2561光强传感器Arduino库深度解析与工程实践1.1 传感器原理与硬件特性TSL2561是由TAOS&#xff08;现为AMS&#xff09;推出的高精度数字环境光传感器&#xff0c;采用CMOS工艺集成双通道光电二极管阵列&#xff0c;分别对可见光&#xff08;VIS&#xff09;和红外光&…...

隐式建模的革新:GemPy如何重新定义三维地质结构可视化

隐式建模的革新&#xff1a;GemPy如何重新定义三维地质结构可视化 【免费下载链接】gempy GemPy is an open-source, Python-based 3-D structural geological modeling software, which allows the implicit (i.e. automatic) creation of complex geological models from int…...

别再只用SVG了!用Vue3 + Konva给你的后台管理系统加个流程图编辑器(附完整代码)

Vue3 Konva实战&#xff1a;打造高交互流程图编辑器的完整方案 在后台管理系统开发中&#xff0c;流程图编辑器是提升业务配置效率的利器。传统SVG方案在复杂交互场景下常遇到性能瓶颈&#xff0c;而基于Canvas的Konva库配合Vue3的响应式特性&#xff0c;能轻松实现流畅的拖拽…...

STM32串口环形队列实现与优化

## 1. STM32串口环形队列实现方案### 1.1 环形队列数据结构设计环形队列&#xff08;Ring Buffer&#xff09;是嵌入式系统中处理串口数据流的经典方案&#xff0c;其核心数据结构定义如下&#xff1a;c #define RING_BUFF_SIZE 256 // 根据实际需求调整缓冲区大小typedef str…...

Windows下Java网络嗅探实战:jNetPcap配置与HTTP报文捕获详解

Windows下Java网络嗅探实战&#xff1a;jNetPcap配置与HTTP报文捕获详解 网络协议分析一直是开发者探索网络通信底层机制的重要途径。对于Java开发者而言&#xff0c;虽然标准库提供了丰富的网络编程接口&#xff0c;但涉及网络层及以下协议的操作却需要借助第三方库。本文将深…...

Windows/Mac/Linux三平台实测:X-AnyLabeling自动标注YOLO数据集避坑指南

Windows/Mac/Linux三平台实测&#xff1a;X-AnyLabeling自动标注YOLO数据集避坑指南 在计算机视觉项目的开发流程中&#xff0c;数据标注往往是耗时最长的环节之一。传统手动标注不仅效率低下&#xff0c;还容易因疲劳导致标注质量下降。X-AnyLabeling作为一款新兴的开源标注工…...

新手必看:用T16IZ遥控器给PX4无人机对频,保姆级图文教程(附接线避坑点)

T16IZ遥控器与PX4无人机对频全指南&#xff1a;从零开始的安全操作手册 刚拿到T16IZ遥控器和PX4无人机套件时&#xff0c;面对密密麻麻的接口和陌生的专业术语&#xff0c;很多新手会感到无从下手。本文将以最直观的方式&#xff0c;带你一步步完成对频操作&#xff0c;同时避开…...