Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ThirdwebTransaction.Sign + Modify Estimation/Simulation Flows #17

Merged
merged 4 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions Thirdweb.Console/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System.Numerics;
using Thirdweb;
using dotenv.net;
using Newtonsoft.Json;
using Nethereum.RPC.Eth.DTOs;
using Nethereum.Hex.HexTypes;

DotEnv.Load();

Expand Down Expand Up @@ -90,6 +93,42 @@
var balanceAfter = await ThirdwebContract.Read<BigInteger>(contract, "balanceOf", await smartWallet.GetAddress());
Console.WriteLine($"Balance after mint: {balanceAfter}");

// Transaction Builder
var preparedTx = await ThirdwebContract.Prepare(wallet: smartWallet, contract: contract, method: "mintTo", weiValue: 0, parameters: new object[] { await smartWallet.GetAddress(), 100 });
Console.WriteLine($"Prepared transaction: {preparedTx}");
var estimatedCosts = await ThirdwebTransaction.EstimateGasCosts(preparedTx);
Console.WriteLine($"Estimated ETH gas cost: {estimatedCosts.ether}");
var totalCosts = await ThirdwebTransaction.EstimateTotalCosts(preparedTx);
Console.WriteLine($"Estimated ETH total cost: {totalCosts.ether}");
var simulationData = await ThirdwebTransaction.Simulate(preparedTx);
Console.WriteLine($"Simulation data: {simulationData}");
var txHash = await ThirdwebTransaction.Send(preparedTx);
Console.WriteLine($"Transaction hash: {txHash}");
var receipt = await ThirdwebTransaction.WaitForTransactionReceipt(client, 421614, txHash);
Console.WriteLine($"Transaction receipt: {JsonConvert.SerializeObject(receipt)}");

// Transaction Builder - raw transfer
var rawTx = new TransactionInput
{
From = await smartWallet.GetAddress(),
To = await smartWallet.GetAddress(),
Value = new HexBigInteger(BigInteger.Zero),
Data = "0x",
};
var preparedRawTx = await ThirdwebTransaction.Create(client: client, wallet: smartWallet, txInput: rawTx, chainId: 421614);
Console.WriteLine($"Prepared raw transaction: {preparedRawTx}");
var estimatedCostsRaw = await ThirdwebTransaction.EstimateGasCosts(preparedRawTx);
Console.WriteLine($"Estimated ETH gas cost: {estimatedCostsRaw.ether}");
var totalCostsRaw = await ThirdwebTransaction.EstimateTotalCosts(preparedRawTx);
Console.WriteLine($"Estimated ETH total cost: {totalCostsRaw.ether}");
var simulationDataRaw = await ThirdwebTransaction.Simulate(preparedRawTx);
Console.WriteLine($"Simulation data: {simulationDataRaw}");
var txHashRaw = await ThirdwebTransaction.Send(preparedRawTx);
Console.WriteLine($"Raw transaction hash: {txHashRaw}");
var receiptRaw = await ThirdwebTransaction.WaitForTransactionReceipt(client, 421614, txHashRaw);
Console.WriteLine($"Raw transaction receipt: {JsonConvert.SerializeObject(receiptRaw)}");


// Storage actions

// // Will download from IPFS or normal urls
Expand Down
17 changes: 17 additions & 0 deletions Thirdweb.Tests/Thirdweb.PrivateKeyWallet.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,23 @@ public async Task SignTransaction_Success()
Assert.NotNull(signature);
}

[Fact]
public async Task SignTransaction_NoFrom_Success()
{
var account = await GetAccount();
var transaction = new TransactionInput
{
To = Constants.ADDRESS_ZERO,
// Value = new HexBigInteger(0),
Gas = new HexBigInteger(21000),
Data = "0x",
Nonce = new HexBigInteger(99999999999),
GasPrice = new HexBigInteger(10000000000)
};
var signature = await account.SignTransaction(transaction, 421614);
Assert.NotNull(signature);
}

[Fact]
public async Task SignTransaction_NullTransaction()
{
Expand Down
10 changes: 10 additions & 0 deletions Thirdweb.Tests/Thirdweb.SmartWallet.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ public async Task Initialization_Fail()
Assert.Equal("SmartAccount.Connect: Personal account must be connected.", ex.Message);
}

[Fact]
public async Task ForceDeploy_Success()
{
var client = ThirdwebClient.Create(secretKey: _secretKey);
var privateKeyAccount = await PrivateKeyWallet.Create(client, _testPrivateKey);
var smartAccount = await SmartWallet.Create(client, personalWallet: privateKeyAccount, factoryAddress: "0xbf1C9aA4B1A085f7DA890a44E82B0A1289A40052", gasless: true, chainId: 421614);
await smartAccount.ForceDeploy();
Assert.True(await smartAccount.IsDeployed());
}

[Fact]
public async Task IsDeployed_True()
{
Expand Down
51 changes: 48 additions & 3 deletions Thirdweb.Tests/Thirdweb.Transactions.Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,27 @@ public async Task SetValue_SetsGasPrice()
Assert.Equal(gas.ToHexBigInteger(), transaction.Input.GasPrice);
}

[Fact]
public async Task Sign_SmartWallet_SignsTransaction()
{
var client = ThirdwebClient.Create(secretKey: _secretKey);
var privateKeyAccount = await PrivateKeyWallet.Create(client, _testPrivateKey);
var smartAccount = await SmartWallet.Create(client, personalWallet: privateKeyAccount, factoryAddress: "0xbf1C9aA4B1A085f7DA890a44E82B0A1289A40052", gasless: true, chainId: 421614);
var transaction = await ThirdwebTransaction.Create(
client,
smartAccount,
new TransactionInput()
{
To = Constants.ADDRESS_ZERO,
Value = new HexBigInteger(0),
Data = "0x"
},
421614
);
var signed = await ThirdwebTransaction.Sign(transaction);
Assert.NotNull(signed);
}

[Fact]
public async Task Send_ThrowsIfToAddressNotProvided()
{
Expand Down Expand Up @@ -128,6 +149,7 @@ public async Task EstimateTotalCosts_CalculatesCostsCorrectly()
public async Task EstimateTotalCosts_WithoutSetting_CalculatesCostsCorrectly()
{
var transaction = await CreateSampleTransaction();
transaction.Input.From = Constants.ADDRESS_ZERO;
_ = transaction.SetValue(new BigInteger(1000));

var costs = await ThirdwebTransaction.EstimateTotalCosts(transaction);
Expand Down Expand Up @@ -162,13 +184,36 @@ public async Task EstimateGasCosts_CalculatesCostsCorrectly()
public async Task EstimateGasCosts_WithoutSetting_CalculatesCostsCorrectly()
{
var transaction = await CreateSampleTransaction();
transaction.Input.From = Constants.ADDRESS_ZERO;
_ = transaction.SetValue(new BigInteger(1000));

var costs = await ThirdwebTransaction.EstimateGasCosts(transaction);

Assert.NotEqual(BigInteger.Zero, costs.wei);
}

[Fact]
public async Task EstimateGasCosts_SmartWalletHigherThanPrivateKeyWallet()
{
var client = ThirdwebClient.Create(secretKey: _secretKey);
var privateKeyAccount = await PrivateKeyWallet.Create(client, _testPrivateKey);
var smartAccount = await SmartWallet.Create(client, personalWallet: privateKeyAccount, factoryAddress: "0xbf1C9aA4B1A085f7DA890a44E82B0A1289A40052", gasless: true, chainId: 421614);

var transaction = await ThirdwebTransaction.Create(client, smartAccount, new TransactionInput(), 421614);
_ = transaction.SetTo(Constants.ADDRESS_ZERO);
_ = transaction.SetValue(new BigInteger(1000));

var smartCosts = await ThirdwebTransaction.EstimateGasCosts(transaction);

transaction = await ThirdwebTransaction.Create(client, privateKeyAccount, new TransactionInput(), 421614);
_ = transaction.SetTo(Constants.ADDRESS_ZERO);
_ = transaction.SetValue(new BigInteger(1000));

var privateCosts = await ThirdwebTransaction.EstimateGasCosts(transaction);

Assert.True(smartCosts.wei > privateCosts.wei);
}

[Fact]
public async Task EstimateTotalCosts_HigherThanGasCostsByValue()
{
Expand Down Expand Up @@ -205,7 +250,7 @@ public async Task Simulate_ThrowsInsufficientFunds()
}

[Fact]
public async Task Simulate_ReturnsGasEstimate()
public async Task Simulate_ReturnsData()
{
var client = ThirdwebClient.Create(secretKey: _secretKey);
var privateKeyAccount = await PrivateKeyWallet.Create(client, _testPrivateKey);
Expand All @@ -214,8 +259,8 @@ public async Task Simulate_ReturnsGasEstimate()
_ = transaction.SetValue(new BigInteger(0));
_ = transaction.SetGasLimit(250000);

var gas = await ThirdwebTransaction.Simulate(transaction);
Assert.NotEqual(BigInteger.Zero, gas);
var data = await ThirdwebTransaction.Simulate(transaction);
Assert.NotNull(data);
}

[Fact]
Expand Down
66 changes: 31 additions & 35 deletions Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System.Numerics;
using Nethereum.Hex.HexTypes;
using Nethereum.RPC.Eth.DTOs;
using Nethereum.RPC.Eth.Transactions;
using Newtonsoft.Json;
using Nethereum.Contracts;
using Nethereum.ABI.FunctionEncoding;
Expand Down Expand Up @@ -34,6 +33,7 @@ public static async Task<ThirdwebTransaction> Create(ThirdwebClient client, IThi
{
var address = await wallet.GetAddress();
txInput.From ??= address;
txInput.Data ??= "0x";
return address != txInput.From
? throw new ArgumentException("Transaction sender (from) must match wallet address")
: client == null
Expand Down Expand Up @@ -89,54 +89,50 @@ public ThirdwebTransaction SetNonce(BigInteger nonce)
public static async Task<TotalCosts> EstimateGasCosts(ThirdwebTransaction transaction)
{
var gasPrice = transaction.Input.GasPrice?.Value ?? await EstimateGasPrice(transaction);
var gasLimit = transaction.Input.Gas?.Value ?? await EstimateGasLimit(transaction, true);
var gasLimit = transaction.Input.Gas?.Value ?? await EstimateGasLimit(transaction);
var gasCost = BigInteger.Multiply(gasLimit, gasPrice);
return new TotalCosts { ether = gasCost.ToString().ToEth(18, false), wei = gasCost };
}

public static async Task<TotalCosts> EstimateTotalCosts(ThirdwebTransaction transaction)
{
var gasPrice = transaction.Input.GasPrice?.Value ?? await EstimateGasPrice(transaction);
var gasLimit = transaction.Input.Gas?.Value ?? await EstimateGasLimit(transaction, true);
var gasCost = BigInteger.Multiply(gasLimit, gasPrice);
var gasCostWithValue = BigInteger.Add(gasCost, transaction.Input.Value?.Value ?? 0);
return new TotalCosts { ether = gasCostWithValue.ToString().ToEth(18, false), wei = gasCostWithValue };
var gasCosts = await EstimateGasCosts(transaction);
var value = transaction.Input.Value?.Value ?? 0;
return new TotalCosts { ether = (value + gasCosts.wei).ToString().ToEth(18, false), wei = value + gasCosts.wei };
}

public static async Task<BigInteger> EstimateGasPrice(ThirdwebTransaction transaction, bool withBump = true)
{
{
var rpc = ThirdwebRPC.GetRpcInstance(transaction._client, transaction.Input.ChainId.Value);
var hex = new HexBigInteger(await rpc.SendRequestAsync<string>("eth_gasPrice"));
return withBump ? hex.Value * 10 / 9 : hex.Value;
}
var rpc = ThirdwebRPC.GetRpcInstance(transaction._client, transaction.Input.ChainId.Value);
var hex = new HexBigInteger(await rpc.SendRequestAsync<string>("eth_gasPrice"));
return withBump ? hex.Value * 10 / 9 : hex.Value;
}

public static async Task<BigInteger> Simulate(ThirdwebTransaction transaction)
public static async Task<string> Simulate(ThirdwebTransaction transaction)
{
return await EstimateGasLimit(transaction, false);
var rpc = ThirdwebRPC.GetRpcInstance(transaction._client, transaction.Input.ChainId.Value);
var data = await rpc.SendRequestAsync<string>("eth_call", transaction.Input, "latest");
return data;
}

public static async Task<BigInteger> EstimateGasLimit(ThirdwebTransaction transaction, bool overrideBalance = true)
public static async Task<BigInteger> EstimateGasLimit(ThirdwebTransaction transaction)
{
var rpc = ThirdwebRPC.GetRpcInstance(transaction._client, transaction.Input.ChainId.Value);
var from = transaction.Input.From;
var hex = overrideBalance
? await rpc.SendRequestAsync<string>(
"eth_estimateGas",
transaction.Input,
"latest",
new Dictionary<string, Dictionary<string, string>>()
{
{
from,
new() { { "balance", "0xFFFFFFFFFFFFFFFFFFFF" } }
}
}
)
: await rpc.SendRequestAsync<string>("eth_estimateGas", transaction.Input, "latest");

return new HexBigInteger(hex).Value;
if (transaction._wallet.AccountType == ThirdwebAccountType.SmartAccount)
{
var smartAccount = transaction._wallet as SmartWallet;
return await smartAccount.EstimateUserOperationGas(transaction.Input, transaction.Input.ChainId.Value);
}
else
{
var rpc = ThirdwebRPC.GetRpcInstance(transaction._client, transaction.Input.ChainId.Value);
var hex = await rpc.SendRequestAsync<string>("eth_estimateGas", transaction.Input, "latest");
return new HexBigInteger(hex).Value;
}
}

public static async Task<string> Sign(ThirdwebTransaction transaction)
{
return await transaction._wallet.SignTransaction(transaction.Input, transaction.Input.ChainId.Value);
}

public static async Task<string> Send(ThirdwebTransaction transaction)
Expand All @@ -149,18 +145,18 @@ public static async Task<string> Send(ThirdwebTransaction transaction)
transaction.Input.From ??= await transaction._wallet.GetAddress();
transaction.Input.Value ??= new HexBigInteger(0);
transaction.Input.Data ??= "0x";
transaction.Input.GasPrice ??= new HexBigInteger(await EstimateGasPrice(transaction));
transaction.Input.MaxFeePerGas = null;
transaction.Input.MaxPriorityFeePerGas = null;
transaction.Input.Gas ??= new HexBigInteger(await EstimateGasLimit(transaction));
transaction.Input.GasPrice ??= new HexBigInteger(await EstimateGasPrice(transaction));

var rpc = ThirdwebRPC.GetRpcInstance(transaction._client, transaction.Input.ChainId.Value);
string hash;
switch (transaction._wallet.AccountType)
{
case ThirdwebAccountType.PrivateKeyAccount:
transaction.Input.Nonce ??= new HexBigInteger(await rpc.SendRequestAsync<string>("eth_getTransactionCount", await transaction._wallet.GetAddress(), "latest"));
var signedTx = await transaction._wallet.SignTransaction(transaction.Input, transaction.Input.ChainId.Value);
var signedTx = await Sign(transaction);
hash = await rpc.SendRequestAsync<string>("eth_sendRawTransaction", signedTx);
break;
case ThirdwebAccountType.SmartAccount:
Expand Down
12 changes: 10 additions & 2 deletions Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Nethereum.Hex.HexConvertors.Extensions;
using Nethereum.Hex.HexTypes;
using Nethereum.RPC.Eth.DTOs;
using Newtonsoft.Json;
using Thirdweb.AccountAbstraction;

namespace Thirdweb
Expand Down Expand Up @@ -420,9 +421,16 @@ public Task<string> SignTypedDataV4<T, TDomain>(T data, TypedData<TDomain> typed
return _personalAccount.SignTypedDataV4(data, typedData);
}

public Task<string> SignTransaction(TransactionInput transaction, BigInteger chainId)
public async Task<BigInteger> EstimateUserOperationGas(TransactionInput transaction, BigInteger chainId)
{
return _personalAccount.SignTransaction(transaction, chainId);
var signedOp = await SignUserOp(transaction);
var cost = signedOp.CallGasLimit + signedOp.VerificationGasLimit + signedOp.PreVerificationGas;
return cost;
}

public async Task<string> SignTransaction(TransactionInput transaction, BigInteger chainId)
{
return JsonConvert.SerializeObject(EncodeUserOperation(await SignUserOp(transaction)));
}

public Task<bool> IsConnected()
Expand Down
Loading