UniswapV2交易DAPP开发-基本实现
上两篇文章对Uniswap、开发环境及需要的基础数据进行了详细的说明,本篇将对用到的实体类、Nethereum相关的类、及对Uniswap具体的调用进行详细的讲解。具体包括通过Nethereum和区块链节点建立连接、根据任意币对创建流动池子、给池子添加流动性、撤出流动性、获得LP地址、获得池子深度计算Token代币价格以及进行Token兑换交易等。
# 程序结构
项目结构由Model、Resources、Utils等目录组成,Model主要保存节点、合约、账户、Swap交易信息及Web3操作等实体类、扩展类。路由、工厂、LP等合约的ABI信息已文件的形式保存在Resources目录下。Utils目录下,ABIFunction.cs为ABI操作函数定义的类型及File的扩展操作。
工程目录结构
# 实体类
RPCModel
链、节点等相关信息保存在RPCModel类中,主要包括节点URL、网络ID以及链的GasPrice和操作的GasLimit。
ContractModel
ContractModel合约实体类主要包括合约地址、合约的ABI以及添加池子、撤池子、交易兑换的数量字段(AmountLiquidity)。
AccountModel
AccountModel账户类,主要用到了地址、私钥等属性。
SwapModel
SwapModel为代币交易兑换时用到的类,主要包括滑点设置和Token兑换路由数组。
Web3Ext为对Nethereum的扩展和封装,主要包括流动池的创建、添加流动池、撤出流动性、获取LP地址、获取流动池子的深度信息、对路由合约授权操作以及Swap交易兑换等。
# 功能实现
程序启动就创建了节点、路由合约、Factory合约、LP合约、账户等实例,并加载了合约对应的ABI和地址,账户对应的地址和私钥。
功能流程图
# 创建池子
选择任意的Erc20币对,通过Factory工厂合约提供的“createPair"接口进行流动池子的创建。
public static ResultModel createPair(RPCModel rPCModel, AccountModel accountModel, ContractModel factoryContract, ContractModel contractA, ContractModel contractB)
{
ResultModel resultModel=new ResultModel();
try
{
Account account = new Account(accountModel.PrivateKey, rPCModel.NetworkId);
Web3 web3 = new Web3(account, rPCModel.URL);
Contract myContract = web3.Eth.GetContract(factoryContract.ABI, factoryContract.Address);
BigInteger gPrice = Web3.Convert.ToWeiFromUnit(rPCModel.GasPrice, 1000000000);
BigInteger gasLimit = new BigInteger(rPCModel.GasLimit);
TransactionInput transactionInput = new TransactionInput
{
Data = "",
Gas = new HexBigInteger(gasLimit),
From = account.Address,
Value = new HexBigInteger(0),
GasPrice = new HexBigInteger(gPrice)
};
var swapResult = myContract.GetFunction("createPair").SendTransactionAsync(transactionInput, contractA.Address, contractB.Address);
swapResult.Wait();
int count = 0;
resultModel.TransHash = swapResult.Result;
while (true)
{
var receipt = web3.Eth.Transactions.GetTransactionReceipt.SendRequestAsync(swapResult.Result);
receipt.Wait();
if (receipt.Result != null)
{
resultModel.State = (int)receipt.Result.Status.ToUlong();
break;
}
if (count >= 20)
{
resultModel.State = 2;
break;
}
Thread.Sleep(2000);
count++;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
if (ex.InnerException != null)
{
Console.WriteLine(ex.InnerException.ToString());
}
resultModel.Message = ex.Message;
}
return resultModel;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 添加流动性
添加流动是调用Uniswap的路由合约中的addLiquidity进行操作。其中需要注意的参数:amountADesired、amountBDesired用户期望添加的数量,amountAMin、amountBMin为了避免价格波动带来损失,最小允许添加的数量。
public static ResultModel addLiquidity(RPCModel rPCModel, AccountModel accountModel, ContractModel routerContract, ContractModel contractA, ContractModel contractB)
{
ResultModel resultModel = new ResultModel();
try
{
Account account = new Account(accountModel.PrivateKey, rPCModel.NetworkId);
Web3 web3 = new Web3(account, rPCModel.URL);
Contract myContract = web3.Eth.GetContract(routerContract.ABI, routerContract.Address);
BigInteger gPrice = Web3.Convert.ToWeiFromUnit(rPCModel.GasPrice, 1000000000);
BigInteger gasLimit = new BigInteger(rPCModel.GasLimit);
BigInteger amountADesired = Web3.Convert.ToWei(contractA.AmountLiquidity, contractA.UnitDecimal);
BigInteger amountBDesired = Web3.Convert.ToWei(contractB.AmountLiquidity, contractB.UnitDecimal);
TransactionInput transactionInput = new TransactionInput
{
Data = "",
Gas = new HexBigInteger(gasLimit),
From = account.Address,
Value = new HexBigInteger(0),
GasPrice = new HexBigInteger(gPrice)
};
var swapResult = myContract.GetFunction("addLiquidity").SendTransactionAsync(transactionInput, contractA.Address, contractB.Address, amountADesired, amountBDesired, 0, 0, account.Address,GetSeconds(DateTime.Now));
swapResult.Wait();
int count = 0;
resultModel.TransHash = swapResult.Result;
while (true)
{
var receipt = web3.Eth.Transactions.GetTransactionReceipt.SendRequestAsync(swapResult.Result);
receipt.Wait();
if (receipt.Result != null)
{
resultModel.State = (int)receipt.Result.Status.ToUlong();
break;
}
if (count >= 20)
{
resultModel.State = 2;
break;
}
Thread.Sleep(2000);
count++;
}
}
catch (Exception ex)
{
resultModel.State = -1;
resultModel.Message = ex.Message;
if (ex.InnerException != null)
{
resultModel.Message=ex.InnerException.Message;
}
}
return resultModel;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 移除流动性
移除流动是调用Uniswap的路由合约中的removeLiquidity进行操作。其中liquidity为LP的数量,amountAMin、amountBMin为了避免价格波动带来损失,最小允许获得的代币数量。
public static ResultModel removeLiquidity(RPCModel rPCModel, AccountModel accountEntity, ContractModel routerContract, ContractModel contractLP, ContractModel contractA, ContractModel contractB)
{
ResultModel resultModel = new ResultModel();
try
{
Account account = new Account(accountEntity.PrivateKey, rPCModel.NetworkId);
Web3 web3 = new Web3(account, rPCModel.URL);
Contract myContract = web3.Eth.GetContract(routerContract.ABI, routerContract.Address);
BigInteger gPrice = Web3.Convert.ToWeiFromUnit(rPCModel.GasPrice, 1000000000);
BigInteger gasLimit = new BigInteger(rPCModel.GasLimit);
BigInteger liquidity = Web3.Convert.ToWei(contractLP.AmountLiquidity, contractLP.UnitDecimal);
TransactionInput transactionInput = new TransactionInput
{
Data = "",
Gas = new HexBigInteger(gasLimit),
From = account.Address,
Value = new HexBigInteger(0),
GasPrice = new HexBigInteger(gPrice)
};
var swapResult = myContract.GetFunction("removeLiquidity").SendTransactionAsync(transactionInput, contractA.Address, contractB.Address, liquidity, 0, 0, account.Address,GetSeconds(DateTime.Now));
swapResult.Wait();
int count = 0;
resultModel.TransHash = swapResult.Result;
resultModel.Message = "LP撤出成功!";
while (true)
{
var receipt = web3.Eth.Transactions.GetTransactionReceipt.SendRequestAsync(swapResult.Result);
receipt.Wait();
if (receipt.Result != null)
{
resultModel.State = (int)receipt.Result.Status.ToUlong();
break;
}
if (count >= 20)
{
resultModel.State = 2;
break;
}
Thread.Sleep(2000);
count++;
}
}
catch (Exception ex)
{
resultModel.State = -1;
resultModel.Message=ex.Message;
if (ex.InnerException != null)
{
resultModel.Message=ex.Message+ex.InnerException.ToString();
}
}
return resultModel;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 获取池子深度信息
通过获得的深度信息可以计算代币的价格,getReserves是LP合约提供的方法,该方法无参数。
public static ResultModel getLPReserves(RPCModel rPCModel, ContractModel lPContract, ContractModel contractA, ContractModel contractB)
{
ResultModel resultModel = new ResultModel();
try
{
Web3 web3 = new Web3(rPCModel.URL);
Contract myContract = web3.Eth.GetContract(lPContract.ABI, lPContract.Address);
var reservesResult = myContract.GetFunction("getReserves").CallDecodingToDefaultAsync();// .CallAsync<BigInteger>();
reservesResult.Wait();
if ((BigInteger)reservesResult.Result[0].Result > 0)
{
decimal reserves0;// = Web3.Convert.FromWei((BigInteger)reservesResult.Result[0].Result);
decimal reserves1;// = Web3.Convert.FromWei((BigInteger)reservesResult.Result[1].Result);
reserves0 = Web3.Convert.FromWei((BigInteger)reservesResult.Result[0].Result, contractA.UnitDecimal);
reserves1 = Web3.Convert.FromWei((BigInteger)reservesResult.Result[1].Result, contractB.UnitDecimal);
resultModel.TokenAReserves = reserves0;
resultModel.TokenBReserves = reserves1;
resultModel.State = 1;
}
}
catch (Exception ex)
{
resultModel.State = -1;
resultModel.Message = ex.Message;
}
return resultModel;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Token代币买入
这里调用了swapExactTokensForTokens方法进行代币的买入兑换,在发送代币买入指令之前可以先通过getAmountsOut函数计算能得到的代币数量,然后减去允许的滑点得到amountOutMin参数。
public static ResultModel swapExactTokensForTokens(RPCModel rPCModel, AccountModel accountEntity, ContractModel routerContract, ContractModel contractA, ContractModel contractB, SwapModel swapModel)
{
ResultModel resultModel = new ResultModel();
try
{
Account account = new Account(accountEntity.PrivateKey, rPCModel.NetworkId);
Web3 web3 = new Web3(account, rPCModel.URL);
Contract myContract = web3.Eth.GetContract(routerContract.ABI, routerContract.Address);
BigInteger amountIn = 0;
amountIn = Web3.Convert.ToWei(contractB.AmountLiquidity, contractB.UnitDecimal);
BigInteger gPrice = Web3.Convert.ToWeiFromUnit(rPCModel.GasPrice, 1000000000);
var amountResult = myContract.GetFunction("getAmountsOut").CallDecodingToDefaultAsync(amountIn, swapModel.Path);
amountResult.Wait();
System.Collections.Generic.List<BigInteger> list = (System.Collections.Generic.List<BigInteger>)amountResult.Result[0].Result;
decimal decOut = 0;
BigInteger amountOutMin = 0;//
decOut = (decimal)Web3.Convert.FromWei((BigInteger)list[1], contractA.UnitDecimal) * (decimal)(1 - swapModel.Slippage / 100);
amountOutMin = Web3.Convert.ToWei(decOut, contractA.UnitDecimal);
BigInteger gasLimit=new BigInteger(rPCModel.GasLimit);
TransactionInput transactionInput = new TransactionInput
{
Data = "",
Gas = new HexBigInteger(gasLimit),
From = account.Address,
Value = new HexBigInteger(0),
GasPrice = new HexBigInteger(gPrice)
};
var swapResult = myContract.GetFunction("swapExactTokensForTokens").SendTransactionAsync(transactionInput, amountIn, amountOutMin, swapModel.Path, account.Address, GetSeconds(DateTime.Now));
swapResult.Wait();
int count = 0;
resultModel.TransHash = swapResult.Result;
while (true)
{
var receipt = web3.Eth.Transactions.GetTransactionReceipt.SendRequestAsync(swapResult.Result);
receipt.Wait();
if (receipt.Result != null)
{
resultModel.State = (int)receipt.Result.Status.ToUlong();
if (resultModel.State == 1)
{
resultModel.Message = "Swap交易成功!";
}
else
{
resultModel.Message = "Swap交易失败!";
}
break;
}
if (count >= 20)
{
resultModel.State = 2;
resultModel.Message = "获取交易hash超时!";
break;
}
Thread.Sleep(2000);
count++;
}
}
catch (Exception ex)
{
resultModel.State =-1;
resultModel.Message= ex.Message;
if (ex.InnerException != null)
{
resultModel.Message += ex.InnerException.Message;
}
}
return resultModel;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# Token代币卖出
代币卖出调用了swapExactTokensForTokensSupportingFeeOnTransferTokens函数,该函数支持有滑点、销毁、通缩、通胀等模式的代币Token。
public static ResultModel swapExactTokensForTokensSupportingFeeOnTransferTokens(RPCModel rPCModel, AccountModel accountModel, ContractModel routerContract, ContractModel contractA, ContractModel contractB, SwapModel swapModel)
{
ResultModel resultModel = new ResultModel();
try
{
Account account = new Account(accountModel.PrivateKey, rPCModel.NetworkId);
Web3 web3 = new Web3(account, rPCModel.URL);
Contract myContract = web3.Eth.GetContract(routerContract.ABI, routerContract.Address);
BigInteger amountIn = Web3.Convert.ToWei(contractA.AmountLiquidity, contractA.UnitDecimal);
BigInteger gPrice = Web3.Convert.ToWeiFromUnit(rPCModel.GasPrice, 1000000000);
var amountResult = myContract.GetFunction("getAmountsOut").CallDecodingToDefaultAsync(amountIn, swapModel.Path);
amountResult.Wait();
System.Collections.Generic.List<BigInteger> list = (System.Collections.Generic.List<BigInteger>)amountResult.Result[0].Result;
decimal decOut = 0;
BigInteger amountOutMin = 0;
decOut = (decimal)Web3.Convert.FromWei((BigInteger)list[1], contractB.UnitDecimal) * (decimal)(1 - swapModel.Slippage / 100);
amountOutMin = Web3.Convert.ToWei(decOut, contractB.UnitDecimal);
BigInteger gasLimit = new BigInteger(rPCModel.GasLimit);
TransactionInput transactionInput = new TransactionInput
{
Data = "",
Gas = new HexBigInteger(gasLimit),
From = account.Address,
Value = new HexBigInteger(0),
GasPrice = new HexBigInteger(gPrice)
};
var swapResult = myContract.GetFunction("swapExactTokensForTokensSupportingFeeOnTransferTokens").SendTransactionAsync(transactionInput, amountIn, amountOutMin, swapModel.Path, account.Address, GetSeconds(DateTime.Now));
swapResult.Wait();
int count = 0;
resultModel.TransHash = swapResult.Result;
while (true)
{
try
{
var receipt = web3.Eth.Transactions.GetTransactionReceipt.SendRequestAsync(swapResult.Result);
receipt.Wait();
if (receipt.Result != null)
{
resultModel.State = (int)receipt.Result.Status.ToUlong();
if (resultModel.State == 1)
{
resultModel.Message = "Swap交易成功!";
}
else
{
resultModel.Message = "Swap交易失败!";
}
break;
}
}
catch (Exception ex)
{
}
if (count >= 20)
{
resultModel.State = 2;
resultModel.Message = "获取交易hash超时!";
break;
}
Thread.Sleep(2000);
count++;
}
return resultModel;
}
catch (Exception ex)
{
resultModel.State = -1;
resultModel.Message= "交易失败:" + ex.Message;
if (ex.InnerException != null)
{
resultModel.Message += ex.InnerException.Message;
}
return resultModel;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75