Skip to content

Commit 2bc7f65

Browse files
authored
ThirdwebTransaction.Sign + Modify Estimation/Simulation Flows (#17)
* ThirdwebTransaction.Sign + Modify Estimation/Simulation Flows * t sign * Update Program.cs * t wallets
1 parent dfd8bf3 commit 2bc7f65

File tree

6 files changed

+155
-40
lines changed

6 files changed

+155
-40
lines changed

Thirdweb.Console/Program.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
using System.Numerics;
22
using Thirdweb;
33
using dotenv.net;
4+
using Newtonsoft.Json;
5+
using Nethereum.RPC.Eth.DTOs;
6+
using Nethereum.Hex.HexTypes;
47

58
DotEnv.Load();
69

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

96+
// Transaction Builder
97+
var preparedTx = await ThirdwebContract.Prepare(wallet: smartWallet, contract: contract, method: "mintTo", weiValue: 0, parameters: new object[] { await smartWallet.GetAddress(), 100 });
98+
Console.WriteLine($"Prepared transaction: {preparedTx}");
99+
var estimatedCosts = await ThirdwebTransaction.EstimateGasCosts(preparedTx);
100+
Console.WriteLine($"Estimated ETH gas cost: {estimatedCosts.ether}");
101+
var totalCosts = await ThirdwebTransaction.EstimateTotalCosts(preparedTx);
102+
Console.WriteLine($"Estimated ETH total cost: {totalCosts.ether}");
103+
var simulationData = await ThirdwebTransaction.Simulate(preparedTx);
104+
Console.WriteLine($"Simulation data: {simulationData}");
105+
var txHash = await ThirdwebTransaction.Send(preparedTx);
106+
Console.WriteLine($"Transaction hash: {txHash}");
107+
var receipt = await ThirdwebTransaction.WaitForTransactionReceipt(client, 421614, txHash);
108+
Console.WriteLine($"Transaction receipt: {JsonConvert.SerializeObject(receipt)}");
109+
110+
// Transaction Builder - raw transfer
111+
var rawTx = new TransactionInput
112+
{
113+
From = await smartWallet.GetAddress(),
114+
To = await smartWallet.GetAddress(),
115+
Value = new HexBigInteger(BigInteger.Zero),
116+
Data = "0x",
117+
};
118+
var preparedRawTx = await ThirdwebTransaction.Create(client: client, wallet: smartWallet, txInput: rawTx, chainId: 421614);
119+
Console.WriteLine($"Prepared raw transaction: {preparedRawTx}");
120+
var estimatedCostsRaw = await ThirdwebTransaction.EstimateGasCosts(preparedRawTx);
121+
Console.WriteLine($"Estimated ETH gas cost: {estimatedCostsRaw.ether}");
122+
var totalCostsRaw = await ThirdwebTransaction.EstimateTotalCosts(preparedRawTx);
123+
Console.WriteLine($"Estimated ETH total cost: {totalCostsRaw.ether}");
124+
var simulationDataRaw = await ThirdwebTransaction.Simulate(preparedRawTx);
125+
Console.WriteLine($"Simulation data: {simulationDataRaw}");
126+
var txHashRaw = await ThirdwebTransaction.Send(preparedRawTx);
127+
Console.WriteLine($"Raw transaction hash: {txHashRaw}");
128+
var receiptRaw = await ThirdwebTransaction.WaitForTransactionReceipt(client, 421614, txHashRaw);
129+
Console.WriteLine($"Raw transaction receipt: {JsonConvert.SerializeObject(receiptRaw)}");
130+
131+
93132
// Storage actions
94133

95134
// // Will download from IPFS or normal urls

Thirdweb.Tests/Thirdweb.PrivateKeyWallet.Tests.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,23 @@ public async Task SignTransaction_Success()
169169
Assert.NotNull(signature);
170170
}
171171

172+
[Fact]
173+
public async Task SignTransaction_NoFrom_Success()
174+
{
175+
var account = await GetAccount();
176+
var transaction = new TransactionInput
177+
{
178+
To = Constants.ADDRESS_ZERO,
179+
// Value = new HexBigInteger(0),
180+
Gas = new HexBigInteger(21000),
181+
Data = "0x",
182+
Nonce = new HexBigInteger(99999999999),
183+
GasPrice = new HexBigInteger(10000000000)
184+
};
185+
var signature = await account.SignTransaction(transaction, 421614);
186+
Assert.NotNull(signature);
187+
}
188+
172189
[Fact]
173190
public async Task SignTransaction_NullTransaction()
174191
{

Thirdweb.Tests/Thirdweb.SmartWallet.Tests.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ public async Task Initialization_Fail()
3636
Assert.Equal("SmartAccount.Connect: Personal account must be connected.", ex.Message);
3737
}
3838

39+
[Fact]
40+
public async Task ForceDeploy_Success()
41+
{
42+
var client = ThirdwebClient.Create(secretKey: _secretKey);
43+
var privateKeyAccount = await PrivateKeyWallet.Create(client, _testPrivateKey);
44+
var smartAccount = await SmartWallet.Create(client, personalWallet: privateKeyAccount, factoryAddress: "0xbf1C9aA4B1A085f7DA890a44E82B0A1289A40052", gasless: true, chainId: 421614);
45+
await smartAccount.ForceDeploy();
46+
Assert.True(await smartAccount.IsDeployed());
47+
}
48+
3949
[Fact]
4050
public async Task IsDeployed_True()
4151
{

Thirdweb.Tests/Thirdweb.Transactions.Tests.cs

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,27 @@ public async Task SetValue_SetsGasPrice()
9292
Assert.Equal(gas.ToHexBigInteger(), transaction.Input.GasPrice);
9393
}
9494

95+
[Fact]
96+
public async Task Sign_SmartWallet_SignsTransaction()
97+
{
98+
var client = ThirdwebClient.Create(secretKey: _secretKey);
99+
var privateKeyAccount = await PrivateKeyWallet.Create(client, _testPrivateKey);
100+
var smartAccount = await SmartWallet.Create(client, personalWallet: privateKeyAccount, factoryAddress: "0xbf1C9aA4B1A085f7DA890a44E82B0A1289A40052", gasless: true, chainId: 421614);
101+
var transaction = await ThirdwebTransaction.Create(
102+
client,
103+
smartAccount,
104+
new TransactionInput()
105+
{
106+
To = Constants.ADDRESS_ZERO,
107+
Value = new HexBigInteger(0),
108+
Data = "0x"
109+
},
110+
421614
111+
);
112+
var signed = await ThirdwebTransaction.Sign(transaction);
113+
Assert.NotNull(signed);
114+
}
115+
95116
[Fact]
96117
public async Task Send_ThrowsIfToAddressNotProvided()
97118
{
@@ -128,6 +149,7 @@ public async Task EstimateTotalCosts_CalculatesCostsCorrectly()
128149
public async Task EstimateTotalCosts_WithoutSetting_CalculatesCostsCorrectly()
129150
{
130151
var transaction = await CreateSampleTransaction();
152+
transaction.Input.From = Constants.ADDRESS_ZERO;
131153
_ = transaction.SetValue(new BigInteger(1000));
132154

133155
var costs = await ThirdwebTransaction.EstimateTotalCosts(transaction);
@@ -162,13 +184,36 @@ public async Task EstimateGasCosts_CalculatesCostsCorrectly()
162184
public async Task EstimateGasCosts_WithoutSetting_CalculatesCostsCorrectly()
163185
{
164186
var transaction = await CreateSampleTransaction();
187+
transaction.Input.From = Constants.ADDRESS_ZERO;
165188
_ = transaction.SetValue(new BigInteger(1000));
166189

167190
var costs = await ThirdwebTransaction.EstimateGasCosts(transaction);
168191

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

195+
[Fact]
196+
public async Task EstimateGasCosts_SmartWalletHigherThanPrivateKeyWallet()
197+
{
198+
var client = ThirdwebClient.Create(secretKey: _secretKey);
199+
var privateKeyAccount = await PrivateKeyWallet.Create(client, _testPrivateKey);
200+
var smartAccount = await SmartWallet.Create(client, personalWallet: privateKeyAccount, factoryAddress: "0xbf1C9aA4B1A085f7DA890a44E82B0A1289A40052", gasless: true, chainId: 421614);
201+
202+
var transaction = await ThirdwebTransaction.Create(client, smartAccount, new TransactionInput(), 421614);
203+
_ = transaction.SetTo(Constants.ADDRESS_ZERO);
204+
_ = transaction.SetValue(new BigInteger(1000));
205+
206+
var smartCosts = await ThirdwebTransaction.EstimateGasCosts(transaction);
207+
208+
transaction = await ThirdwebTransaction.Create(client, privateKeyAccount, new TransactionInput(), 421614);
209+
_ = transaction.SetTo(Constants.ADDRESS_ZERO);
210+
_ = transaction.SetValue(new BigInteger(1000));
211+
212+
var privateCosts = await ThirdwebTransaction.EstimateGasCosts(transaction);
213+
214+
Assert.True(smartCosts.wei > privateCosts.wei);
215+
}
216+
172217
[Fact]
173218
public async Task EstimateTotalCosts_HigherThanGasCostsByValue()
174219
{
@@ -205,7 +250,7 @@ public async Task Simulate_ThrowsInsufficientFunds()
205250
}
206251

207252
[Fact]
208-
public async Task Simulate_ReturnsGasEstimate()
253+
public async Task Simulate_ReturnsData()
209254
{
210255
var client = ThirdwebClient.Create(secretKey: _secretKey);
211256
var privateKeyAccount = await PrivateKeyWallet.Create(client, _testPrivateKey);
@@ -214,8 +259,8 @@ public async Task Simulate_ReturnsGasEstimate()
214259
_ = transaction.SetValue(new BigInteger(0));
215260
_ = transaction.SetGasLimit(250000);
216261

217-
var gas = await ThirdwebTransaction.Simulate(transaction);
218-
Assert.NotEqual(BigInteger.Zero, gas);
262+
var data = await ThirdwebTransaction.Simulate(transaction);
263+
Assert.NotNull(data);
219264
}
220265

221266
[Fact]

Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs

Lines changed: 31 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System.Numerics;
22
using Nethereum.Hex.HexTypes;
33
using Nethereum.RPC.Eth.DTOs;
4-
using Nethereum.RPC.Eth.Transactions;
54
using Newtonsoft.Json;
65
using Nethereum.Contracts;
76
using Nethereum.ABI.FunctionEncoding;
@@ -34,6 +33,7 @@ public static async Task<ThirdwebTransaction> Create(ThirdwebClient client, IThi
3433
{
3534
var address = await wallet.GetAddress();
3635
txInput.From ??= address;
36+
txInput.Data ??= "0x";
3737
return address != txInput.From
3838
? throw new ArgumentException("Transaction sender (from) must match wallet address")
3939
: client == null
@@ -89,54 +89,50 @@ public ThirdwebTransaction SetNonce(BigInteger nonce)
8989
public static async Task<TotalCosts> EstimateGasCosts(ThirdwebTransaction transaction)
9090
{
9191
var gasPrice = transaction.Input.GasPrice?.Value ?? await EstimateGasPrice(transaction);
92-
var gasLimit = transaction.Input.Gas?.Value ?? await EstimateGasLimit(transaction, true);
92+
var gasLimit = transaction.Input.Gas?.Value ?? await EstimateGasLimit(transaction);
9393
var gasCost = BigInteger.Multiply(gasLimit, gasPrice);
9494
return new TotalCosts { ether = gasCost.ToString().ToEth(18, false), wei = gasCost };
9595
}
9696

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

106104
public static async Task<BigInteger> EstimateGasPrice(ThirdwebTransaction transaction, bool withBump = true)
107105
{
108-
{
109-
var rpc = ThirdwebRPC.GetRpcInstance(transaction._client, transaction.Input.ChainId.Value);
110-
var hex = new HexBigInteger(await rpc.SendRequestAsync<string>("eth_gasPrice"));
111-
return withBump ? hex.Value * 10 / 9 : hex.Value;
112-
}
106+
var rpc = ThirdwebRPC.GetRpcInstance(transaction._client, transaction.Input.ChainId.Value);
107+
var hex = new HexBigInteger(await rpc.SendRequestAsync<string>("eth_gasPrice"));
108+
return withBump ? hex.Value * 10 / 9 : hex.Value;
113109
}
114110

115-
public static async Task<BigInteger> Simulate(ThirdwebTransaction transaction)
111+
public static async Task<string> Simulate(ThirdwebTransaction transaction)
116112
{
117-
return await EstimateGasLimit(transaction, false);
113+
var rpc = ThirdwebRPC.GetRpcInstance(transaction._client, transaction.Input.ChainId.Value);
114+
var data = await rpc.SendRequestAsync<string>("eth_call", transaction.Input, "latest");
115+
return data;
118116
}
119117

120-
public static async Task<BigInteger> EstimateGasLimit(ThirdwebTransaction transaction, bool overrideBalance = true)
118+
public static async Task<BigInteger> EstimateGasLimit(ThirdwebTransaction transaction)
121119
{
122-
var rpc = ThirdwebRPC.GetRpcInstance(transaction._client, transaction.Input.ChainId.Value);
123-
var from = transaction.Input.From;
124-
var hex = overrideBalance
125-
? await rpc.SendRequestAsync<string>(
126-
"eth_estimateGas",
127-
transaction.Input,
128-
"latest",
129-
new Dictionary<string, Dictionary<string, string>>()
130-
{
131-
{
132-
from,
133-
new() { { "balance", "0xFFFFFFFFFFFFFFFFFFFF" } }
134-
}
135-
}
136-
)
137-
: await rpc.SendRequestAsync<string>("eth_estimateGas", transaction.Input, "latest");
138-
139-
return new HexBigInteger(hex).Value;
120+
if (transaction._wallet.AccountType == ThirdwebAccountType.SmartAccount)
121+
{
122+
var smartAccount = transaction._wallet as SmartWallet;
123+
return await smartAccount.EstimateUserOperationGas(transaction.Input, transaction.Input.ChainId.Value);
124+
}
125+
else
126+
{
127+
var rpc = ThirdwebRPC.GetRpcInstance(transaction._client, transaction.Input.ChainId.Value);
128+
var hex = await rpc.SendRequestAsync<string>("eth_estimateGas", transaction.Input, "latest");
129+
return new HexBigInteger(hex).Value;
130+
}
131+
}
132+
133+
public static async Task<string> Sign(ThirdwebTransaction transaction)
134+
{
135+
return await transaction._wallet.SignTransaction(transaction.Input, transaction.Input.ChainId.Value);
140136
}
141137

142138
public static async Task<string> Send(ThirdwebTransaction transaction)
@@ -149,18 +145,18 @@ public static async Task<string> Send(ThirdwebTransaction transaction)
149145
transaction.Input.From ??= await transaction._wallet.GetAddress();
150146
transaction.Input.Value ??= new HexBigInteger(0);
151147
transaction.Input.Data ??= "0x";
152-
transaction.Input.GasPrice ??= new HexBigInteger(await EstimateGasPrice(transaction));
153148
transaction.Input.MaxFeePerGas = null;
154149
transaction.Input.MaxPriorityFeePerGas = null;
155150
transaction.Input.Gas ??= new HexBigInteger(await EstimateGasLimit(transaction));
151+
transaction.Input.GasPrice ??= new HexBigInteger(await EstimateGasPrice(transaction));
156152

157153
var rpc = ThirdwebRPC.GetRpcInstance(transaction._client, transaction.Input.ChainId.Value);
158154
string hash;
159155
switch (transaction._wallet.AccountType)
160156
{
161157
case ThirdwebAccountType.PrivateKeyAccount:
162158
transaction.Input.Nonce ??= new HexBigInteger(await rpc.SendRequestAsync<string>("eth_getTransactionCount", await transaction._wallet.GetAddress(), "latest"));
163-
var signedTx = await transaction._wallet.SignTransaction(transaction.Input, transaction.Input.ChainId.Value);
159+
var signedTx = await Sign(transaction);
164160
hash = await rpc.SendRequestAsync<string>("eth_sendRawTransaction", signedTx);
165161
break;
166162
case ThirdwebAccountType.SmartAccount:

Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Nethereum.Hex.HexConvertors.Extensions;
66
using Nethereum.Hex.HexTypes;
77
using Nethereum.RPC.Eth.DTOs;
8+
using Newtonsoft.Json;
89
using Thirdweb.AccountAbstraction;
910

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

423-
public Task<string> SignTransaction(TransactionInput transaction, BigInteger chainId)
424+
public async Task<BigInteger> EstimateUserOperationGas(TransactionInput transaction, BigInteger chainId)
424425
{
425-
return _personalAccount.SignTransaction(transaction, chainId);
426+
var signedOp = await SignUserOp(transaction);
427+
var cost = signedOp.CallGasLimit + signedOp.VerificationGasLimit + signedOp.PreVerificationGas;
428+
return cost;
429+
}
430+
431+
public async Task<string> SignTransaction(TransactionInput transaction, BigInteger chainId)
432+
{
433+
return JsonConvert.SerializeObject(EncodeUserOperation(await SignUserOp(transaction)));
426434
}
427435

428436
public Task<bool> IsConnected()

0 commit comments

Comments
 (0)