最近,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]方式建立一個賬戶也很簡單。
在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]
來源:* [*ropsten.etherscan.io*](https://ropsten.etherscan.io/ "*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餘額
首先,透過讀取助記符進入到我們的賬戶中。在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傳送到我們的帳戶。
發起交易來呼叫智慧合約
在以太坊的智慧合約中有主要有兩類方法:讀寫和只讀。第一種方式可以修改區塊鏈上的資料,而第二種僅僅是讀取區塊鏈上的資料,但是不能修改資料。 只讀方法不用透過交易來呼叫,所以不會耗費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費用。
如果你在看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位小數。
你可以在這裡[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
你可以在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
恭喜
在本教程中,你學習瞭如何生成錢包、查詢餘額、轉移代幣和呼叫智慧合約。你可能覺得自己還不太瞭解區塊鏈,不過你已經有足夠的知識,去構建自己加密錢包應用程式。為了保持簡單,我們一直在編寫命令列指令碼,那麼是否可以嘗試構建一個圖形介面的網頁呢?
在本教程系列的下一部分中,我們將從頭開始用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&