前言
提示:本文以Uniswap为案例进行项目分析。旨在于区块链技术分享,相互学习探讨为目的,不作任何投资建议,读者使用本文技术做违法行为与本人无关。
一、Uniswap介绍
1.中心化交易所与去中心化交易所的异同
1.1.中心化交易优缺点
a.优点:
交易速度快
用户使用门槛低,不用自己管理私钥
有otc机制
b.缺点:
可以对价格进行操控
用户的资产被撑控,交易所有可能封停,也有可能跑路
用户信息被暴露
1.2.去中心化交易优缺点
a.优点:
身份信息保密
交易数据公开透明
b.缺点:
门槛高,用户必须先持有一个种币资产
价格高于中心化交易所
2.架构分析
Uniswap是一种自动流动性协议,由恒定乘积公式提供支持,并在以太坊区块链上的不可升级智能合约系统中实施。它消除了对受信任的中介机构的需求,从而优先考虑了权力下放,审查制度抵抗和安全性。 Uniswap是根据GPL许可的开源软件。
每个Uniswap智能合约或一对智能合约都管理由两个ERC-20代币的储备组成的流动资金池。
任何人都可以通过存入等值的基础代币来换取池代币,从而成为池的流动性提供者(LP)。
这些代币跟踪总储备中按比例分配的LP份额,
并可随时赎回相关资产。
货币对充当自动做市商,只要保留“恒定乘积”的公式,就随时准备接受另一种代币。该公式 最简单地表示为x * y = k,它表示交易不得更改货币对的准备金余额(x和y)的乘积(k)。 因为k从交易的参考框架保持不变,所以通常将其称为不变式。此公式具有令人满意的特性, 即相对于较小的交易,较大的交易(相对于储备金)以指数级的差价执行。
实际上,Uniswap对交易收取0.30%的费用,该费用会添加到准备金中。结果,每笔交易实 际上增加了k。这是对LP的支出,这是在LP燃烧其池令牌以提取其总储备中的一部分时实现 的。将来,此费用可能会降低到0.25%,其余的0.05%作为协议范围的费用保留。
由于两对资产的相对价格只能通过交易进行更改,因此Uniswap价格与外部价格之间的差异 会产生套利机会。这种机制确保Uniswap价格始终趋向于结算价。
3.uniswap中的恒定乘积做市
公式: K= XY
同时加一个值或减一个值,来保证最后的 值不变 (X+a)(Y-b) = K
这里 , 则代表一个交易对,如 USDT<=>ETH
最初的价格由最开始添加流动性来决定,但价格如果不合理与外界交易所偏差过大时,会被套 利抹平,最终趋向于合理价。
本节可以参考《手把手教你搭建去中心化交易所》课程- lesson 1-《uiswap介绍》
二、通过sdk使用uniswap各项功能
准备工作
1. 安装好nodejs环境&安装npm
(提示:由于编译的代码所需的版本不同,本文分享的源码Node.js _v>15,用到的npm指令如下:)
同时可以参考往前的文章《npm常用命令详解》
安装相关命令:
npm install <package-name>: 安装指定的包。
npm install <package-name>@<version>: 安装指定版本的包。
npm install: 安装项目package.json文件中列出的所有依赖项。
npm install --global <package-name>: 全局安装指定的包。
更新相关命令
npm update <package-name>: 更新指定的包到最新版本。
npm update: 更新package.json中列出的所有依赖项。
npm outdated: 检查项目中哪些包有新版本可用。
配置相关命令
npm config list: 显示当前npm配置。
npm config set <key> <value>: 设置npm配置参数。
npm config get <key>: 获取指定的npm配置参数值。
npm config delete <key>: 删除指定的npm配置参数。
2. 准备好vscode编辑器
Visual Studio Code(VS Code)是一款免费、开源且跨平台的代码编辑器,支持多种操作系统和设备。对于各设备的下载,您可以参考以下步骤:
- 访问VS Code的官方网站:https://code.visualstudio.com/。
- 在网站首页,您会看到针对不同操作系统的下载选项,包括Windows、macOS和Linux。
请注意,以上步骤是一般性的指导,具体步骤可能因设备和操作系统版本的不同而有所差异。在下载和安装过程中,如果遇到任何问题,您可以参考VS Code官方文档或向社区寻求帮助。此外,为了确保您下载的是最新版本的VS Code,请务必从官方网站进行下载,避免从非官方渠道下载,以免遭遇恶意软件或病毒的风险。
3.熟悉部分区块链Web3调用库的知识
如果有兴趣可以详细阅读一下文档资料,对区块链应用技术非常有用。下面是我梳理了一下大家的求知方向,建议大家可以专研。
1. 获取token信息
2. 获取配对合约
3. 获取对应token价格
4. 生成交易
5. 发送交易
sdk文档 https://uniswap.org/docs/v2
etherjs https://docs.ethers.io/v5/
alchemy https://dashboard.alchemyapi.io
本节可以参考《手把手教你搭建去中心化交易所》课程- lesson 2-《通过sdk使用uniswap各项功能》
三、合约部署
• uniswap-v2-core 核心合约
• uniswap-v2-periphery 周边合约
• uniswap-interface web界面
开发环境
• 增加typescript支持 https://hardhat.org/guides/typescript.html
部署
1.整理合约,将两个合约合并在一起
2.修改代码,部署
2.1. 编译版本号不统一问题
2.2. Route02合约过大的问题
本节可以参考《手把手教你搭建去中心化交易所》课程- lesson 3-《合约部署》
四、兑换流程源码分析
关键API
使用确定数量的TokenA来换取TokenB
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
使用最大量的TokenA来换取确定数量的TokenB
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
还有一些类似的Api,作用都差不太多,可以举一反三 swapExactTokensForTokensSupportingFeeOnTransferTokens 这种是支持通缩Token
...
关键流程
本节可以参考《手把手教你搭建去中心化交易所》课程- lesson 4-《兑换流程源码分析》
五、添加流动性流程分析
关键API
addLiquidity
添加流动性
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint amountA, uint amountB, uint liquidity)
removeLiquidity
移除流动性
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) public virtual override ensure(deadline) returns (uint amountA, uint amountB)
removeLiquidityETHSupportingFeeOnTransferTokens
关键流程
本节可以参考《手把手教你搭建去中心化交易所》课程- lesson 5-《添加流动性流程分析》
六、前端界面部署
准备工作
1. 下载整合好的uniswap合约代码《手把手教你搭建去中心化交易所》课程- 代码包
合约代码在“contracts”文件夹中
2. 下载uniswap前端代码《· uniswap-interface web界面》
编译合约
注意 .env 文件需要自己填写
yarn hardhat run scripts/uniswap.ts --network kovan
记下编译出来router地址
修改web
1. 修改router地址
src/constants/index.ts
ROUTER_ADDRESS
2. 修改配置文件为kovan环境
.env
3. 修改sdk中的factory和initcode
node_modules下面有个@uniswap/sdk/dist/constants.d.ts和sdk.esm.js
4. 启动
yarn start
5. 部署
yarn build
安装yarn可以参考《一文学会yarn安装与配置》
本节可以参考《手把手教你搭建去中心化交易所》课程- lesson 6-《前端界面部署》
七、测试
以上操作如没出问题,直接 npm run 就可以进入测试页面。
八、实操
熟悉以上内容,原理,大家可以用我提供的BSC链的SWAP项目作为练习。
下载链接:《bsc swap simple code》如链接失效可以直接评论区或私信。
1.安装node.js
Node.js 可以在多种操作系统上运行,包括 Windows、Linux 和 macOS。自己根据设备自行下载并运行安装,配置环境变量。(提示下,上面代码使用的是node.js v16)
1.1.安装npm管理器
npm install <package-name>: 安装指定的包
npm install <package-name>@<version>: 安装指定版本的包。
npm install: 安装项目package.json文件中列出的所有依赖项。
npm install --global <package-name>: 全局安装指定的包。
不懂安装的可以看《npm常用命令详解》
1.2.安装yarn
在命令行中运行以下命令:npm install -g yarn
。这个命令会全局安装Yarn,使得您可以在任何地方使用Yarn命令。参考《一文学会yarn安装与配置》
cd <package-name> yarn install
yarn _v1.22.17
1.3.启动项目
$ react-scripts start
如果提示 npx browserslist@latest --update-db 这意味着你当前的 caniuse-lite
数据库版本已经过时了,因此你可能无法获取到最新的浏览器兼容性数据。直接输入:
npx browserslist@latest --update-db
这个命令会利用 npx
运行最新版本的 browserslist
工具,并使用 --update-db
标志来更新 caniuse-lite
数据库。确保你在运行这个命令时有足够的权限(在Unix系统上可能需要使用 sudo
,如果你全局安装了 npx
),并且你的网络连接是稳定的,以便能够成功地从 npm 注册表下载最新的数据。
如无意外,项目会自动打开你电脑默认的浏览器,画面如下,代表你的环境已经没问题了!👍
2.修改前端UI
这部分我就不细讲了,html,CSS,JS...想怎么改就怎么改。不熟悉的可以在CSDN搜索html网页制作的相关教程,以后我也会对该技术内容作分享。
网页UI在以下文件夹
.../public
3.修改/编译/部署合约
编辑器打开.\src\constants\index.ts
复制合约地址:0x10ED43C718714eb63d5aA57B78B54704E256024E
打开BSCSCAN浏览器(以后再分享几篇区块链浏览器的相关知识,现在为了正题凑合一下),搜索刚复制的合约地址:0x10ED43C718714eb63d5aA57B78B54704E256024E 复制合约的开源代码
复制路由合约&工厂合约的开源代码
预防大家“网速不好”我还是把合约代码粘贴在这里吧,代码中已有对应的注析,大家可以参考“章节四”进行参数的修改。
路由合约代码
/**
*Submitted for verification at BscScan.com on 2020-09-03
*/
/**
*Submitted for verification at Bscscan.com on 2020-09-03
*/
pragma solidity ^0.4.18;
contract WBNB {
string public name = "Wrapped BNB";
string public symbol = "WBNB";
uint8 public decimals = 18;
event Approval(address indexed src, address indexed guy, uint wad);
event Transfer(address indexed src, address indexed dst, uint wad);
event Deposit(address indexed dst, uint wad);
event Withdrawal(address indexed src, uint wad);
mapping (address => uint) public balanceOf;
mapping (address => mapping (address => uint)) public allowance;
function() public payable {
deposit();
}
function deposit() public payable {
balanceOf[msg.sender] += msg.value;
Deposit(msg.sender, msg.value);
}
function withdraw(uint wad) public {
require(balanceOf[msg.sender] >= wad);
balanceOf[msg.sender] -= wad;
msg.sender.transfer(wad);
Withdrawal(msg.sender, wad);
}
function totalSupply() public view returns (uint) {
return this.balance;
}
function approve(address guy, uint wad) public returns (bool) {
allowance[msg.sender][guy] = wad;
Approval(msg.sender, guy, wad);
return true;
}
function transfer(address dst, uint wad) public returns (bool) {
return transferFrom(msg.sender, dst, wad);
}
function transferFrom(address src, address dst, uint wad)
public
returns (bool)
{
require(balanceOf[src] >= wad);
if (src != msg.sender &am