基于以太坊和 USDC 搭建去中心化金融系统
译文出自:登链翻译计划 [1]
译者:DIFENG[2]
校对 : Tiny 熊 [3]
在 Coinbase,我们希望可以创建一个开放的金融系统。我们坚信提高金融的自由度可以让世界更美好。去中心化金融,简称 DeFi 是一个开放,无界限并且可以程序化的金融,是提供金融自由度的一种方式。
智能合约
DeFi 是运行在去中心化网络上(例如以太坊 [4]),由智能合约(例如 USD 币:一种区块链上美元代币)驱动的。智能合约其实是很好理解的,Nick Szabo 是数字货币和加密学的先驱者,在 1997 年他 最早提出智能合约 并将其比喻为自动贩卖机。
自动贩卖机就如一个被植入自动化程序的合约,他有如下特点:
-
你按照显示的金额放入货币,机器会给你饮料;
-
你不按照显示的金额付款,你拿不到饮料;
-
如果你付了应付金额,但是机器没给你饮料,亦或是你在没付钱的情况下机器给了你饮料,这些都是违反自动贩卖机的规则。
自动贩卖机可以在无人干涉情况下,很好的履行他的合约精神。
现代智能合约 [5] 工作原理也是类似的,合约的条件是用可执行的代码来表达的。去中心化网络保证按要求执行,并且任何人都不能破坏规则或者篡改结果。因为网络会一字不差地执行代码,有瑕疵的智能合约会产生预想不到的后果。(“代码是条例”)
把握当下
很多人觉得在区块链上去搭建应用比较困难,认为只有高级玩家可以尝试。但是近几年出现来了很多工具,开发者界面,帮助编程能力一般的人去实现构建。
最近,DeFi[6] 生态呈现爆发式地增长。USDC 不到 2 年捕获的总价值达到 10 亿美元 [7],同时各种各样的 DeFi 服务在不到 3 年的时间,总价值超过 20 亿美金。当下可谓是 DeFi 发展的最佳时机。
下面的教程主要目的是介绍如何开发自己的 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 语法高亮。
在以太坊建立账户
在以太坊上做任何事情之前,你需要有一个帐户。账户通常被称为“钱包”,因为它们可以包含像 ETH 和 USDC 这样的数字资产。终端用户通常使用以太坊钱包应用,像 Coinbase 钱包 [19] 或 Metamask[20] 来创建钱包 , 但通过程序使用 ethers.js[21] 方式创建一个账户也很简单。
在 hide 目录下,创建一个新的 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 hide/createWallet.js Mnemonic: rabbit enforce proof always embrace tennis version reward scout shock license wingAddress: 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]
来源 : ropsten.etherscan.io
你可以看到现在余额是 0。保持该页面打开,并在另一个页面中打开 Ropsten Ethereum Faucet[30]。在第二个页面中,输入你的地址,然后点击“发送我(Send me)”按钮。完成后可能只需要几秒钟到一两分钟。稍后再次检查 Etherscan,你应该会看到新的余额为 1ETH 和转入交易。
通过编程获取 ETH 余额
连接以太坊网络
我们可以使用 Etherscan 查看余额,但是使用代码也可以很容易查看余额。在我们写代码之前,我们需要连接到以太坊网络。有许多方法可以实现,包括在自己的计算机上运行一个网络节点,但到目前为止,最快和最简单的方法是通过一个托管节点来实现,例如 INFURA[31] 或 Alchemy[32]。前往 INFURA[33],创建一个免费帐户并创建一个新项目来获取 API 密钥 (项目 ID)。
ℹ Go Ethereum (“geth”)[34] 和 Open Ethereum[35](之前被称为 Parity Ethereum)。这两个是最为广泛使用地节点软件。
通过代码查看 ETH 余额
首先,通过读取助记符进入到我们的账户中。在 hide 文件夹下,创建一个名为 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 hide/wallet.jsAddress: 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 hide/getBalance.jsAddress: 0xB3512cF013F71598F359bd5CA3f53C1F4260956aETH 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 发送到我们的帐户。
发起交易来调用智能合约
在以太坊的智能合约中有主要有两类方法:读写和只读。第一种方式可以修改区块链上的数据,而第二种仅仅是读取区块链上的数据,但是不能修改数据。 只读方法不用通过交易来调用,所以不会耗费 ETH, 除非是在读写方法中的一部分。读写方法是一定要通过交易来调用,所以一定会消耗 ETH。调用 gimmeSome 方法会改变 USDC 数量的改变,所以必须通过一次交易来完成。
调用智能合约的方法需要再多些步骤,但是也不复杂。第一,需要知道调用方法的完整接口,被称为函数签名或函数原型。我们看下 gimmeSome 方法的源码如下:
function gimmeSome() external
这是一个没有任何参数的方法,而且被标记为 external,表示只能从外部可以调用,不能被合约内的其他方法调用。这个对我们来说不影响,因为我们就是从外部调用。
在主链上的真实的 USDC 合约 [39] 是没有 gimmeSome 方法的
在 hide 文件夹下创建一个新文件,命名为 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 hide/getTestnetUSDC.js Address: 0xB3512cF013F71598F359bd5CA3f53C1F4260956aTransaction hash: 0xd8b4b06c19f5d1393f29b408fc0065d0774ec3b4d11d41be9fd72a8d84cb6208Transaction confirmed in block 8156350Gas used: 35121
好的,祝贺你通过代码的方式完成了第一次 ETH 的交易。在 Ropsten Etherscan[43] 查看下你的账户地址和交易 hash。你应该可以查看到,账户里有 10 个测试 USDC,ETH 的余额小于 1,因为支付了 gas 费用。
ℹ如果你在看 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,让我们通过代码检查余额来确认这一点。
我们修改下 hide 文件夹下的 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 位小数。
ℹ 你可以在这里 [46] 了解更多关于 Solidity 方法。
执行以下代码,你就可以看到 USDC 余额
*
$ node hide/getBalance.js Address: 0xB3512cF013F71598F359bd5CA3f53C1F4260956aETH Balance: 0.9961879USDC Balance: 10.0
ETH 和 USDC 转账
现在我们来看看怎么可以使用账户中的 ETH 和 USDC
使用 ETH
在 hide 文件夹下创建 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 hide/createWallet.js Mnemonic: napkin invite special reform cheese hunt refuse ketchup arena bag love cautionAddress: 0xDdAC089Fe56F0a9C70e6a04C74DCE52F86a91e13 $ node hide/transferETH.js 0xDdAC089Fe56F0a9C70e6a04C74DCE52F86a91e13 0.1 Address: 0xB3512cF013F71598F359bd5CA3f53C1F4260956aTransferring 0.1 ETH to 0xDdAC089Fe56F0a9C70e6a04C74DCE52F86a91e13...Transaction hash: 0xa9f159fa8a9509ec8f8afa8ebb1131c3952cb3b2526471605fd84e8be408cebfTransaction confirmed in block 8162896
你可以在 Etherscan[47] 看到结果,我们再来测试验证逻辑是有效的。
$ node hide/transferETH.js foo Address: 0xB3512cF013F71598F359bd5CA3f53C1F4260956aInvalid address: foo $ node hide/transferETH.js 0xDdAC089Fe56F0a9C70e6a04C74DCE52F86a91e13 0.1.2Address: 0xB3512cF013F71598F359bd5CA3f53C1F4260956aInvalid amount: 0.1.2 $ node hide/transferETH.js 0xDdAC089Fe56F0a9C70e6a04C74DCE52F86a91e13 -0.1 Address: 0xB3512cF013F71598F359bd5CA3f53C1F4260956aInvalid amount: -0.1 $ node hide/transferETH.js 0xDdAC089Fe56F0a9C70e6a04C74DCE52F86a91e13 100 Address: 0xB3512cF013F71598F359bd5CA3f53C1F4260956aInsufficient 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 hide/transferUSDC.js 0xDdAC089Fe56F0a9C70e6a04C74DCE52F86a91e13 1 Address: 0xB3512cF013F71598F359bd5CA3f53C1F4260956aTransferring 1.0 USDC to 0xDdAC089Fe56F0a9C70e6a04C74DCE52F86a91e13...Transaction hash: 0xc1b2157a83f29d6c04f960bc49e968a0cd2ef884761af7f95cc83880631fe4afTransaction confirmed in block 8162963
恭喜
在本教程中,你学习了如何生成钱包、查询余额、转移代币和调用智能合约。你可能觉得自己还不太了解区块链,不过你已经有足够的知识,去构建自己加密钱包应用程序。为了保持简单,我们一直在编写命令行脚本,那么是否可以尝试构建一个图形界面的网页呢 ?
在本教程系列的下一部分中,我们将从头开始用 solidity 编写智能合约,并学习如何构建自己的硬币,可与 USDC 交换。我们还将使用今天学到的技术来与我们构建的合约进行互动。请继续关注。
参考资料
[1]
登链翻译计划 : https://github.com/lbc-team/Pioneer
[2]
DIFENG: https://learnblockchain.cn/people/1234
[3]
Tiny 熊 : https://learnblockchain.cn/people/15
[4]
以太坊 : https://learnblockchain.cn/2017/11/20/whatiseth
[5]
智能合约 : https://learnblockchain.cn/2018/01/04/understanding-smart-contracts
[6]
DeFi: https://learnblockchain.cn/article/1185
[7]
USDC 不到 2 年捕获的总价值达到 10 亿美元 : https://medium.com/centre-blog/usdc-market-cap-exceeds-1-billion-fastest-growing-digital-dollar-stablecoin-to-do-so-c5ba314474ca
[8]
Javascript: https://en.wikipedia.org/wiki/Javascript
[9]
Solidity: https://solidity.readthedocs.io/
[10]
Ethereum: https://ethereum.org/
[11]
USDC: https://www.coinbase.com/usdc
[12]
Node.js v12.x: https://nodejs.org/
[13]
Ubuntu on WSL: https://ubuntu.com/wsl
[14]
这里 : https://treehouse.github.io/installing-guides/mac/nod-mac.html
[15]
这里 : https://docs.microsoft.com/en-us/windows/nodejs/setup-on-wsl2
[16]
Visual Studio Code: https://code.visualstudio.com/
[17]
Vim 的快捷键绑定方式 : https://xkcd.com/378/
[18]
预配置模板 : https://github.com/coinbasestablecoin/solid-tutorial]
[19]
Coinbase 钱包 : https://wallet.coinbase.com/
[20]
Metamask: https://metamask.io/
[21]
ethers.js: https://github.com/ethers-io/ethers.js/
[22]
椭圆曲线密码学 : https://en.wikipedia.org/wiki/Elliptic-curve_cryptography
[23]
BIP39: https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
[24]
BIP32: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
[25]
EIP55: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md
[26]
在本项目中 : https://github.com/petejkim/wallet.ts
[27]
gas: https://learnblockchain.cn/2019/06/11/gas-mean
[28]
Etherscan: https://ropsten.etherscan.io/
[29]
https://ropsten.etherscan.io/address/YOUR_ADDRESS: https://ropsten.etherscan.io/address/YOUR_ADDRESS
[30]
Ropsten Ethereum Faucet: https://faucet.ropsten.be/
[31]
INFURA: https://infura.io/
[32]
Alchemy: https://alchemyapi.io/
[33]
INFURA: https://infura.io/
[34]
Go Ethereum (“geth”): https://geth.ethereum.org/
[35]
Open Ethereum: https://github.com/openethereum/openethereum#readme
[36]
这里 : https://ethdocs.org/en/latest/ether.html
[37]
伪 USDC 智能合约 : https://ropsten.etherscan.io/token/0x68ec573C119826db2eaEA1Efbfc2970cDaC869c4
[38]
合约代码栏目 : https://ropsten.etherscan.io/address/0x68ec573C119826db2eaEA1Efbfc2970cDaC869c4#code
[39]
真实的 USDC 合约 : https://etherscan.io/address/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48
[40]
0x68ec⋯69c4: https://ropsten.etherscan.io/address/0x68ec573C119826db2eaEA1Efbfc2970cDaC869c4
[41]
Promise: https://developer.mozilla.org/en-US/docs/Web/Javascript/Reference/Global_Objects/Promise
[42]
await: https://developer.mozilla.org/en-US/docs/Web/Javascript/Reference/Operators/await
[43]
Ropsten Etherscan: https://ropsten.etherscan.io/
[44]
Ethereum 合约 ABI 规范 : https://solidity.readthedocs.io/en/v0.6.10/abi-spec.html
[45]
ERC20 规范 : https://eips.ethereum.org/EIPS/eip-20
[46]
这里 : https://solidity.readthedocs.io/en/v0.6.11/contracts.html#functions
[47]
Etherscan: https://ropsten.etherscan.io/
[48]
Pete Kim: https://twitter.com/petejkim
来源链接:mp.weixin.qq.com
- 免责声明
- 世链财经作为开放的信息发布平台,所有资讯仅代表作者个人观点,与世链财经无关。如文章、图片、音频或视频出现侵权、违规及其他不当言论,请提供相关材料,发送到:2785592653@qq.com。
- 风险提示:本站所提供的资讯不代表任何投资暗示。投资有风险,入市须谨慎。
- 世链粉丝群:提供最新热点新闻,空投糖果、红包等福利,微信:juu3644。