14

Nethereum.Unity开发教程

 2 years ago
source link: http://blog.hubwiz.com/2022/01/28/nethereum-unity-tutorial/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Nethereum.Unity 库是一个特定于 Nethereum 的 Unity 库和 API,它支持 UnityWebRequest 使用 RPC over Http 与 Ethereum 交互。Nethereum.Unity 库是唯一支持在 Unity 中使用协程时使用 IEnumerator 和 yield 的库。

flappy.png

用自己熟悉的语言学习 以太坊开发Java | Php | Python | .Net / C# | Golang | Node.JS | Flutter / Dart

1、Nethereum.Unity简介

如果想在 net461 / netstandard 中使用 async / await 和 Tasks,只要你的环境不需要使用 UnityWebRequest 而不是 HttpRequest,也可以使用与“vanilla” Nethereum 相同的方式。 (Webgl 需要使用 UnityWebRequest)

Nethereum 还为 net351 和 net461 框架构建提供了 AoT 库。 所有“dll”都可以从 Nethereum github 下载。

flappy eth 是 Unity3d 的“flappy”示例,使用 Nethereum 作为 webgl dapp 游戏转换为 与 Ethereum、Infura 和 Metamask 交互。主要集成组件的源代码可以在这里找到, 你也可以在这里尝试游戏。

2、Unity3dSimpleSample示例简介

Unity3dSimpleSample是使用 Net472(现在从 Net461 升级)进行 Unity3d 开发的简单示例,使用的 Unity 版本是 2020.3.15f LTS。 示例中包含资产文件夹中的所有 DLL,你可能不需要其中一些代码,直接删除它们(如 Besu、Geth、HdWallet、NBitcoin 等)就可以了,具体 取决于你的需要。

示例代码演示了如何使用Nethereum.Unity实现以下功能:

  • 在异步和协程中使用 Unity.UI 将当前 BlockNumber 输出到日志
  • 使用 Unity.UI 和协程进行以太传输
  • ETH转账时使用 1559 Suggestion 策略或 Legacy 模式
  • 智能合约部署(ERC20)、交易(Transfer)和查询(Balance)
  • WebGL 仅支持协程 UnityWebRequest。如果构建 WebGL版本发生问题,请取消选中 Development Build。
  • 为了支持 WebGL 和 AOT,此示例使用 Net472AOT dll 和自定义 Json.Net Unity
  • 请记住删除 Nethereum 发布包的 System.HttpClient 和 UnityEngine

下面是桌面版的截图:

desktop.png

下面是WebGL版的截图:

webgl.png

要运行本地区块链,可以使用预配置的测试链

3、异步查询区块号 — 非协程方式

下面的代码展示了如何用Nethereum.Unity异步查询区块号:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using Nethereum.ABI.FunctionEncoding.Attributes;
using UnityEngine;
using UnityEngine.UI;
using Nethereum.Contracts;
using Nethereum.Web3;


public class GetLatestBlockVanillaNethereum : MonoBehaviour {

private static bool TrustCertificate(object sender, X509Certificate x509Certificate, X509Chain x509Chain, SslPolicyErrors sslPolicyErrors)
{
// all certificates are accepted
return true;
}

public string Url = "https://mainnet.infura.io";

public InputField ResultBlockNumber;
public InputField InputUrl;

// Use this for initialization
void Start()
{
InputUrl.text = Url;
}

public async void GetBlockNumber()
{
Url = InputUrl.text;
//This is to workaround issue with certificates https://forum.unity.com/threads/how-to-allow-self-signed-certificate.522183/
//Uncomment if needed
// ServicePointManager.ServerCertificateValidationCallback = TrustCertificate;
var web3 = new Web3(Url);

var blockNumber = await web3.Eth.Blocks.GetBlockNumber.SendRequestAsync();
ResultBlockNumber.text = blockNumber.Value.ToString();
}
}

4、异步查询区块号 — 协程方式

下面的代码展示了如何用Nethereum.Unity以协程方式异步查询区块号:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Nethereum.JsonRpc.UnityClient;
using Nethereum.RPC.Eth.DTOs;
using Nethereum.Util;

public class GetLatestBlockCoroutine : MonoBehaviour
{
public string Url = "https://mainnet.infura.io";

public InputField ResultBlockNumber;
public InputField InputUrl;

// Use this for initialization
void Start()
{
InputUrl.text = Url;
}

public void GetBlockNumberRequest()
{
StartCoroutine(GetBlockNumber());
}

public IEnumerator GetBlockNumber()
{
Url = InputUrl.text;

var blockNumberRequest = new EthBlockNumberUnityRequest(Url);

yield return blockNumberRequest.SendRequest();

ResultBlockNumber.text = blockNumberRequest.Result.Value.ToString();
}
}

5、ETH转账的简单实现

为了支持ETH转账, Nethereum 提供了一个特定的 Unity 请求,EthTransferUnityRequest.

EthTransferUnityRequest 使用我们的以太坊客户端的“url”、能够签署交易的私钥和我们的帐户 地址(与私钥相同)进行实例化。

var  url  =  " http://localhost:8545 " ;
var privateKey = " 0xb5b1870957d373ef0eeffecc6e4812c0fd08f554b37b233526acc331bf1544f7 " ;
var ethTransfer = new EthTransferUnityRequest ( url , privateKey , " YOURCHAINID " );

一旦我们的请求被实例化,就可以使用传统模式启动传输,下面的代码设置 2 Gwei 作为gas价格:

var receivingAddress = "0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe";
yield return ethTransfer.TransferEther(receivingAddress, 1.1m, 2);

在这里,我们指定了接收地址、发送量和可选的 gas 价格。该请求将自动将 gas 价格转换为 Wei。

我们可以在之后验证是否有任何异常,如下所示:

if (ethTransfer.Exception != null)
{
Debug.Log(ethTransfer.Exception.Message);
yield break;
}

如果没有发生错误,我们可以每 2 秒从请求和轮询中检索交易哈希以等待交易确认:

var transactionHash = ethTransfer.Result;
//create a poll to get the receipt when mined
var transactionReceiptPolling = new TransactionReceiptPollingRequest(url);
//checking every 2 seconds for the receipt
yield return transactionReceiptPolling.PollForReceipt(transactionHash, 2);

最后,我们可以使用EthGetBalanceUnityRequest检查收款账户的余额。请注意,我们在执行 请求时声明想要最新块记录的余额。

var balanceRequest = new EthGetBalanceUnityRequest(url);
yield return balanceRequest.SendRequest(receivingAddress, BlockParameter.CreateLatest());

可以使用默认的 Wei UnitConvertor 将 Wei 中的结果转换为 Eth:

Debug.Log("Balance of account:" + UnitConversion.Convert.FromWei(balanceRequest.Result.Value));

6、EIP 1559 使用建议

以下是使用协程、TimePreference、MedianFeeHistory 或 LegacyMode 提供的费用建议策略以使用 旧模式或与其他链一起使用的一些示例。

要使用 LegacyMode,你必须提供 GasPrice,或者可以通过设置UseLegacyAsDefault为 true 来强制 LegacyMode:

if (feeStrategy == FeeStrategy.TimePreference)
{
Debug.Log("Time Preference");
var timePreferenceFeeSuggestion = new TimePreferenceFeeSuggestionUnityRequestStrategy(Url);

yield return timePreferenceFeeSuggestion.SuggestFees();

if (timePreferenceFeeSuggestion.Exception != null)
{
Debug.Log(timePreferenceFeeSuggestion.Exception.Message);
yield break;
}

//lets get the first one so it is higher priority
Debug.Log(timePreferenceFeeSuggestion.Result.Length);
if (timePreferenceFeeSuggestion.Result.Length > 0)
{
Debug.Log(timePreferenceFeeSuggestion.Result[0].MaxFeePerGas);
Debug.Log(timePreferenceFeeSuggestion.Result[0].MaxPriorityFeePerGas);
}
var fee = timePreferenceFeeSuggestion.Result[0];

yield return ethTransfer.TransferEther(receivingAddress, Amount, fee.MaxPriorityFeePerGas.Value, fee.MaxFeePerGas.Value);
if (ethTransfer.Exception != null)
{
Debug.Log(ethTransfer.Exception.Message);
yield break;
}
}


if(feeStrategy == FeeStrategy.MedianFeeHistory)
{
Debug.Log("MedianFeeHistory mode");
var medianPriorityFeeStrategy = new MedianPriorityFeeHistorySuggestionUnityRequestStrategy(Url);

yield return medianPriorityFeeStrategy.SuggestFee();

if (medianPriorityFeeStrategy.Exception != null)
{
Debug.Log(medianPriorityFeeStrategy.Exception.Message);
yield break;
}

Debug.Log(medianPriorityFeeStrategy.Result.MaxFeePerGas);
Debug.Log(medianPriorityFeeStrategy.Result.MaxPriorityFeePerGas);

var fee = medianPriorityFeeStrategy.Result;

yield return ethTransfer.TransferEther(receivingAddress, Amount, fee.MaxPriorityFeePerGas.Value, fee.MaxFeePerGas.Value);
if (ethTransfer.Exception != null)
{
Debug.Log(ethTransfer.Exception.Message);
yield break;
}
}

if (feeStrategy == FeeStrategy.Legacy)
{
Debug.Log("Legacy mode");
//I am forcing the legacy mode but also I am including the gas price
ethTransfer.UseLegacyAsDefault = true;

yield return ethTransfer.TransferEther(receivingAddress, Amount, GasPriceGwei);

if (ethTransfer.Exception != null)
{
Debug.Log(ethTransfer.Exception.Message);
yield break;
}

}

7、智能合约定义声明

在游戏中实现智能合约支持的第一步是包含我们的智能合约定义,这可以是使用 vscode solidity 扩展 或控制台代码生成工具生成的代码:

//Deployment contract object definition
public partial class EIP20Deployment : EIP20DeploymentBase
{
public EIP20Deployment() : base(BYTECODE) { }
public EIP20Deployment(string byteCode) : base(byteCode) { }
}

public class EIP20DeploymentBase : ContractDeploymentMessage
{
public static string BYTECODE = "608060405234801561001057600080fd5b506040516107843803806107848339810160409081528151602080840151838501516060860151336000908152808552959095208490556002849055908501805193959094919391019161006991600391860190610096565b506004805460ff191660ff8416179055805161008c906005906020840190610096565b5050505050610131565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106100d757805160ff1916838001178555610104565b82800160010185558215610104579182015b828111156101045782518255916020019190600101906100e9565b50610110929150610114565b5090565b61012e91905b80821115610110576000815560010161011a565b90565b610644806101406000396000f3006080604052600436106100ae5763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166306fdde0381146100b3578063095ea7b31461013d57806318160ddd1461017557806323b872dd1461019c57806327e235e3146101c6578063313ce567146101e75780635c6581651461021257806370a082311461023957806395d89b411461025a578063a9059cbb1461026f578063dd62ed3e14610293575b600080fd5b3480156100bf57600080fd5b506100c86102ba565b6040805160208082528351818301528351919283929083019185019080838360005b838110156101025781810151838201526020016100ea565b50505050905090810190601f16801561012f5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561014957600080fd5b50610161600160a060020a0360043516602435610348565b604080519115158252519081900360200190f35b34801561018157600080fd5b5061018a6103ae565b60408051918252519081900360200190f35b3480156101a857600080fd5b50610161600160a060020a03600435811690602435166044356103b4565b3480156101d257600080fd5b5061018a600160a060020a03600435166104b7565b3480156101f357600080fd5b506101fc6104c9565b6040805160ff9092168252519081900360200190f35b34801561021e57600080fd5b5061018a600160a060020a03600435811690602435166104d2565b34801561024557600080fd5b5061018a600160a060020a03600435166104ef565b34801561026657600080fd5b506100c861050a565b34801561027b57600080fd5b50610161600160a060020a0360043516602435610565565b34801561029f57600080fd5b5061018a600160a060020a03600435811690602435166105ed565b6003805460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156103405780601f1061031557610100808354040283529160200191610340565b820191906000526020600020905b81548152906001019060200180831161032357829003601f168201915b505050505081565b336000818152600160209081526040808320600160a060020a038716808552908352818420869055815186815291519394909390927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925928290030190a350600192915050565b60025481565b600160a060020a03831660008181526001602090815260408083203384528252808320549383529082905281205490919083118015906103f45750828110155b15156103ff57600080fd5b600160a060020a038085166000908152602081905260408082208054870190559187168152208054849003905560001981101561046157600160a060020a03851660009081526001602090815260408083203384529091529020805484900390555b83600160a060020a031685600160a060020a03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef856040518082815260200191505060405180910390a3506001949350505050565b60006020819052908152604090205481565b60045460ff1681565b600160209081526000928352604080842090915290825290205481565b600160a060020a031660009081526020819052604090205490565b6005805460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156103405780601f1061031557610100808354040283529160200191610340565b3360009081526020819052604081205482111561058157600080fd5b3360008181526020818152604080832080548790039055600160a060020a03871680845292819020805487019055805186815290519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a350600192915050565b600160a060020a039182166000908152600160209081526040808320939094168252919091522054905600a165627a7a7230582084c618322109054a21a57e27075384a6172ab854e4b2c2d35062a964a6bf593f0029";

public EIP20DeploymentBase() : base(BYTECODE) { }

public EIP20DeploymentBase(string byteCode) : base(byteCode) { }

[Parameter("uint256", "_initialAmount", 1)]

public BigInteger InitialAmount { get; set; }

[Parameter("string", "_tokenName", 2)]

public string TokenName { get; set; }

[Parameter("uint8", "_decimalUnits", 3)]

public byte DecimalUnits { get; set; }

[Parameter("string", "_tokenSymbol", 4)]

public string TokenSymbol { get; set; }

}

[Function("transfer", "bool")]
public class TransferFunctionBase : FunctionMessage
{
[Parameter("address", "_to", 1)]
public string To { get; set; }
[Parameter("uint256", "_value", 2)]
public BigInteger Value { get; set; }
}

public partial class TransferFunction : TransferFunctionBase
{
}

[Function("balanceOf", "uint256")]
public class BalanceOfFunction : FunctionMessage
{
[Parameter("address", "_owner", 1)]
public string Owner { get; set; }
}

[FunctionOutput]
public class BalanceOfFunctionOutput : IFunctionOutputDTO
{
[Parameter("uint256", 1)]
public int Balance { get; set; }
}

[Event("Transfer")]
public class TransferEventDTOBase : IEventDTO
{
[Parameter("address", "_from", 1, true)]
public virtual string From { get; set; }

[Parameter("address", "_to", 2, true)]
public virtual string To { get; set; }

[Parameter("uint256", "_value", 3, false)]
public virtual BigInteger Value { get; set; }
}

public partial class TransferEventDTO : TransferEventDTOBase
{
public static EventABI GetEventABI()
{
return EventExtensions.GetEventABI<TransferEventDTO>();
}
}

8、智能合约部署

为了部署智能合约,我们使用节点 url 和签名信息创建一个 TransactionSignedUnityRequest。 创建一个新的 EIP20Deployment 合约定义,我们设置构造函数参数并发送交易。最后,我们创建 TransactionReceiptPollingRequest 来轮询交易收据并从交易收据中检索新部署的合约地址:

var url = "http://localhost:8545";
var privateKey = "0xb5b1870957d373ef0eeffecc6e4812c0fd08f554b37b233526acc331bf1544f7";
var account = "0x12890d2cce102216644c59daE5baed380d84830c";
//initialising the transaction request sender

var transactionRequest = new TransactionSignedUnityRequest(url, privateKey, "YOURCHAINID");

var deployContract = new EIP20Deployment()
{
InitialAmount = 10000,
FromAddress = account,
TokenName = "TST",
TokenSymbol = "TST"
};

//deploy the contract
yield return transactionRequest.SignAndSendDeploymentContractTransaction<EIP20DeploymentBase>(deployContract);

if (transactionRequest.Exception != null)
{
Debug.Log(transactionRequest.Exception.Message);
yield break;
}

var transactionHash = transactionRequest.Result;
Debug.Log("Deployment transaction hash:" + transactionHash);

//create a poll to get the receipt when mined
var transactionReceiptPolling = new TransactionReceiptPollingRequest(url);

//checking every 2 seconds for the receipt
yield return transactionReceiptPolling.PollForReceipt(transactionHash, 2);

var deploymentReceipt = transactionReceiptPolling.Result;

Debug.Log("Deployment contract address:" + deploymentReceipt.ContractAddress);

9、查询智能合约

要查询智能合约,我们需要创建一个提供 FunctionType 和 ReturnType 的新 QueryUnityRequest。 然后我们将执行查询,查询结果 Result 对象将为我们提供已经解码的合约的输出:

//Query request using our acccount and the contracts address (no parameters needed and default values)
var queryRequest = new QueryUnityRequest<BalanceOfFunction, BalanceOfFunctionOutput>(url, account);
yield return queryRequest.Query(new BalanceOfFunction(){Owner = account}, deploymentReceipt.ContractAddress);

//Getting the dto response already decoded
var dtoResult = queryRequest.Result;
Debug.Log(dtoResult.Balance);

10、转账交易

发送交易以便与与智能合约进行交互的步骤类似于部署。我们首先创建一个 TransactionSignedUnityRequest 和包含任何参数的函数,一旦发送交易,我们会轮询确认交易成功的交易收据。

使用交易收据,我们可以解码该交易的任何日志/事件:

var transactionTransferRequest = new TransactionSignedUnityRequest(url, privateKey, "YOURCHAINID");
var newAddress = "0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe";


var transactionMessage = new TransferFunction
{
FromAddress = account,
To = newAddress,
Value = 1000,
};

yield return transactionTransferRequest.SignAndSendTransaction(transactionMessage, deploymentReceipt.ContractAddress);
var transactionTransferHash = transactionTransferRequest.Result;

Debug.Log("Transfer txn hash:" + transactionHash);

transactionReceiptPolling = new TransactionReceiptPollingRequest(url);
yield return transactionReceiptPolling.PollForReceipt(transactionTransferHash, 2);
var transferReceipt = transactionReceiptPolling.Result;

var transferEvent = transferReceipt.DecodeAllEvents<TransferEventDTO>();
Debug.Log("Transferd amount from event: " + transferEvent[0].Event.Value);

11、日志和事件

为了检索智能合约的日志/事件,我们使用 EthGetLogsUnityRequest 和特定于我们事件的 FilterInput。 可以使用 EventDTO 扩展 GetEventABI() 创建 FilterInputs。一旦我们生成了请求,我们就可以使用 Result.DecodeAllEvents 扩展方法解码所有匹配的事件。

var getLogsRequest = new EthGetLogsUnityRequest(url);
var eventTransfer = TransferEventDTO.GetEventABI();
yield return getLogsRequest.SendRequest(eventTransfer.CreateFilterInput(deploymentReceipt.ContractAddress, account));
var eventDecoded = getLogsRequest.Result.DecodeAllEvents<TransferEventDTO>();
Debug.Log("Transferd amount from get logs event: " + eventDecoded[0].Event.Value);

原文链接:Introduction to Unity in Nethereum

汇智网翻译整理,转载请标明出处


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK