欢迎访问比特币快报,了解最新比特币价格行情资讯及区块链技术信息就上eBTCnews.com!
🏠 技术 基于以太坊和USDC搭建去中心化金融系统

基于以太坊和USDC搭建去中心化金融系统

比特王 比特王 | 2020-08-05 09:30 | 人气:51 | 赞:0 |

在Coinbase,我们希望可以创建一个开放的金融系统。我们坚信提高金融的自由度可以让世界更美好。去中心化金融,简称DeFi是一个开放,无界限并且可以程序化的金融,是提供金融自由度的一种方式。


基于以太坊和USDC搭建去中心化金融系统


智能合约


DeFi是运行在去中心化网络上(例如以太坊[4]),由智能合约(例如USD币:一种区块链上美元代币)驱动的。智能合约其实是很好理解的,Nick Szabo是数字货币和加密学的先驱者,在1997年他[最早提出智能合约]((https://www.fon.hum.uva.nl/rob/Courses/InformationInSpeech/CDROM/Literature/LOTwinterschool2006/szabo.best.vwh.net/idea.html "最早提出智能合约") )并将其比喻为自动贩卖机。


自动贩卖机就如一个被植入自动化程序的合约,他有如下特点:


1. 你按照显示的金额放入货币,机器会给你饮料;

2. 你不按照显示的金额付款,你拿不到饮料;

3. 如果你付了应付金额,但是机器没给你饮料,亦或是你在没付钱的情况下机器给了你饮料,这些都是违反自动贩卖机的规则。


自动贩卖机可以在无人干涉情况下,很好的履行他的合约精神。


现代智能合约[5]工作原理也是类似的,合约的条件是用可执行的代码来表达的。去中心化网络保证按要求执行,并且任何人都不能破坏规则或者篡改结果。因为网络会一字不差地执行代码,有瑕疵的智能合约会产生预想不到的后果。(“代码是条例”)


把握当下


很多人觉得在区块链上去搭建应用比较困难,认为只有高级玩家可以尝试。但是近几年出现来了很多工具,开发者界面,帮助编程能力一般的人去实现构建。


最近,DeFi[6]生态呈现爆发式地增长。USDC不到2年捕获的总价值达到10亿美元[7],同时各种各样的DeFi服务在不到3年的时间,总价值超过20亿美金。当下可谓是DeFi发展的最佳时机。


基于以太坊和USDC搭建去中心化金融系统


下面的教程主要目的是介绍如何开发自己的DeFi智能合约。我们希望,本教程可以帮助创建一个全球、开放的金融体系。


开始


本系列教程假设你有使用JavaScript[8]的经验,这是世界上使用最广泛的编程语言。你还将学习Solidity[9],Ethereum[10]上使用的智能合约编程语言。最后,你也会认识USDC[11],这是DeFi应用程序中最广泛采用的由法币支持的稳定代币。


设置开发环境


首先,我们需要一个类unix的环境,并在上面安装Node.js v12.x[12] (LTS的最新版本)。macOS本身就是Unix环境,Windows用户可以通过从微软商店安装Ubuntu on WSL[13]来获得它。更详细的步骤macOS可以查看这里[14],Windows查看这里[15]。对于文本编辑器,强烈推荐使用Visual Studio Code[16],因为你将使用的项目模板是预先配置的,但你可以使用任何编辑器。哦,我更喜欢Vim的快捷键绑定方式[17]。


建立项目


建立一个Solidity项目需要一些工作,而且老实说,在这个阶段我们不希望被搭建项目琐碎的工作而分心了,所以已经为你准备了一个预配置模板[18]。


通过在终端中运行以下命令下载和设置模板:


$ git clone https://github.com/CoinbaseStablecoin/solidity-tutorial.git

$ cd solidity-tutorial

$ npm install -g yarn        # Install yarn package manager

$ yarn                       # Install project dependencies


当yarn在安装的时候,你可能会看到一些编译错误。你可以忽略这些错误。当你最后看到“完成”信息,你就可以开始了。


在Visual Studio Code打开项目


在Visual Studio Code中打开项目文件夹(solidity-tutorial)。项目第一次打开时,Visual Studio Code可能会提示你安装扩展。继续并点击“安装所有”,这将增加各种有用的扩展,如代码自动格式化和solidity语法高亮。


基于以太坊和USDC搭建去中心化金融系统


在以太坊建立账户


在以太坊上做任何事情之前,你需要有一个帐户。账户通常被称为“钱包”,因为它们可以包含像ETH和USDC这样的数字资产。终端用户通常使用以太坊钱包应用,像Coinbase钱包[19]或Metamask[20]来创建钱包,但通过程序使用ethers.js[21]方式创建一个账户也很简单。


在src目录下,创建一个新的js文件createWallet.js,写入如下代码:


const ethers = require("ethers");


const wallet = ethers.Wallet.createRandom();


console.log(`Mnemonic: ${wallet.mnemonic.phrase}`);

console.log(`Address: ${wallet.address}`);


保存文件,然后使用Node.js来执行文件


$ node src/createWallet.js


Mnemonic: rabbit enforce proof always embrace tennis version reward scout shock license wing

Address: 0xB3512cF013F71598F359bd5CA3f53C1F4260956a


刚才发生了什么?好吧,你得到了一个全新的Ethereum账号。“mnemonic”是“助记符”或被称为的“恢复短语”,是用于帐户执行操作所需的加密密钥,地址是帐户的名称。记得把它们写下来。另外,为了防止你们使用我的助记符,我已经做了轻微的修改,请使用你自己的!


可以把这些看作是密码和银行账户的帐号,不过钱包地址可以在几秒钟内创建一个,而且你不需要填写申请表格或分享任何个人信息。而且你可以在任何地方运行此代码。


助记符必须保密。如果你丢失了它,你将永远无法访问你的帐户和帐户中存储的任何资产,没有人能够帮助你!把它放在安全的地方!


从技术上讲,你并没有真正“创造”一个帐户本身。相反,你创建的是一个私有/公共密钥对。如果你好奇到底发生了什么,可以看下椭圆曲线密码学[22],比特币和以太坊规范BIP39[23], BIP32[24],EIP55[25]及其在本项目中[26]的实现。


关于Gas和挖矿


以太坊是一个去中心化的网络,由世界各地成千上万台计算机组成,但是它们并不是免费运行的。要在区块链上执行变更状态,如存储和更新数据,你必须用用ETH向网络支付交易费,在以太坊上也称为“gas”。gas[27]费用和增加新区块获得的奖金就是激励矿工运算的激励。这个过程被称为“挖矿”,不断做运算的被称为“挖矿者”。我们将在稍后的教程中再次讨论这个问题(gas,gas价格和gas限额)。


获得测试网络ETH


现在你有了账户,你应该存一些ETH。在开发的时候我们不想浪费真正的ETH,所以我们需要一些ETH用于在测试网络开发和测试网络(“testnet”)。现在有许多不同的Ethereum测试网络,我们将会使用Ropsten,因为获得测试代币比较容易。首先,让我们使用Etherscan[28]检查当前余额,这是一个以太坊的区块信息的浏览器。你可以在浏览器中输入以下URL,将你的地址替换为之前创建的地址,以0x开始。


https://ropsten.etherscan.io/address/**YOUR_ADDRESS**[29]


基于以太坊和USDC搭建去中心化金融系统


来源:* [*ropsten.etherscan.io*](https://ropsten.etherscan.io/ "*ropsten.etherscan.io*")


你可以看到现在余额是0。保持该页面打开,并在另一个页面中打开Ropsten Ethereum Faucet[30]。在第二个页面中,输入你的地址,然后点击“发送我(Send me)”按钮。完成后可能只需要几秒钟到一两分钟。稍后再次检查Etherscan,你应该会看到新的余额为1ETH和转入交易。


基于以太坊和USDC搭建去中心化金融系统


通过编程获取ETH余额


连接以太坊网络


我们可以使用Etherscan查看余额,但是使用代码也可以很容易查看余额。在我们写代码之前,我们需要连接到以太坊网络。有许多方法可以实现,包括在自己的计算机上运行一个网络节点,但到目前为止,最快和最简单的方法是通过一个托管节点来实现,例如INFURA[31]或Alchemy[32]。前往INFURA[33],创建一个免费帐户并创建一个新项目来获取API密钥(项目ID)。


Go Ethereum (“geth”)[34] 和 Open Ethereum[35](之前被称为Parity Ethereum)。这两个是最为广泛使用地节点软件。


通过代码查看ETH余额


首先,通过读取助记符进入到我们的账户中。在src文件夹下,创建一个名为wallet.js的JavaScript文件。敲入以下代码:


const ethers = require("ethers");


// 在这里替换你自己的助记符


const mnemonic =

  "rabbit enforce proof always embrace tennis version reward scout shock license wing";

const wallet = ethers.Wallet.fromMnemonic(mnemonic);


console.log(`Mnemonic: ${wallet.mnemonic.phrase}`);

console.log(`Address: ${wallet.address}`);


module.exports = wallet;


用你自己的字符串替换代码中的助记符字符串。请注意,在生产中,助记符不应该像这样直接写在代码中。理想的是它从配置文件或环境变量中读取,这样它就不会因为写在源代码中而泄漏。


执行代码,你应该能够看到和之前相同的地址


$ node src/wallet.js

Address: 0xB3512cF013F71598F359bd5CA3f53C1F4260956a


接下来,在同一个文件夹中,创建一个名为provider.js的新文件。在这个文件中,我们将使用前面获得的INFURA API密钥。记得替换成你自己的api key:


const ethers = require("ethers");


const provider = ethers.getDefaultProvider("ropsten", {

  // 替换INFURA API KEY

  infura: "0123456789abcdef0123456789abcdef",

});


module.exports = provider;


最后,我们会引用wallet.js和provider.js,在同一目录下创建新的文件getBalance.js


const ethers = require("ethers");

const wallet = require("./wallet");

const provider = require("./provider");


async function main() {

  const account = wallet.connect(provider);

  const balance = await account.getBalance();

  console.log(`ETH Balance: ${ethers.utils.formatUnits(balance, 18)}`);

}


main();


执行代码,你就可以看到余额了


$ node src/getBalance.js

Address: 0xB3512cF013F71598F359bd5CA3f53C1F4260956a

ETH Balance: 1.0


代币换算


我们刚刚创建的代码非常容易理解,但是你会想知道**ethers.utils.formatUnits(balance, 18)**的作用。嗯,ETH实际上有18位,最小的单位叫“wei”(发音为“way”)。换句话说,一个ETH等于1000,000,000,000,000,000,000 wei。另一个常见的单位是Gwei(发音为“Giga-way”),也就是1,000,000,000 wei。getBalance方法是以wei中返回了结果,因此我们必须通过将结果除以10的18次方将其转换回ETH。你可以在这里[36]找到全部的单位名称。


你也可以使用 ethers.utils.formaTether(balance), 相当于**ethers.utils.formatUnits(balance, 18)**的简写.


获得测试网络的USDC


你账户里的只有ETH,略显孤单,所以我们打算增加一些USDC。我已经在Ropsten testnet上部署了一个伪USDC智能合约[37]。虽然我们没有专门获得免费USDC的网站,但是在合约中已经包含了该功能,当你调用它时,它会给你一些免费的testnet USDC。你可以在Etherscan中的合约代码栏目[38]找到合约,并在合约源代码中搜索gimmeSome。我们将调用这个函数来将一些USDC发送到我们的帐户。


基于以太坊和USDC搭建去中心化金融系统


发起交易来调用智能合约


在以太坊的智能合约中有主要有两类方法:读写和只读。第一种方式可以修改区块链上的数据,而第二种仅仅是读取区块链上的数据,但是不能修改数据。 只读方法不用通过交易来调用,所以不会耗费ETH,除非是在读写方法中的一部分。读写方法是一定要通过交易来调用,所以一定会消耗ETH。调用gimmeSome方法会改变USDC数量的改变,所以必须通过一次交易来完成。


调用智能合约的方法需要再多些步骤,但是也不复杂。第一,需要知道调用方法的完整接口,被称为函数签名或函数原型。我们看下gimmeSome方法的源码如下:


function gimmeSome() external


这是一个没有任何参数的方法,而且被标记为external,表示只能从外部可以调用,不能被合约内的其他方法调用。这个对我们来说不影响,因为我们就是从外部调用。


在主链上的真实的USDC合约[39]是没有gimmeSome 方法的


在src 文件夹下创建一个新文件,命名为getTestnetUSDC.js,然后输入以下代码


const ethers = require("ethers");

const wallet = require("./wallet");

const provider = require("./provider");


async function main() {

  const account = wallet.connect(provider);


  const usdc = new ethers.Contract(

    "0x68ec573C119826db2eaEA1Efbfc2970cDaC869c4",

    ["function gimmeSome() external"],

    account

  );


  const tx = await usdc.gimmeSome({ gasPrice: 20e9 });

  console.log(`Transaction hash: ${tx.hash}`);


  const receipt = await tx.wait();

  console.log(`Transaction confirmed in block ${receipt.blockNumber}`);

  console.log(`Gas used: ${receipt.gasUsed.toString()}`);

}


main();


代码开始部分, 使用我们感兴趣的gimmeSome的接口和测试网络的地址USDC合约0x68ec⋯69c4[40]地址实例化了一个合约对象(new ethers.Contract)。 这个方法是不需要任何参数,但是你可以在最后加入一个参数。这次我20 Gwei的gas费,来加快交易打包速度。与网络交互的所有方法在本质上是异步的,返回一个**Promise**[41],所以我们使用JavaScript的**await**[42]。完成后会返回交易的hash值,这是用于查看交易的惟一标识符。


运行该代码,你将看到如下内容:


$ node src/getTestnetUSDC.js


Address: 0xB3512cF013F71598F359bd5CA3f53C1F4260956a

Transaction hash: 0xd8b4b06c19f5d1393f29b408fc0065d0774ec3b4d11d41be9fd72a8d84cb6208

Transaction confirmed in block 8156350

Gas used: 35121


好的,祝贺你通过代码的方式完成了第一次ETH的交易。在Ropsten Etherscan[43]查看下你的账户地址和交易hash。你应该可以查看到,账户里有10个测试USDC,ETH的余额小于1,因为支付了gas费用。


基于以太坊和USDC搭建去中心化金融系统


如果你在看Etherscan交易,你会发现这是一笔发送0个ETH连同4个字节的数据到合约地址。如果调用方法时有参数,就会有超过4字节的数据。如果你想了解该数据是如何编码的,请阅读Ethereum合约ABI规范[44]。


Gas,Gas费用 和 Gas限制


之前我提到过,我们给这笔交易20Gwei的gas价格来加快交易速度,程序也显示了使用的gas的量。这一切意味着什么?嗯,以太坊是由网络运营商组成的网络。可以把它想象成一台世界计算机。这不是一台免费的电脑,你在这台电脑上运行的每条指令都要花钱。这台电脑也被全世界的人共享,这意味着每个人都必须互相竞争,以获得他们使用这台电脑的时间。


我们怎样才能做到公平呢?嗯,我们可以把这台电脑上的时间进行拍卖,你愿意出的价越高,你执行的效率也更快。这当然不是十全十美的,因为可能会导致只有有很多ETH的人才有特权使用这个电脑。然而,在系统变得更可扩展并能够容纳更多交易之前,这是我们可以选择的一个可行解决方案。


回到区块链术语上来, “gas used”是在完成交易所消耗的计算资源的数量,“gas price”是你愿意为每一单位gas支付的价格。一般来说,你愿意支付的金额越高,你的交易优先级就越高,通过网络确认的速度也就越快。上面我们使用20 Gwei作为gas价格,所使用的gas为35,121(可以在Etherscan中查看交易),所以总共使用gas费用为35,121 * 20 Gwei = 702,420 Gwei或0.00070242 ETH。


因为gas需要消耗金钱,你可能想要设定你愿意花费的最多gas。幸运的是,你可以通过“gas limit”设置。如果交易最终需要的gas超过规定的限额,交易就会失败,而不会继续执行。需要注意的是如果交易因为gas限额而失败,已经花费的gas将不会退还给你。


通过调用智能合约读取数据


你可以在Etherscan上查看到收到了10个USDC,让我们通过代码检查余额来确认这一点。


我们修改下src文件夹下的getBalance.js文件


const ethers = require("ethers");

const wallet = require("./wallet");

const provider = require("./provider");


async function main() {

  const account = wallet.connect(provider);


  // 定义合约接口

  const usdc = new ethers.Contract(

    "0x68ec573C119826db2eaEA1Efbfc2970cDaC869c4",

    [

      "function balanceOf(address _owner) public view returns (uint256 balance)",

    ],

    account

  );


  const ethBalance = await account.getBalance();

  console.log(`ETH Balance: ${ethers.utils.formatEther(ethBalance)}`);


  // 调用balanceOf方法

  const usdcBalance = await usdc.balanceOf(account.address);

  console.log(`USDC Balance: ${ethers.utils.formatUnits(usdcBalance, 6)}`);

}


main();


USDC是ERC20代币,因此它包含ERC20规范[45]中定义的所有方法。balanceOf就是其中之一,它的接口直接来自规范定义的。 balanceOf是一个只读函数,所以它可以免费调用。最后,值得注意的是,USDC使用6位小数精度,而其他许多ERC20代币使用18位小数。


基于以太坊和USDC搭建去中心化金融系统


你可以在这里[46]了解更多关于Solidity方法。


执行以下代码,你就可以看到USDC余额


$ node src/getBalance.js


Address: 0xB3512cF013F71598F359bd5CA3f53C1F4260956a

ETH Balance: 0.9961879

USDC Balance: 10.0


ETH和USDC转账


现在我们来看看怎么可以使用账户中的ETH和USDC


使用ETH


在src文件夹下创建transferETH.js文件


const ethers = require("ethers");

const wallet = require("./wallet");

const provider = require("./provider");


async function main(args) {

  const account = wallet.connect(provider);

  let to, value;


  // 生成第一个参数——接受地址

  try {

    to = ethers.utils.getAddress(args[0]);

  } catch {

    console.error(`Invalid recipient address: ${args[0]}`);

    process.exit(1);

  }


    // 生成第二个参数——数量

  try {

    value = ethers.utils.parseEther(args[1]);

    if (value.isNegative()) {

      throw new Error();

    }

  } catch {

    console.error(`Invalid amount: ${args[1]}`);

    process.exit(1);

  }

  const valueFormatted = ethers.utils.formatEther(value);


  //检查账户有足够余额

  const balance = await account.getBalance();

  if (balance.lt(value)) {

    const balanceFormatted = ethers.utils.formatEther(balance);


    console.error(

      `Insufficient balance to send ${valueFormatted} (You have ${balanceFormatted})`

    );

    process.exit(1);

  }


  console.log(`Transferring ${valueFormatted} ETH to ${to}...`);


  // 提交转账

  const tx = await account.sendTransaction({ to, value, gasPrice: 20e9 });

  console.log(`Transaction hash: ${tx.hash}`);


  const receipt = await tx.wait();

  console.log(`Transaction confirmed in block ${receipt.blockNumber}`);

}


main(process.argv.slice(2));


这段代码虽然比前面的代码长,但实际上只是将之前所学的代码组合起来。这段代码中要有两个命令行参数。第一个是接收者地址,第二个是要发送的金额。然后确保提供的地址是有效的,提供的金额不是负数,并且帐户有足够的余额能够发送请求的金额。然后,提交交易并等待它被确认。


用之前的createWallet.js创建一个新账户,然后尝试向这个地址转些ETH


$ node src/createWallet.js


Mnemonic: napkin invite special reform cheese hunt refuse ketchup arena bag love caution

Address: 0xDdAC089Fe56F0a9C70e6a04C74DCE52F86a91e13


$ node src/transferETH.js 0xDdAC089Fe56F0a9C70e6a04C74DCE52F86a91e13 0.1


Address: 0xB3512cF013F71598F359bd5CA3f53C1F4260956a

Transferring 0.1 ETH to 0xDdAC089Fe56F0a9C70e6a04C74DCE52F86a91e13...

Transaction hash: 0xa9f159fa8a9509ec8f8afa8ebb1131c3952cb3b2526471605fd84e8be408cebf

Transaction confirmed in block 8162896


基于以太坊和USDC搭建去中心化金融系统


你可以在Etherscan[47]看到结果,我们再来测试验证逻辑是有效的。


$ node src/transferETH.js foo


Address: 0xB3512cF013F71598F359bd5CA3f53C1F4260956a

Invalid address: foo


$ node src/transferETH.js 0xDdAC089Fe56F0a9C70e6a04C74DCE52F86a91e13 0.1.2

Address: 0xB3512cF013F71598F359bd5CA3f53C1F4260956a

Invalid amount: 0.1.2


$ node src/transferETH.js 0xDdAC089Fe56F0a9C70e6a04C74DCE52F86a91e13 -0.1


Address: 0xB3512cF013F71598F359bd5CA3f53C1F4260956a

Invalid amount: -0.1


$ node src/transferETH.js 0xDdAC089Fe56F0a9C70e6a04C74DCE52F86a91e13 100


Address: 0xB3512cF013F71598F359bd5CA3f53C1F4260956a

Insufficient balance to send 100.0 (You have 0.89328474)


USDC转账


上面很大一部的代码可以用到这里,主要的区别是USDC是精确到6位,还有你是使用ERC20 规范中的transfer。入参依然是“to” 及 “value”,然后调用智能合约的transfer 方法。


在同一文件下创建transferUSDC.js文件


const ethers = require("ethers");

const wallet = require("./wallet");

const provider = require("./provider");


async function main(args) {

  const account = wallet.connect(provider);


  // 在合约中定义balanceOf和transfer方法

  const usdc = new ethers.Contract(

    "0x68ec573C119826db2eaEA1Efbfc2970cDaC869c4",

    [

      "function balanceOf(address _owner) public view returns (uint256 balance)",

      "function transfer(address _to, uint256 _value) public returns (bool success)",

    ],

    account

  );


  let to, value;


    // 生成第一个参数——接受地址

  try {

    to = ethers.utils.getAddress(args[0]);

  } catch {

    console.error(`Invalid address: ${args[0]}`);

    process.exit(1);

  }


    // 生成第二个参数——数量

  try {

    value = ethers.utils.parseUnits(args[1], 6);

    if (value.isNegative()) {

      throw new Error();

    }

  } catch {

    console.error(`Invalid amount: ${args[1]}`);

    process.exit(1);

  }

  const valueFormatted = ethers.utils.formatUnits(value, 6);


  //检查账户有足够余额

  const balance = await usdc.balanceOf(account.address);

  if (balance.lt(value)) {

    const balanceFormatted = ethers.utils.formatUnits(balance, 6);


    console.error(

      `Insufficient balance to send ${valueFormatted} (You have ${balanceFormatted})`

    );

    process.exit(1);

  }


  console.log(`Transferring ${valueFormatted} USDC to ${to}...`);


  // 提交转账,调用transfer方法

  const tx = await usdc.transfer(to, value, { gasPrice: 20e9 });

  console.log(`Transaction hash: ${tx.hash}`);


  const receipt = await tx.wait();

  console.log(`Transaction confirmed in block ${receipt.blockNumber}`);

}


main(process.argv.slice(2));


试一试,你应该可以看到以下结果:


$ node src/transferUSDC.js 0xDdAC089Fe56F0a9C70e6a04C74DCE52F86a91e13 1


Address: 0xB3512cF013F71598F359bd5CA3f53C1F4260956a

Transferring 1.0 USDC to 0xDdAC089Fe56F0a9C70e6a04C74DCE52F86a91e13...

Transaction hash: 0xc1b2157a83f29d6c04f960bc49e968a0cd2ef884761af7f95cc83880631fe4af

Transaction confirmed in block 8162963


基于以太坊和USDC搭建去中心化金融系统


恭喜


在本教程中,你学习了如何生成钱包、查询余额、转移代币和调用智能合约。你可能觉得自己还不太了解区块链,不过你已经有足够的知识,去构建自己加密钱包应用程序。为了保持简单,我们一直在编写命令行脚本,那么是否可以尝试构建一个图形界面的网页呢?


在本教程系列的下一部分中,我们将从头开始用solidity编写智能合约,并学习如何构建自己的硬币,可与USDC交换。我们还将使用今天学到的技术来与我们构建的合约进行互动。请继续关注。

关键词:以太坊 ETH USDC DeFi 去中心化金融

原标题:基于以太坊和USDC搭建去中心化金融系统

0

【版权说明】文章为作者独立观点,不代表《比特币快报》立场。如有侵权,请联系我们。

共0条评论 你可以在 登录注册 后对本文发表评论