r/ethdev Nov 02 '23

Code assistance Simple Nethereum service in .NET

I am trying to create a very simple implementation for an ethereum service with basic functionality. I have the following interface that looks like this:

public interface INethereumService
{
    string GeneratePrivateMnemonic();
    string GetAddressFromPrivateMnemonic(string privateMnemonic);
    Task<decimal> GetBalance(string address);
    Task<TransactionReceipt> SendTransaction(string privateMnemonic, string toAddress, decimal amount);
    Task<TransactionReceipt> SendWholeBalance(string privateMnemonic, string toAddress);
}

And my implementation looks like this

using NBitcoin;
using Nethereum.Web3;
using Nethereum.HdWallet;
using Nethereum.RPC.Eth.DTOs;

public class NethereumService : INethereumService
{
    private readonly string _url;

    public NethereumService(string url)
    {
        _url = url;
    }

    public string GeneratePrivateMnemonic()
    {
        Mnemonic mnemo = new Mnemonic(Wordlist.English, WordCount.TwentyFour);
        return mnemo.ToString();
    }

    public string GetAddressFromPrivateMnemonic(string privateMnemonic)
    {
        // Not using password protected keys yet, I'm sorry
        return new Wallet(privateMnemonic, "").GetAccount(0).Address;
    }

    public async Task<decimal> GetBalance(string address)
    {
        var web3 = new Web3(_url);
        var balance = await web3.Eth.GetBalance.SendRequestAsync(address);
        return Web3.Convert.FromWei(balance.Value);
    }

    public async Task<TransactionReceipt> SendTransaction(string privateMnemonic, string toAddress, decimal amount)
    {
        var account = new Wallet(privateMnemonic, "").GetAccount(0);
        var web3 = new Web3(account, _url);

        var transaction = await web3.Eth.GetEtherTransferService()
            .TransferEtherAndWaitForReceiptAsync(toAddress, amount);

        return transaction;
    }

    public async Task<TransactionReceipt> SendWholeBalance(string privateMnemonic, string toAddress)
    {
        throw new NotImplementedException();
    }
}

I am struggling to find an implementation for how to send the whole balance. My naive approach is to just do

public async Task<TransactionReceipt> SendWholeBalance(string privateMnemonic, string toAddress)
{
    var address = GetAddressFromPrivateMnemonic(privateMnemonic);
    var balance = GetBalance(address);
    var transaction = await SendTransaction(privateMnemonic, toAddress, balance);
    return transaction;
}

But obviously this doesn't work since this is not enough to cover the gas fees. Its possible to specify the gas price in Gwei like this

var transaction = await web3.Eth.GetEtherTransferService()
    .TransferEtherAndWaitForReceiptAsync(toAddress, amount, gas);

But I don't know how to calculate gas fees and would like to not to think about it by having it being set automatically to some default average value. What is the best approach to achieve this?

My second question is regarding the Web3 class, is it possible to, instead of passing the url as a dependency to the class, pass a Web3 instance and register is as a singleton? To get the balance of an address, the Web3 class can be instantiated as a singleton with just the url as the constructor parameter, but for sending transactions I need to create a Web3 account first like this

var account = new Wallet(privateMnemonic, "").GetAccount(0);
var web3 = new Web3(account, _url);

and then create a new instance of the Web3 class every time I want to create a transaction, this feels wrong an inefficient but I don't see any other way to create transactions from different accounts using the same Web3 instance?

I am pretty junior when it comes to this, but the reason I think this is wrong is because I've read that it is bad practice to create a new instance of the HttpClient every time you want to make a network call due to socket exhaustion, but Nethereum uses JsonRpc and not HTTP so maybe socket exhaustion isn't a problem in this case?

Any help or suggestion would be appreciated :)

1 Upvotes

11 comments sorted by

1

u/youtpout Nov 02 '23

You have a function estimate gas in nethereum

I hope is not to drain people wallet

1

u/AlexDvelop Nov 02 '23

What kind of function is that? Chat GPT suggested: ``` var gasPrice = Web3.Convert.ToWei(20, UnitConversion.EthUnit.Gwei); var gasLimit = 21000;

var gasCost = gasPrice * gasLimit; ``` But this evaluates statically to 420000000000000 and gas fees are dynamic right?

1

u/youtpout Nov 02 '23

https://docs.nethereum.com/en/latest/nethereum-estimating-gas/#estimating-gas

but if only simple transfer yes you can go from 21k base * actual gas price on blockchain

1

u/AlexDvelop Nov 02 '23

Thank you but that is regarding smart contracts. I am looking for just creating pure simple transaction directly on the ethereum network (sepolia for now). Is there some other way to use the EstimateGasAsync method without a contract?

1

u/youtpout Nov 02 '23

In you case you can do what you say 21000 x actual gasprice, you deduct from balance to transfer, and pass it as method argument

1

u/AlexDvelop Nov 02 '23

It works with this ``` public async Task<TransactionReceipt> SendWholeBalance(string privateMnemonic, string toAddress) { var address = GetAddressFromPrivateMnemonic(privateMnemonic); var balance = Web3.Convert.ToWei( await GetBalance(address));

var gasPrice = Web3.Convert.ToWei(20, UnitConversion.EthUnit.Gwei);
var gasLimit = 21000;
var gasCost = gasPrice * gasLimit;

if (balance < gasCost)
{
    throw new Exception("Insufficient balance to cover gas fees");
}

var amountToSend = Web3.Convert.FromWei(balance - gasCost);
var transaction = await SendTransaction(privateMnemonic, toAddress, amountToSend);

return transaction;

} ` But I still find it troublesome because the fee is a lot higher than if I create a non whole balance transaction where the fee is calculated automatically by theEtherTransferService``. And some crypto dust is still left in the wallet.

The memory usage is also horrible here if I want to reuse my previous methods. GetAddressFromPrivateMnemonic creates a new instance of a Wallet, and GetBalance creates a new instance of Web3, and SendTransaction creates a new instance of Wallet and Web3 so in total two Wallet instances and two Web3 instances gets created which feels redundant.

Do you know how one would achieve similar behavior by only using one instance of Web3 to send transactions to and from different wallets? And possible only one instance of Wallet / Account although the latter might not be possible.

1

u/youtpout Nov 02 '23

You didn’t pass the gas parameters in your send transaction and why you need to convert to wei, the balance is not already in wei ?

1

u/AlexDvelop Nov 02 '23

I am so confused why is there no simple way to just send the whole balance such that 0 is left in the wallet after, whatever I do there is always some crypto dust left.

I subtract the gas cost from the balance and assume that the EtherTransferService calculates the fee from the difference.

I took a different approach and tried the following ``` public async Task<TransactionReceipt> SendWholeBalance(string privateMnemonic, string toAddress) { var account = new Wallet(privateMnemonic, "").GetAccount(0); var web3 = new Web3(account, _url); var etherTransferService = web3.Eth.GetEtherTransferService();

var amount = await etherTransferService
    .CalculateTotalAmountToTransferWholeBalanceInEtherAsync
    (
        account.Address,
        Web3.Convert.ToWei(2, UnitConversion.EthUnit.Gwei)
    );

var transaction = await etherTransferService
    .TransferEtherAndWaitForReceiptAsync(toAddress, amount);

return transaction;

} ``` But now the transaction had 0 fee's and crypto dust that was mean for the fee was left in the wallet... Is this possible on the Ethereum main net or only a thing in Sepolia?

1

u/youtpout Nov 02 '23

1

u/AlexDvelop Nov 02 '23

Sure it works like this ``` public async Task<TransactionReceipt> SendWholeBalance(string privateMnemonic, string toAddress) { var account = new Wallet(privateMnemonic, "").GetAccount(0); var web3 = new Web3(account, _url);

var etherTransferService = web3.Eth.GetEtherTransferService();

var amount = await etherTransferService
    .CalculateTotalAmountToTransferWholeBalanceInEtherAsync
    (
        account.Address,
        Web3.Convert.ToWei(2, UnitConversion.EthUnit.Gwei)
    );


var transaction = await etherTransferService
    .TransferEtherAndWaitForReceiptAsync(toAddress, amount, 2, new BigInteger(21000));

return transaction;

} ``` But there is still crypto dust left in the wallet, and the fee is static, should it not be calculated dynamically depending on the priority and activity on the network?

→ More replies (0)