Skip to content

Commit

Permalink
fix:add markdown
Browse files Browse the repository at this point in the history
Signed-off-by: rzry <[email protected]>
  • Loading branch information
laukkw committed Apr 13, 2022
1 parent 3308a41 commit ed2fba9
Showing 1 changed file with 329 additions and 0 deletions.
329 changes: 329 additions & 0 deletions basic/75-golang-dapp/go-dapp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@

# Table of Contents

1. [画龙点睛](#org45c2ad6)
2. [高屋建瓴](#org299d05b)
3. [分星掰两](#org938912f)
4. [厚积薄发](#org52221f0)



<a id="org45c2ad6"></a>

# 画龙点睛

对于一个Web3.0的开发者,Golang的需求是很大的
很多时候需要使用Golang来和链交互操作,来完成一些请求操作.
那Golang怎么和链交互呢?
本文以BSC网络(类ETH)的Dex抢跑程序,以及抢购公开铸造NFT程序的部分代码来交互链上数据.
来看一下整体的流程是如何的.


<a id="org299d05b"></a>

# 高屋建瓴

1. 获取到合约Abi/合约源码,如果是多文件扁平化处理
2. 使用solc abigen等工具生成go代码
3. 开发者调用代码 于链交互


<a id="org938912f"></a>

# 分星掰两

- 获取合约代码,或者Abi

很多链上很多swap都是使用uniswap V2的代码,比如pancake,sushi,mm...
我们直接使用uniswap的router02以及factory 就可以了,具体的代码github里面就有,也可以直接复制下面的代码
https://cronoscan.com/address/0x145677fc4d9b8f19b5d56d1820c48e0443049a30#contracts

一个小技巧,很多链这个没法复制,可以点击More Options --> Compare 来复制.
保存到truffle的一个文件夹.
关于truffle的使用,就不啰嗦了,可以翻看具体之前项目内的文档.
一些工具的推荐,solc-select(pip install),可以切换solc的版本

在 contracts 文件夹 touch uniswap.sol
copy 代码进去. 修改默认的truffle-config.js版本.
执行 solcjs --bin uniswap.sol
如果你顺利的话应该能碰到这种版本错误的问题

这个问题,你首先要确定你的npm 读的是那个 solc ,然后才能解决.
如果未在truffle项目内 npm init -f .那直接删除掉全局的npm内的solc
再使用npm install -g [email protected]就可以了.
当然solc-select也可以.
下为演示步骤
rm -rf /usr/local/lib/node_modules/solc
sudo npm install -g [email protected]

再运行命令,即可生成 bin 文件 . 同理生成 abi 文件
solcjs --bin uniswap.sol
solcjs --abi uniswap.sol

接下来使用abigen 即可生成 go 文件 .
abigen --bin=uniswap_sol_IMeerkatRouter02.bin \
--abi=uniswap_sol_IMeerkatRouter02.abi --pkg=Store --out=router.go

那这个文件就是go 来链接链上Router合约,以及调用Router合约方法的文件
同理生成factory合约文件...

- 使用go来与合约交互

使用一个现有的很简单的项目来讲解,此项目可以在github.com/rzry/flashDeal 找到源码
项目分层为下图.cmd 存放main.go internal 存放主要实现.
pkg 存放了我们上一步生成的一些go的文件.

此项目是监控Swap池子余量,在池子最开始的时候抢跑
(这个失败机率很大,已经被废弃了,请勿用于主网链上.)

项目就是监控池子余量, 根据path 来发起swap的请求.
我们主要来看授权 , 以及调用.

func TestFlash(t *testing.T) {
//建立 client 链接
ci, err := ethclient.Dial("https://evm.cronos.org")
if err != nil {
return
}
//使用Erc20合约生成的go文件,链接这个token,获得Store
pinstance, err := platform.NewStore(common.HexToAddress("0x66e428c3f67a68878562e79A0234c1F83c20870"), ci)
if err != nil {
return
}
//使用此Store来调用Allowance,查询有多少Approve数量
in, err := pinstance.Allowance(nil, common.HexToAddress("0x545c2f7689bd45f8c9b78b6756f13580165e6d4"), common.HexToAddress("0x145677FC4d9b8F19B5D56d1820c48e0443049a30"))
if err != nil {
return
}
t.Logf(in.String())
// 这就是最简单的一个交互,没有授权,一个只是view的请求
}

//删除一些无关代码.会在goroutine 获取到信号后,开始执行case
for {
select {
case <-FasterDone:
// 首先获取到auth
auth, err := Auth(opt)
if err != nil {
return
}
//在这里调用router的 swapExactTokensFroTokens,而第一个参数就是auth
// 在上面view的请求中,我们可以直接传空,但是如果要付Gas,就要从此地址扣钱.
tx, err := opt.RouterInstance.SwapExactTokensForTokens(auth,
pkg.ToEthers(opt.BuyAmount, opt.Decimal).BigInt(),
big.NewInt(0),
opt.Path,
auth.From,
big.NewInt(time.Now().Unix()+20*60))
if err != nil {
return
}

func Auth(opt *options.Config) (auth *bind.TransactOpts, err error) {
//获取Gas
gas, err := opt.Ci.SuggestGasPrice(context.Background())
if err != nil {
return
}
//获取chainId
chainId, err := opt.Ci.ChainID(context.Background())
if err != nil {
return
}
//根据私钥获取PrivateKey
privateKey, err := crypto.HexToECDSA(opt.PrivateKey)
if err != nil {
return
}
//获取到auth
auth, err = bind.NewKeyedTransactorWithChainID(privateKey, chainId)
if err != nil {
return
}
//获取到公钥
publicKeyECDSA, ok := privateKey.Public().(*ecdsa.PublicKey)
if !ok {
return
}
//根据公钥获取from
fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA)
//获取from的nonce
nonce, err := opt.Ci.PendingNonceAt(context.Background(), fromAddress)
if err != nil {
return
}
//修改nonce
auth.Nonce = big.NewInt(int64(nonce))
//修改 msg.value
auth.Value = big.NewInt(0) // in wei
//修改 gaslimit
auth.GasLimit = uint64(opt.GasLimit) // in units
// 修改GasPrice
auth.GasPrice = gas
return
}

有了Auth,就可以请求Swap的接口了,有时候有一些需求,比如项目方需要给很多地址空投
那使用go的线程池,你只用维护一个全局唯一累加的nonce,就可以很快的将所有交易全发出.
(不过这个也要谨慎哦...)

- 使用go 来和Abi 交互

前几天有小伙伴在群内问,如果没有开源的合约怎么调用之类的问题.
恰好之前的写抢购公开Nft的脚本的时候有一部分代码重合.
场景是要在很快的时间来根据Abi获取到Go能调用的函数.
然后拼凑参数,监控目前请求的Gas,排序后,发起一笔交易.
这部分代码比较多,我们只关注发起交易

//也是删除掉无关紧要的代码后的结果,并不具备运行能力.
func (d *Dynamic) NewTx(key string, new bool) (txHash *types.Transaction, err error) {
// DealAbi 函数就是在根据合约地址拿到ABi后自动截取我所需要的那一段,然后返回拼凑的data
// 具体的实现,我会贴在下面
_, data := d.DealAbi()
// 获取ChainId
chain, err := d.GetChain()
if err != nil {
return
}
//获取nonce
nonce, err := d.GetNonce(new, key)
if err != nil {
return
}
// 调用Eth 库 NewTx 来发起一笔新的交易
tx := types.NewTx(&types.DynamicFeeTx{
ChainID: chain,
Nonce: nonce,
// utils.toEthers 是将string转decimal扩大9位
GasTipCap: utils.ToEthers(d.Gas, 9).BigInt(),
GasFeeCap: utils.ToEthers(d.Gas, 9).BigInt(),
Gas: uint64(2150000),
To: d.GetTo(),
Value: utils.ToEthers(d.Value, 18).BigInt(),
Data: data,
})
//获取私钥
private, err := d.getPrivate(key)
if err != nil {
return
}
// 签名
tx, err = types.SignTx(tx, d.Getsigner(chain), private)
if err != nil {
return
}
// 发送到内存池中
if err = d.Cc.SendTransaction(context.Background(), tx); err != nil {
return
}
return tx, err
}

//此为处理 abi的函数
func dealAbi(abis string, inputs []string) (method abi.Method, data []byte) {
//定义pack的数据类型
u256, _ := abi.NewType("uint256", "", nil)
u8, _ := abi.NewType("u8", "", nil)
addr, _ := abi.NewType("address", "", nil)
byteslic, _ := abi.NewType("bytes32[]", "", nil)
newType, _ := abi.NewType("bytes", "", nil)
//将string 按 " 分割
x := strings.Split(abis, "\"")
var (
m string
params []string
ispay bool
)
for k, v := range x {
if v == "internalType" {
params = append(params, x[k+2])
continue
}
if v == "name" {
m = x[k+2]
continue
}

if v == "stateMutability" && x[k+2] == "payable" {
ispay = true
}
}
if len(m) == 0 {
return
}
if len(params) != len(inputs) {
return
}
// 一顿遍历后获得了一个params 代表有多少个参数
var input []abi.Argument
// 遍历所有参数后 append到 数组中
var packinput []interface{}
for k, param := range params {
switch param {
case "uint256":
input = append(input, abi.Argument{
Type: u256,
})
d, err := decimal.NewFromString(inputs[k])
if err != nil {
return
}
packinput = append(packinput, d.BigInt())
case "uint8":
input = append(input, abi.Argument{
Type: u8,
})
d, err := decimal.NewFromString(inputs[k])
if err != nil {
return
}
packinput = append(packinput, d.BigInt())
case "address":
input = append(input, abi.Argument{
Type: addr,
})
packinput = append(packinput, common.HexToAddress(inputs[k]))
case "bytes32[]":
input = append(input, abi.Argument{
Type: byteslic,
})
packinput = append(packinput, inputs[k])
case "bytes":
input = append(input, abi.Argument{
Type: newType,
})
packinput = append(packinput, inputs[k])
}
}
// 调用NewMethod 函数, 来 构建我们请求的函数.
method = abi.NewMethod(m, m, abi.Function, "", false, ispay, input, nil)
// 再将inpit数组 pack起来
data, err := method.Inputs.Pack(packinput...)
if err != nil {
return
}
//再将Method.Sig部分于 data拼起来,就是我们newTx时候的data了
data = bytesCombine(crypto.Keccak256([]byte(method.Sig))[:4], data)
return
}
func bytesCombine(b ...[]byte) []byte {
length := len(b)
s := make([][]byte, length)
for index := 0; index < length; index++ {
s[index] = b[index]
}
sep := []byte("")
return bytes.Join(s, sep)
}


<a id="org52221f0"></a>

# 厚积薄发

在可以使用go 来调用合约后,可以在链下进行很多操作.
比如三明治机器人,闪电贷机器人,抢购Nft,等等都可以操作.
但是链下数据对比链上,以及每条链内存池,以及上链时间的变化都各有千秋.
路漫漫其修远兮.吾将上下而求索.

0 comments on commit ed2fba9

Please sign in to comment.