Admin 发布的文章

  • 安装
npm install -g truffle
  • 新建一个文件夹,在文件夹里执行:
truffle init
  • 在 contracts/ 文件夹新建自己的.sol文件,例如 Storage.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract Storage {
    uint256 number;
    function store(uint256 num) public {
        number = num;
    }
    function retrieve() public view returns (uint256){
        return number;
    }
}
  • 编译
truffle compile

如果报错Cannot find module '@truffle/hdwallet-provider'就执行npm install @truffle/hdwallet-provider安装一下

编译成功就可以在 build/contracts/ 文件夹看到一些.json文件,里面包含了编译后的abi

编译前可以在truffle-config.js里配置solc版本,例如version: "0.8.4"

第一次编译可能需要很久,cmd界面一直显示 Compiling your contracts...,耐心等待一般没问题。

  • 在 test/ 文件夹添加测试文件,例如 Storage.js
const Storage = artifacts.require('Storage.sol');
contract('Storage', () => {
  it('Should update data', async () => {
    const storage = await Storage.new();
    await storage.store(10);
    const data = await storage.retrieve();
    assert(data.toString() === '10');
  });
});
  • 进行测试验证
truffle test
  • 在 migrations/ 文件夹新建 2_deploy_contracts.js,内容如下:
const Storage = artifacts.require('Storage.sol');
module.exports = function (deployer) {
  deployer.deploy(Storage);
};

如果文件名为deploy_contracts.js会导致后面的deploy没有结果,因此文件名前面要放“2_”。文件夹里自带的1_initial_migration.js可以删除,否则后续deploy也会deploy其对应的contract

  • 运行本地block chain net
truffle develop

如果没有ganache可能会报错,可能得先用npm安装ganache

启动成功就会进入truffle命令行状态,以 truffle(develop)> 开头

启动后会创建10个测试账号,这10个账号在下次启动时不会变

  • 在truffle命令行状态下,deploy之前编写的solidity:
migrate --reset

deploy成功就可以看到contract address了

  • deploy到bsc test net前,先去faucet领取一个BNB test coin
  • 安装
npm install @truffle/hdwallet-provider
  • 编辑truffle-config.js,在module.exports之前添加:
const fs = require('fs');
const HDWalletProvider = require('@truffle/hdwallet-provider');
let privateKey = fs.readFileSync(".privateKey").toString().trim();
if (-1===privateKey.toLowerCase().indexOf('0x')) {
  privateKey = '0x' + privateKey
}
const privateKeys = [privateKey]

私钥放在.privateKey文件里,注意添加到.gitignore

如果没有安装truffle/hdwallet-provider,需要先npm安装一下

在networks节点添加:

bscTestnet: {
  provider: ()=> new HDWalletProvider(privateKeys, 'https://data-seed-prebsc-2-s2.binance.org:8545/'),
  network_id: 97, skipDryRun: true
}

如果deploy到其它节点,按照上述格式添加其它节点信息即可

  • deploy 到bsc test net
truffle migrate --reset --network bscTestnet

deploy成功可以看到transaction hash和contract address等信息,到bscscan就可以查到相应信息

  • 用truffle console去跟bsc test交互
truffle console --network bscTestnet

成功就会进入truffle命令行状态,以 truffle(bscTestnet)> 开头。不过因为oversea,经常断线退出truffle命令行状态

  • 用truffle console进行交互
truffle(bscTestnet)> storage = await Storage.deployed()
truffle(bscTestnet)> storage.address
'log contract address here'
truffle(bscTestnet)> await storage.store(10)
truffle(bscTestnet)> data = await storage.retrieve()
truffle(bscTestnet)> data.toString()
'10'

在使用前添加这段代码

Number.prototype.toFloor = function (decimal) {
    let num = this.toString();
    let index = num.indexOf('.');
    if(index !== -1){
        num = num.substring(0, decimal + index + 1)
    } else {
        num = num.substring(0)
    }
    return parseFloat(num).toFixed(decimal)
}
String.prototype.toFloor = function (decimal) {
  let numberString = this
  if (isNaN( Number(numberString) )) {
    return '0'
  }
  if (-1===numberString.indexOf('.') || isNaN(parseInt(decimal)) ) {
    return numberString
  }
  const strings = numberString.split('.')
  if (parseInt(decimal)<1) {
    return strings[0]
  }
  return strings[0]+'.'+strings[1].substring(0, decimal)
}

写在开头:ethers的官方文档不够详细,如果看不懂,可以去web3的官方文档找对应的函数查看解释

  • 首先需要npm安裝ethers
  • 然后再在项目里引入

    const ethers = require("ethers") // nodejs
    import ethers from "ethers" // front-end
    // 如果是非webpack前端,也可以直接用script标签引入.js文件
  • 关联钱包

    const wallet = new ethers.Wallet('your private key') // 这个必须用私钥。还有另一个方式是用助记词
  • 将钱包关联到contract,这样就可以发起签名,进行transfer、approve等操作

    const account = wallet.connect(provider)
    const someContract = new ethers.Contract(
    'contract address for example, USDT, BUSD, panca etc...',
    ['function approve(address spender, uint amount) public returns(bool)',
    'function allowance(address owner, address spender) view returns(uint256)'], // 这个数组放你需要用到的abi
    account
    )
  • 配置provider,用于链上交互

    const provider = new ethers.providers.WebSocketProvider("wss://bsc-ws-node.nariox.org:443") // 示例里的链接为bsc主网的社区wss,测试用可以,大型项目建议使用付费的或者自建full node
  • 例子1:发起approve。注意,await需要在async类型的函数里运行

    const gasPrice = await provider.getGasPrice(); // bsc大多时候返回5000000000 (5e9)
    const approveResult = await someContract.approve(
    "spender's address", // 允许让谁调用,可以是别人的账号,也可以是诸如pancakeswap的router address等
    123456, // 最高0xffff(64个F)
    { gasPrice: gasPrice, gasLimit: "450000" } // 为什么gasPrice>gasLimit?
    )
    // approveResult包含了此次approve事件的hash等信息
  • 例子2:查看某个approve的授权额

    const allowance = await someContract.allowance(wallet.address, "spender's address")
    // 返回值为uni256的BigNumber,如果是0,用0===allowance依然false,但0==allowance则为true
    // 如果从来没approve过,则allowance为0
  • 例子3:取消某个approve:

    • 方法跟例子1一样,只不过第二个参数授权额度设置为0即可
  • 例子4:根据token0和token1获取它们在LP的pair address

    // 想要获取pair address需要先构造对应AMM的factory contract
    const factory = new ethers.Contract(
    "AMM's factory address", 
    ["function getPair(address tokenA, address tokenB) external view returns (address pair)"], // factory的abi不止这个,详情查看对应的factory.sol,如果是bsc则在bscscan上查AMM's factory address应该也能查到
    account
    )
    const pairAddress = await factory.getPair("token0 address", "token1 address") // 两个address顺序调换不影响
  • 例子5:获取某个token对在LP里的储量

    // 想要获得token0和token1在LP里的储量,需要先构造pair contract
    const pair = new ethers.Contract(
    pairAddress,
    ["function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)"],
    // 如果想要更多其它abi,假设是bsc链的pairAddress,可以去bscscan搜,里面直接列出了所有的function
    account
    )
    const reserves = await pair.getReserves()
    // reserves.reserve0和reserves.reserve1就是token0和token1在LP里的储量,具体顺序可能需要打印log才知道
    // 此时我们就得到大名鼎鼎的X*Y=K了,reserve0和reserve1分别就是X和Y
    // 利用reserve0/reserve1或者reserve1/reserve0即可得到他们之间互相的swap价格,如果不考虑swap数量的话。如果考虑数量对价格的impact,需要用到下面[例子7]的方式
  • 例子6:ethers.utils类的使用:直接看官方文档吧,这个包含了wei和ether等单位转换之类的小工具
  • 例子7:根据tokenIn的数量获取swap价格

    // 首先要构建router contract
    const router = new ethers.Contract(
    'router contract of AMM, for example pancake',
    [
      "function getAmountsOut(uint amountIn, address[] memory path) public view returns (uint[] memory amounts)",
      "function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)",
    ],
    account
    )
    // 然后再设置tokenIn的数量。变量里的in和out分别指放入LP(sell)和从LP取出(buy)
    const amountIn = ethers.utils.parseEther("123"); // parseEther表示精度为18的token,如果精度不为18需要用另一个parse 
    const amounts = await router.getAmountsOut(amountIn, [
    'tokenIn contract address',
    'tokenOut contract',
    ]);
    // amounts是一个数组,amounts[0]和amounts[1]跟tokenIn和tokenOut的对应关系可以自己打log测试一下
    // 为什么不直接用X/Y去计算价格?因为amountIn会导致X和Y数量的变化,价格也跟着变,如果K大还好,K小的话,影响很大
  • 例子8:发起swap,接上例

    const tx = await router.swapExactTokensForTokens(
    amountIn,
    amounts[1].sub(amounts[1].div(10)), // 这个参数称为amountOutMin,如果最终swap得到的tokenOut数量低于这个值系统就不swap
    [tokenIn address, tokenOut address],
    wallet.address,
    Date.now() + 1000 * 60 * 10, // 链上排队?超过10分钟则放弃swap
    {
      gasPrice: gasPrice,
      gasLimit: "450000", // gasCost = gasPrice * min( gasLimit, gasUsed )
      nonce: null, // ???
    }
    )
    const receipt = await tx.wait();
    // receipt包含了本次swap的hash等信息

    2021.10.05补充:

  • ethers.Contract abi如果包含两个同名函数,该函数名会无法调用,即使函数的参数不一样。同名函数参数不一样,这个在solidity是允许的,并且web3.js调用也正常。这个应该是ethers.js的bug

2021.09.28补充:

  • 如果需要做event listening 或者 get past events, ethers.js没有web3.js方便,特地去看了ethers.js作者的github,作者好像也没打算对这部分进行优化,所以如果用到这两个功能,建议还是web3.js

  • ankr的免费api挺好用的,支持wss和https

springboot打包jar

  • 转自 https://blog.csdn.net/qq_37350706/article/details/84325520
  • 首先配置pom
  • 双击如下图package,如果没有打包出jar,看看是否有代码报错,代码报错是无法打包的,springboot的单元测试代码有错也无法打包,解决代码错误的问题,然后clean一下再package
  • 一切顺利的话应该会得到.jar文件

    服务器部署.jar文件

  • 上传.jar文件到服务器
  • 首先查看是否已经在运行

    ps -ux|grep springboot.jar| grep -v grep
  • 用这个方式可以终止其运行

    kill -9 进程号
  • 上述的9不是进程号,应该在“kill -9 ”后带上进程号。这个细节,时间久了我自己也会忘记
  • 后台运行刚才打包的.jar

    nohup java -jar springboot.jar --spring.datasource.url="jdbc:mysql://127.0.0.1:3306/database_name?useSSL=false&serverTimezone=Asia/Shanghai" --spring.datasource.password=123456 --server.port=8081 &
    • 有时8080端口被占,执行完后,用grep也查不到进程,可用参数更改端口
    • 我有一次本地运行好好的,服务器报500错误,发现是本地测试时,数据库密码跟服务器不一样,上述参数也可以用于更改密码

2021.10.22 补充: