Skip to content

Commit

Permalink
ThirdwebTransaction.Sign + Modify Estimation/Simulation Flows (#17)
Browse files Browse the repository at this point in the history
* ThirdwebTransaction.Sign + Modify Estimation/Simulation Flows

* t sign

* Update Program.cs

* t wallets
  • Loading branch information
0xFirekeeper authored Apr 15, 2024
1 parent dfd8bf3 commit 2bc7f65
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 40 deletions.
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

0 comments on commit 2bc7f65

Please sign in to comment.