paint-brush
dRPC API キーとエンドポイントを的使用して Ethereum ネットワークにスマート コントラクトをデプロイする的办法 に@ileolami
2,950 測定値
2,950 測定値

dRPC API キーとエンドポイントを使用して Ethereum ネットワークにスマート コントラクトをデプロイする方法

Ileolami23m2024/09/11
Read on Terminal Reader

長すぎる; 読むには

この記事では、dRPC エンドポイントと API キーを使用して、コーヒー支払いスマート コントラクトを記述、コンパイル、テストし、Ethereum Sepolia テストネットにデプロイします。機能には、コーヒーの支払い、コーヒーの価格の確認、販売されたコーヒーの合計数と合計収益の取得が含まれます。
featured image - dRPC API キーとエンドポイントを使用して Ethereum ネットワークにスマート コントラクトをデプロイする方法
Ileolami HackerNoon profile picture
0-item

導入

Web3 DApp 開発の技術スタックを明白することで、Web3 dApp 開発のコア技術スタック、dApp 開発における RPC の役割、dRPC を便用してアカウントを制作し、API キー、エンドポイント、エンドポイント介绍を转为し、dRPC アカウントに資金を追加し、残高を確認する形式を学習しているはずです。


スマート コントラクトの展開における dRPC の役割は、Ethereum ノードのセットアップ プロセスを簡素化し、開発者が 1 行のコードで簡単に进行操作および展開できるようにすることです。


この記事では、dRPC エンドポイントと API キーを用して、コーヒー支払いスマート コントラクトを記述、コンパイル、テストし、Ethereum Sepolia Testnet にデプロイします。


機能は次のとおりです:
  1. コーヒー代金
  2. コーヒーの価格の見直し
  3. 販売されたコーヒーの総数と総収益の取得


さあ、手を汚してみましょう。

前提条件

  1. ウォレット(例:Metamask)を用意します。
  2. コードエディター。
  3. 選択した Js ライブラリまたはフレームワーク (React.js、Next.js など) がすでにインストールされています。
  4. 水の入った瓶。

必要な技術とツール

  1. 堅実性。
  2. Vite.js(Typescript) を使用した React.js
  3. ヘルメット。
  4. Web3.js です。
  5. ドテンヴ。
  6. dRPC API キーとエンドポイント。
  7. アカウントの秘密鍵。
  8. メタマスク

コーヒー決済スマートコントラクトの作成

  1. ルート ディレクトリの下にフォルダーを作成し、 contractsという名前を付けます。


  2. contractsフォルダーの下にファイルを作成し、 coffee.solという名前を付けます。

    ファイル ディレクトリが表示され、その中に「contracts」という名前のフォルダーと「coffee.sol」という名前のファイルがあります。

スマート コントラクトを記述するには、Solidity を使用します。Solidity ファイルは、Solidity ソース コードの標準ファイル拡張子であるため、 .sol拡張子で命名されます。


  1. 次のソース コードをcoffee.solに追加します。

     // SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; contract Coffee { uint256 public constant coffeePrice = 0.0002 ether; uint256 public totalCoffeesSold; uint256 public totalEtherReceived; // Custom error definitions error QuantityMustBeGreaterThanZero(); error InsufficientEtherSent(uint256 required, uint256 sent); error DirectEtherTransferNotAllowed(); // Event to log coffee purchases event CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost); // Function to buy coffee function buyCoffee(uint256 quantity) external payable { if (quantity <= 0) { revert QuantityMustBeGreaterThanZero(); } uint256 totalCost = coffeePrice * quantity; if (msg.value > totalCost) { revert InsufficientEtherSent(totalCost, msg.value); } // Update the total coffees sold and total ether received totalCoffeesSold += quantity; totalEtherReceived += totalCost; console.log("Total ether received updated:", totalEtherReceived); console.log("Total coffee sold updated:", totalCoffeesSold); // Emit the purchase event emit CoffeePurchased(msg.sender, quantity, totalCost); // Refund excess Ether sent if (msg.value > totalCost) { uint256 refundAmount = msg.value - totalCost; payable(msg.sender).transfer(refundAmount); } } // Fallback function to handle Ether sent directly to the contract receive() external payable { revert DirectEtherTransferNotAllowed(); } // Public view functions to get totals function getTotalCoffeesSold() external view returns (uint256) { console.log("getTotalCoffeesSold :", totalCoffeesSold); return totalCoffeesSold; } function getTotalEtherReceived() external view returns (uint256) { console.log("getTotalEtherReceived :", totalEtherReceived); return totalEtherReceived; } }

プラグマ

  • //SPDX-License-Identifier: MIT : このライセンス識別子は、コードがライセンスに基づいてライセンスされていることを示します。


  • pragma solidity >=0.8.0 <0.9.0; : コードが 0.8.0 (含む) から 0.9.0 (含まない) までの Solidity バージョン用に記述されていることを指定します。

状態変数

 uint256 public constant coffeePrice = 0.0002 ether; uint256 public totalCoffeesSold; uint256 public totalEtherReceived;
  • coffeePrice : 0.0002 etherの定数値として設定します。
  • totalCoffeesSold : 販売されたコーヒーの数を追跡します。
  • totalEtherReceived : 契約によって受信された Ether の合計を追跡します。

カスタムエラー

Solidity のカスタム エラーは、プログラミング言語によって提供されるデフォルトのエラー メッセージではなく、特定のユース ケースに合わせて調整されたエラー メッセージです。ユーザー エクスペリエンスの向上に役立つだけでなく、スマート コントラクトのデバッグや保守にも役立ちます。


Solidity でカスタム エラーを定義するには、次の構文を用到できます。
  • error : このキーワードはカスタムエラーを定義するために使用されます
  • 一意の名前: エラーには一意の名前が必要です
  • パラメータ: エラー メッセージに特定の詳細またはパラメータを含める場合は、エラー名の後に括弧で囲んで追加できます。
 error QuantityMustBeGreaterThanZero(); error InsufficientEtherSent(uint256 required, uint256 sent); error DirectEtherTransferNotAllowed();
  • QuantityMustBeGreaterThanZero() : 数量がゼロより大きいことを確認します。
  • InsufficientEtherSent(uint256 required, uint256 sent) : 送信された Ether が十分であることを確認します。
  • DirectEtherTransferNotAllowed() : 契約への直接 Ether 転送を防止します。

イベント

イベントは、発行時にトランザクション ログに渡された引数を存放するコントラクトの一部电影です。イベントは一般性、EVM のログ機能を安全食用的して、呼び出し元のアプリケーションにコントラクトの現在の状態を消息消息するために安全食用的されます。イベントは、コントラクトに加えられた変更をアプリケーションに消息消息し、関連するロジックを実行するために安全食用的できます。
 event CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost);
  • CoffeePurchased(address indexed buyer, uint256 quantity, uint256 totalCost) : コーヒーの購入を記録します。

機能

関数は、特定のタスクを実行する自己完結型のコード モジュールです。同じコードを書き直すという冗長性が排除されます。代わりに、開発者は必要に応じてプログラム内の関数を呼び出すことができます。

 function buyCoffee(uint256 quantity) external payable { if (quantity <= 0) { revert QuantityMustBeGreaterThanZero(); } uint256 totalCost = coffeePrice * quantity; if (msg.value > totalCost) { revert InsufficientEtherSent(totalCost, msg.value); } // Update the total coffees sold and total ether received totalCoffeesSold += quantity; totalEtherReceived += totalCost; console.log("Total ether received updated:", totalEtherReceived); console.log("Total coffee sold updated:", totalCoffeesSold); // Emit the purchase event emit CoffeePurchased(msg.sender, quantity, totalCost); // Refund excess Ether sent if (msg.value > totalCost) { uint256 refundAmount = msg.value - totalCost; payable(msg.sender).transfer(refundAmount); } } receive() external payable { revert DirectEtherTransferNotAllowed(); } function getTotalCoffeesSold() external view returns (uint256) { console.log("getTotalCoffeesSold :", totalCoffeesSold); return totalCoffeesSold; } function getTotalEtherReceived() external view returns (uint256) { console.log("getTotalEtherReceived :", totalEtherReceived); return totalEtherReceived; }
  • buyCoffee(uint256 quantity) external payable : コーヒーの購入を処理し、次の操作を実行します。
    • 数量が有効かどうかを確認します。
    • 合計コストを計算します。
    • 十分な Ether が送信されるようにします。
    • 状態変数を更新します。
    • 購入イベントを発行します。
    • 余剰のEtherを返金します。
  • receive() external payable : 誰かが契約アドレスに直接資金を送金した場合に、直接の Ether 転送を元に戻します。
  • getTotalCoffeesSold() external view returns (uint256) : 販売されたコーヒーの合計数を返します。
  • getTotalEtherReceived() external view returns (uint256) : 受信した Ether の合計を返します。

コーヒー決済スマートコントラクトのコンパイル

ここでは、Hardhat を食用してスマート コントラクトをコンパイルします。


  1. 次のコマンド プロンプトを使用して Hardhat をインストールします。

     npm install --save-dev hardhat


    インストールが出色すると、低于の応答が返されます。

    ハードハットのインストールの出力を表示する端末。


  2. このコマンド プロンプトを使用してハードハットを初期化するのと同じディレクトリで、次の操作を実行します。

     npx hardhat init


  3. 下矢印ボタンを使用して「 Create a Javascript projectを選択し、Enter キーを押します。

  4. ルートフォルダにインストールするにはEnterキーを押してください

  5. キーボードのyを使用して、 @nomicfoundation/hardhat-toolbox依存関係を含むすべてのプロンプトを受け入れます。

  6. 下記の応答は初期化に成功したことを示しています

いくつかの新しいフォルダーとファイルがプロジェクトに追加されたことがわかります。たとえば、 Lock.sol iginition/modules test/Lock.js hardhat.config.cjsなどです。これらについては心配しないでください。


唯一役に立つのは、 iginition/moduleshardhat.config.cjsです。これらが何に使われるかは後でわかります。contractsフォルダーの下のLock.solcontracts iginition/modulesフォルダーの下のLock.jsは削除してもかまいません。


  1. 次のコマンド プロンプトを使用して契約をコンパイルします。

     npx hardhat compile 

  1. 次のような追加のフォルダーとファイルが表示されます。

  1. Coffee.jsonファイル内には、スマート コントラクトと対話するときに呼び出す JSON 形式の ABI コードがあります。
 { "_format": "hh-sol-artifact-1", "contractName": "Coffee", "sourceName": "contracts/coffee.sol", "abi": [ { "inputs": [], "name": "DirectEtherTransferNotAllowed", "type": "error" }, { "inputs": [ { "internalType": "uint256", "name": "required", "type": "uint256" }, { "internalType": "uint256", "name": "sent", "type": "uint256" } ], "name": "InsufficientEtherSent", "type": "error" }, { "inputs": [], "name": "QuantityMustBeGreaterThanZero", "type": "error" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "buyer", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "quantity", "type": "uint256" }, { "indexed": false, "internalType": "uint256", "name": "totalCost", "type": "uint256" } ], "name": "CoffeePurchased", "type": "event" }, { "inputs": [ { "internalType": "uint256", "name": "quantity", "type": "uint256" } ], "name": "buyCoffee", "outputs": [], "stateMutability": "payable", "type": "function" }, { "inputs": [], "name": "coffeePrice", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getTotalCoffeesSold", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getTotalEtherReceived", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "totalCoffeesSold", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "totalEtherReceived", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "stateMutability": "payable", "type": "receive" } ], "bytecode": "", "deployedBytecode": "", "linkReferences": {}, "deployedLinkReferences": {} }

スマートコントラクトのテスト

スマート コントラクトの構築中に自動テスト スクリプトを記述することは相对に关键であり、強く推奨されます。これは 2 维度認証 (2FA) のように機能し、ライブ ネットワークに展開する前にスマート コントラクトが看好どおりに動作することを確認します。


testフォルダーの下に新しいファイルを作成し、 Coffee.という名前を付けます。ファイル内に、以下のコードを貼り付けます。

 const { loadFixture } = require("@nomicfoundation/hardhat-toolbox/network-helpers.js"); const { expect } = require("chai"); const pkg = require("hardhat"); const ABI = require('../artifacts/contracts/coffee.sol/Coffee.json'); const { web3 } = pkg; describe("Coffee Contract", function () { // Fixture to deploy the Coffee contract async function deployCoffeeFixture() { const coffeeContract = new web3.eth.Contract(ABI.abi); coffeeContract.handleRevert = true; const [deployer, buyer] = await web3.eth.getAccounts(); const rawContract = coffeeContract.deploy({ data: ABI.bytecode, }); // Estimate gas for the deployment const estimateGas = await rawContract.estimateGas({ from: deployer }); // Deploy the contract const coffee = await rawContract.send({ from: deployer, gas: estimateGas.toString(), gasPrice: "", }); console.log("Coffee contract deployed to: ", coffee.options.address); return { coffee, deployer, buyer, rawContract }; } describe("Deployment", function () { // Test to check initial values after deployment it("Should set the initial values correctly", async function () { const { coffee } = await loadFixture(deployCoffeeFixture); const totalCoffeesSold = await coffee.methods.totalCoffeesSold().call(); const totalEtherReceived = await coffee.methods.totalEtherReceived().call(); expect(totalCoffeesSold).to.equal("0"); expect(totalEtherReceived).to.equal("0"); }); }); describe("Buying Coffee", function () { // Test to check coffee purchase and event emission it("Should purchase coffee and emit an event", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); const quantity = 3; const totalCost = web3.utils.toWei("0.0006", "ether"); // Buyer purchases coffee const receipt = await coffee.methods.buyCoffee(quantity).send({ from: buyer, value: totalCost }); // Check event const event = receipt.events.CoffeePurchased; expect(event).to.exist; expect(event.returnValues.buyer).to.equal(buyer); expect(event.returnValues.quantity).to.equal(String(quantity)); expect(event.returnValues.totalCost).to.equal(totalCost); }); // Test to check revert when quantity is zero it("Should revert if the quantity is zero", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); expect( coffee.methods.buyCoffee(0).send({ from: buyer, value: web3.utils.toWei("0.0002", "ether") }) ).to.be.revertedWith("QuantityMustBeGreaterThanZero"); }); // Test to check if totalCoffeesSold and totalEtherReceived are updated correctly it("Should update totalCoffeesSold and totalEtherReceived correctly", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); const quantity = 5; const totalCost = web3.utils.toWei("0.001", "ether"); await coffee.methods.buyCoffee(quantity).send({ from: buyer, value: totalCost }); const totalCoffeesSold = await coffee.methods.totalCoffeesSold().call(); const totalEtherReceived = await coffee.methods.totalEtherReceived().call(); expect(totalCoffeesSold).to.equal(String(quantity)); expect(totalEtherReceived).to.equal(totalCost); }); }); describe("Fallback function", function () { // Test to check revert when ether is sent directly to the contract it("Should revert if ether is sent directly to the contract", async function () { const { coffee, buyer } = await loadFixture(deployCoffeeFixture); expect( web3.eth.sendTransaction({ from: buyer, to: coffee.options.address, value: web3.utils.toWei("0.001", "ether"), }) ).to.be.revertedWith("DirectEtherTransferNotAllowed"); }); }); });
このコードは、コーヒー スマート コントラクトの機能をテストします。デプロイメント、コーヒーの購入、コントラクトへの随便 Ether 転送の処理のテストが含まれます。


内訳は次のとおりです。

フィクスチャ関数: deployCoffeeFixture

 async function deployCoffeeFixture() {  const coffeeContract = new web3.eth.Contract(ABI.abi);  coffeeContract.handleRevert = true;  const [deployer, buyer] = await web3.eth.getAccounts();  const rawContract = coffeeContract.deploy({    data: ABI.bytecode,  });  const estimateGas = await rawContract.estimateGas({ from: deployer });  const coffee = await rawContract.send({    from: deployer,    gas: estimateGas.toString(),  gasPrice: "",  });  console.log("Coffee contract deployed to: ", coffee.options.address);  return { coffee, deployer, buyer, rawContract }; }
  • Coffee コントラクトをデプロイします。新しいコントラクト インスタンスを作成し、デプロイヤーのアカウントを使用してデプロイします。
  • ガスの見積もり: 展開に必要なガスを見積もります。
  • 戻り値: デプロイされた契約インスタンス、デプロイヤー、および購入者のアカウント。

展開テスト

describe("Deployment", function () {  it("Should set the initial values correctly", async function () {    const { coffee } = await loadFixture(deployCoffeeFixture);    const totalCoffeesSold = await coffee.methods.totalCoffeesSold().call();    const totalEtherReceived = await coffee.methods.totalEtherReceived().call();    expect(totalCoffeesSold).to.equal("0");    expect(totalEtherReceived).to.equal("0");  }); });
  • 初期値を確認します。デプロイ後にtotalCoffeesSoldtotalEtherReceivedがゼロに設定されていることを確認します。

コーヒーテストの購入

describe("Buying Coffee", function () {  it("Should purchase coffee and emit an event", async function () {    const { coffee, buyer } = await loadFixture(deployCoffeeFixture);    const quantity = 3;    const totalCost = web3.utils.toWei("0.0006", "ether");    const receipt = await coffee.methods.buyCoffee(quantity).send({ from: buyer, value: totalCost });    const event = receipt.events.CoffeePurchased;    expect(event).to.exist;    expect(event.returnValues.buyer).to.equal(buyer);    expect(event.returnValues.quantity).to.equal(String(quantity));    expect(event.returnValues.totalCost).to.equal(totalCost);  });  it("Should revert if the quantity is zero", async function () {    const { coffee, buyer } = await loadFixture(deployCoffeeFixture);    expect(      coffee.methods.buyCoffee(0).send({ from: buyer, value: web3.utils.toWei("0.0002", "ether") })    ).to.be.revertedWith("QuantityMustBeGreaterThanZero");  });  it("Should update totalCoffeesSold and totalEtherReceived correctly", async function () {    const { coffee, buyer } = await loadFixture(deployCoffeeFixture);    const quantity = 5;    const totalCost = web3.utils.toWei("0.001", "ether");    await coffee.methods.buyCoffee(quantity).send({ from: buyer, value: totalCost });    const totalCoffeesSold = await coffee.methods.totalCoffeesSold().call();    const totalEtherReceived = await coffee.methods.totalEtherReceived().call();    expect(totalCoffeesSold).to.equal(String(quantity));    expect(totalEtherReceived).to.equal(totalCost);  }); });
  • コーヒーを購入してイベントを発行する: コーヒーを購入すると状態が更新され、 CoffeePurchasedイベントが発行されることをテストします。
  • 数量がゼロの場合に元に戻す: 数量がゼロの場合にトランザクションが元に戻ることを保証します。
  • 状態を正しく更新する: 購入後にtotalCoffeesSoldtotalEtherReceivedが正しく更新されていることを確認します。

フォールバック機能テスト

describe("Fallback function", function () {  it("Should revert if ether is sent directly to the contract", async function () {    const { coffee, buyer } = await loadFixture(deployCoffeeFixture);    expect(      web3.eth.sendTransaction({        from: buyer,        to: coffee.options.address,        value: web3.utils.toWei("0.001", "ether"),      })    ).to.be.revertedWith("DirectEtherTransferNotAllowed");  }); });
  • 直接 Ether 転送を元に戻す: Ether をコントラクトに直接送信すると (関数を呼び出さずに)、トランザクションが元に戻ることを保証します。

スマートコントラクトのテスト

テスト スクリプトを記述したら、次の运行を行います
  1. Hardhat Network でコントラクトとテストを実行する場合、Solidity コードからconsole.log()を呼び出して、ログ メッセージとコントラクト変数を出力できます。これを使用するには、次のようにコントラクト コードにhardhat/console.solをインポートする必要があります
 //SPDX-License-Identifier: MIT pragma solidity >=0.8.0 <0.9.0; import "hardhat/console.sol"; contract Coffee { //... }
  1. 契約をテストするには、ターミナルで次のコマンドを実行します。

     npx hardhat test


  2. 以下のような出力が得られるはずです:

これは、スマート コントラクトが期许どおりに機能していることを示しています。

npx hardhat testを実行すると、スマート コントラクトが自動的にコンパイルされ、テストされます。ぜひ試してみて、コメント セクションでお知らせください。

スマートコントラクトのデプロイ

ここでは、スマート コントラクトをテストネットを运用すると、大きなコストをかけずに、Ethereum メインネットを模倣した環境でスマート コントラクトをテストできます。dApp の機能に問題がなければ、Ethereum メインネットに再デプロイできます。


  1. dotenv パッケージとこれらの依存関係をインストールします。

     npm install dotenv npm install --save-dev @nomicfoundation/hardhat-web3-v4 'web3@4'
    これにより、Web3.Js と Dotenv が 'node_modules' フォルダーに含まれ、プロジェクトに追加されます。


  2. それらをhardhat.config.cjsファイルにインポートします。

     require('dotenv').config(); require("@nomicfoundation/hardhat-toolbox"); require("@nomicfoundation/hardhat-web3-v4"); const HardhatUserConfig = require("hardhat/config"); module.exports = { solidity: "0.8.24", } };
  3. ルート フォルダーに.envファイルを作成します


  4. MetaMask ウォレットからアカウントの秘密鍵と dRPC API キーを取得します。


  5. それらを.envファイルに保存します

     DRPC_API_KEY=your_drpc_api_key PRIVATE_KEY=your_wallet_private_key


  6. hardhat.config.cjsファイルを更新して、Sepolia テストネット構成を含めます。

     require('dotenv').config(); require("@nomicfoundation/hardhat-toolbox"); require("@nomicfoundation/hardhat-web3-v4"); const HardhatUserConfig = require("hardhat/config"); const dRPC_API_KEY = process.env.VITE_dRPC_API_KEY; const PRIVATE_KEY = process.env.VITE_PRIVATE_KEY; module.exports = { solidity: "0.8.24", networks: { sepolia: { url: `//lb.drpc.org/ogrpc?network=sepolia&dkey=${dRPC_API_KEY}`, accounts: [`0x${PRIVATE_KEY}`], } } };
  7. ignition/moduleフォルダーの下に新しいスクリプト ファイルを作成し deploy.cjsという名前を付けます。次のコードを追加して、スマート コントラクトをデプロイします。

     const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules"); const CoffeeModule = buildModule("CoffeeModule", (m) => { const coffee = m.contract("Coffee"); return { coffee }; }); module.exports = CoffeeModule;
  8. ターミナルで次のコマンドを実行して、スマート コントラクトをデプロイします。

     npx hardhat ignition deploy ./ignition/modules/deploy.cjs --network sepolia


    コマンド プロンプトを実行すると、 Confirm deploy to network sepolia (11155111)? (y/n)ので、 yと入力します。デプロイが成功すると、ターミナルにデプロイされたスマート コントラクトのアドレスが表示されます。

    また、 deployed_addresses.jsonファイル内のコントラクト アドレスにアクセスすることもできます。

    Vite-Project ファイル エクスプローラーと、開かれた deployed_addresses.json ファイルのスクリーンショット。おめでとうございます。スマート コントラクトを Sepolia テストネットに正常にデプロイしました。🎉

結論

この記事では、Hardhat CLI を采用して支払いスマート コントラクトを做成し、テスト、コンパイル、およびデプロイする手段を説明しました。


次の記事では、この dApp のフロントエンドの構築措施を学びます。この UI は次の基本要素で構成されます。

  1. 購入したコーヒーの数を入力するフィールド。
  2. 支払い取引を開始し、アカウントから引き落とすボタン。
  3. 購入したコーヒーの合計と受け取った金額をイーサリアムと米ドルで表示します
  4. コーヒー自体の価格(イーサリアムと米ドルの両方)。

参照


← 前の記事次の記事 →

바카라사이트 바카라사이트 온라인바카라