Skip to content

Commit d6173d9

Browse files
authored
Transaction.Populate & Cross-Platform Unification (#209)
1 parent a2e2960 commit d6173d9

File tree

6 files changed

+264
-204
lines changed

6 files changed

+264
-204
lines changed

Assets/Thirdweb/Core/Plugin/thirdweb.jslib

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,29 @@ var plugin = {
461461
dynCall_viii(cb, idPtr, null, buffer);
462462
});
463463
},
464+
ThirdwebGetNonce: async function (taskId, address, blockTag, cb) {
465+
// convert taskId from pointer to str and allocate it to keep in memory
466+
var id = UTF8ToString(taskId);
467+
var idSize = lengthBytesUTF8(id) + 1;
468+
var idPtr = _malloc(idSize);
469+
stringToUTF8(id, idPtr, idSize);
470+
// execute bridge call
471+
window.bridge
472+
.getNonce(UTF8ToString(address), UTF8ToString(blockTag))
473+
.then((returnStr) => {
474+
var bufferSize = lengthBytesUTF8(returnStr) + 1;
475+
var buffer = _malloc(bufferSize);
476+
stringToUTF8(returnStr, buffer, bufferSize);
477+
dynCall_viii(cb, idPtr, buffer, null);
478+
})
479+
.catch((err) => {
480+
var msg = err.message;
481+
var bufferSize = lengthBytesUTF8(msg) + 1;
482+
var buffer = _malloc(bufferSize);
483+
stringToUTF8(msg, buffer, bufferSize);
484+
dynCall_viii(cb, idPtr, null, buffer);
485+
});
486+
},
464487
ThirdwebResolveENSFromAddress: async function (taskId, address, cb) {
465488
// convert taskId from pointer to str and allocate it to keep in memory
466489
var id = UTF8ToString(taskId);

Assets/Thirdweb/Core/Scripts/Bridge.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,23 @@ public static async Task<bool> SmartWalletIsDeployed()
416416
return JsonConvert.DeserializeObject<Result<bool>>(result).result;
417417
}
418418

419+
public static async Task<int> GetNonce(string address, string blockTag)
420+
{
421+
if (!Utils.IsWebGLBuild())
422+
{
423+
ThirdwebDebug.LogWarning("Interacting with the thirdweb SDK is not fully supported in the editor.");
424+
return -1;
425+
}
426+
string taskId = Guid.NewGuid().ToString();
427+
var task = new TaskCompletionSource<string>();
428+
taskMap[taskId] = task;
429+
#if UNITY_WEBGL
430+
ThirdwebGetNonce(taskId, address, blockTag, jsCallback);
431+
#endif
432+
string result = await task.Task;
433+
return JsonConvert.DeserializeObject<Result<int>>(result).result;
434+
}
435+
419436
public static async Task<string> ResolveENSFromAddress(string address)
420437
{
421438
if (!Utils.IsWebGLBuild())
@@ -508,6 +525,8 @@ public static async Task CopyBuffer(string text)
508525
[DllImport("__Internal")]
509526
private static extern string ThirdwebSmartWalletIsDeployed(string taskId, Action<string, string, string> cb);
510527
[DllImport("__Internal")]
528+
private static extern string ThirdwebGetNonce(string taskId, string address, string blockTag, Action<string, string, string> cb);
529+
[DllImport("__Internal")]
511530
private static extern string ThirdwebResolveENSFromAddress(string taskId, string address, Action<string, string, string> cb);
512531
[DllImport("__Internal")]
513532
private static extern string ThirdwebResolveAddressFromENS(string taskId, string ens, Action<string, string, string> cb);

Assets/Thirdweb/Core/Scripts/Contract.cs

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -104,36 +104,11 @@ public async Task<CurrencyValue> GetBalance()
104104
/// <returns>A <see cref="Transaction"/> object representing the prepared transaction.</returns>
105105
public async Task<Transaction> Prepare(string functionName, params object[] args)
106106
{
107-
return await Prepare(functionName, null, args);
108-
}
109-
110-
/// <summary>
111-
/// Prepare a transaction by creating a <see cref="Transaction"/> object.
112-
/// </summary>
113-
/// <param name="functionName">The name of the contract function.</param>
114-
/// <param name="from">The address to send the transaction from.</param>
115-
/// <param name="args">Optional function arguments.</param>
116-
/// <returns>A <see cref="Transaction"/> object representing the prepared transaction.</returns>
117-
public async Task<Transaction> Prepare(string functionName, string from = null, params object[] args)
118-
{
119-
var initialInput = new TransactionInput();
120-
if (Utils.IsWebGLBuild())
121-
{
122-
initialInput.From = from ?? await _sdk.Wallet.GetAddress();
123-
initialInput.To = Address;
124-
}
125-
else
126-
{
127-
if (this.ABI == null)
128-
this.ABI = await FetchAbi(this.Address, await _sdk.Wallet.GetChainId());
129-
130-
var web3 = Utils.GetWeb3(_sdk.Session.ChainId, _sdk.Session.Options.clientId, _sdk.Session.Options.bundleId);
131-
var contract = web3.Eth.GetContract(this.ABI, this.Address);
132-
var function = Utils.GetFunctionMatchSignature(contract, functionName, args);
133-
var fromAddress = from ?? await _sdk.Wallet.GetAddress();
134-
initialInput = function.CreateTransactionInput(fromAddress, args);
135-
}
136-
107+
this.ABI ??= await FetchAbi(this.Address, await _sdk.Wallet.GetChainId());
108+
var contract = new Nethereum.Contracts.Contract(null, this.ABI, this.Address);
109+
var function = Utils.GetFunctionMatchSignature(contract, functionName, args);
110+
var fromAddress = await _sdk.Wallet.GetAddress();
111+
var initialInput = function.CreateTransactionInput(fromAddress, args);
137112
return new Transaction(this, initialInput, functionName, args);
138113
}
139114

Assets/Thirdweb/Core/Scripts/Transaction.cs

Lines changed: 79 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,21 @@ public Transaction(ThirdwebSDK sdk, TransactionInput txInput)
7575
/// <returns>The JSON string representation of the transaction input.</returns>
7676
public override string ToString()
7777
{
78-
return JsonConvert.SerializeObject(Input);
78+
var readableInput = new
79+
{
80+
from = Input.From,
81+
to = Input.To,
82+
value = Input.Value?.Value.ToString(),
83+
gas = Input.Gas?.Value.ToString(),
84+
gasPrice = Input.GasPrice?.Value.ToString(),
85+
data = Input.Data,
86+
nonce = Input.Nonce?.Value.ToString(),
87+
chainId = Input.ChainId?.Value.ToString(),
88+
maxFeePerGas = Input.MaxFeePerGas?.Value.ToString(),
89+
maxPriorityFeePerGas = Input.MaxPriorityFeePerGas?.Value.ToString(),
90+
type = Input.Type?.Value.ToString()
91+
};
92+
return JsonConvert.SerializeObject(readableInput);
7993
}
8094

8195
/// <summary>
@@ -206,17 +220,10 @@ public Transaction SetNonce(string nonce)
206220
/// <returns>The modified <see cref="Transaction"/> object.</returns>
207221
public Transaction SetArgs(params object[] args)
208222
{
209-
if (Utils.IsWebGLBuild())
210-
{
211-
this.FunctionArgs = args;
212-
}
213-
else
214-
{
215-
var web3 = Utils.GetWeb3(_sdk.Session.ChainId, _sdk.Session.Options.clientId, _sdk.Session.Options.bundleId);
216-
var contract = web3.Eth.GetContract(Contract.ABI, Contract.Address);
217-
var function = Utils.GetFunctionMatchSignature(contract, FunctionName, args);
218-
Input.Data = function.GetData(args);
219-
}
223+
this.FunctionArgs = args;
224+
var contract = new Nethereum.Contracts.Contract(null, Contract.ABI, Contract.Address);
225+
var function = Utils.GetFunctionMatchSignature(contract, FunctionName, args);
226+
Input.Data = function.GetData(args);
220227
return this;
221228
}
222229

@@ -237,6 +244,18 @@ public async Task<BigInteger> GetGasPrice()
237244
}
238245
}
239246

247+
public async Task<GasPriceParameters> GetGasFees()
248+
{
249+
if (Utils.IsWebGLBuild())
250+
{
251+
return await Bridge.InvokeRoute<GasPriceParameters>(GetTxBuilderRoute("getGasFees"), Utils.ToJsonStringArray(Input, FunctionName, FunctionArgs));
252+
}
253+
else
254+
{
255+
return await Utils.GetGasPriceAsync(_sdk.Session.ChainId, _sdk.Session.Options.clientId, _sdk.Session.Options.bundleId);
256+
}
257+
}
258+
240259
/// <summary>
241260
/// Estimates the gas limit for the transaction asynchronously.
242261
/// </summary>
@@ -328,34 +347,57 @@ public async Task<string> Sign()
328347
}
329348

330349
/// <summary>
331-
/// Sends the transaction asynchronously.
350+
/// Populates the transaction asynchronously, setting the gas limit, gas price, nonce, and other parameters.
332351
/// </summary>
333-
/// <param name="gasless">Specifies whether to send the transaction as a gasless transaction. Default is null (uses gasless if set up).</param>
334-
/// <returns>The transaction hash as a string.</returns>
335-
public async Task<string> Send(bool? gasless = null)
352+
/// <returns>The prepared <see cref="Transaction"/> object.</returns>
353+
/// <remarks> There is no guarantee the gas and nonce values will be preserved when using Account Abstraction.</remarks>
354+
public async Task<Transaction> Populate()
336355
{
337-
if (Utils.IsWebGLBuild())
356+
Input.Gas ??= new HexBigInteger(await EstimateGasLimit());
357+
358+
Input.Value ??= new HexBigInteger(0);
359+
360+
Input.Nonce ??= new HexBigInteger(await _sdk.Wallet.GetNonce());
361+
362+
var force1559 = Input.Type != null && Input.Type.HexValue == new HexBigInteger((int)TransactionType.EIP1559).HexValue;
363+
var supports1559 = force1559 || (Input.Type == null && Utils.Supports1559(_sdk.Session.ChainId.ToString()));
364+
if (supports1559)
338365
{
339-
if (gasless == null || gasless == false)
340-
return await Send();
341-
else
342-
return await SendGasless();
366+
if (Input.GasPrice == null)
367+
{
368+
var fees = await GetGasFees();
369+
Input.MaxFeePerGas ??= new HexBigInteger(fees.MaxFeePerGas);
370+
Input.MaxPriorityFeePerGas ??= new HexBigInteger(fees.MaxPriorityFeePerGas);
371+
}
343372
}
344373
else
345374
{
346-
if (Input.Gas == null)
347-
await EstimateAndSetGasLimitAsync();
348-
if (Input.Value == null)
349-
Input.Value = new HexBigInteger(0);
350-
bool isGaslessSetup = _sdk.Session.Options.gasless.HasValue && !string.IsNullOrEmpty(_sdk.Session.Options.gasless?.engine.relayerUrl);
351-
if (gasless != null && gasless.Value && !isGaslessSetup)
352-
throw new UnityException("Gasless relayer transactions are not enabled. Please enable them in the SDK options.");
353-
bool sendGaslessly = gasless == null ? isGaslessSetup : gasless.Value;
354-
if (sendGaslessly)
355-
return await SendGasless();
356-
else
357-
return await Send();
375+
if (Input.MaxFeePerGas == null && Input.MaxPriorityFeePerGas == null)
376+
{
377+
ThirdwebDebug.Log("Using Legacy Gas Pricing");
378+
var gasPrice = await GetGasPrice();
379+
Input.GasPrice = new HexBigInteger(gasPrice);
380+
}
358381
}
382+
return this;
383+
}
384+
385+
/// <summary>
386+
/// Sends the transaction asynchronously.
387+
/// </summary>
388+
/// <param name="gasless">Specifies whether to send the transaction as a gasless transaction (through thirdweb Engine relayer). Default is null (uses gasless if set up).</param>
389+
/// <returns>The transaction hash as a string.</returns>
390+
public async Task<string> Send(bool? gasless = null)
391+
{
392+
var tx = await Populate();
393+
bool isGaslessSetup = _sdk.Session.Options.gasless.HasValue && !string.IsNullOrEmpty(_sdk.Session.Options.gasless?.engine.relayerUrl);
394+
if (gasless != null && gasless.Value && !isGaslessSetup)
395+
throw new UnityException("Gasless relayer transactions are not enabled. Please enable them in the SDK options.");
396+
bool sendGaslessly = gasless == null ? isGaslessSetup : gasless.Value;
397+
if (sendGaslessly)
398+
return await tx.SendGasless();
399+
else
400+
return await tx.Send();
359401
}
360402

361403
/// <summary>
@@ -437,35 +479,13 @@ public static async Task<TransactionReceipt> WaitForTransactionResultRaw(string
437479

438480
private async Task<string> Send()
439481
{
482+
string hash;
440483
if (Utils.IsWebGLBuild())
441484
{
442-
return await Bridge.InvokeRoute<string>(GetTxBuilderRoute("send"), Utils.ToJsonStringArray(Input, FunctionName, FunctionArgs));
485+
hash = await Bridge.InvokeRoute<string>(GetTxBuilderRoute("send"), Utils.ToJsonStringArray(Input, FunctionName, FunctionArgs));
443486
}
444487
else
445488
{
446-
var force1559 = Input.Type != null && Input.Type.HexValue == new HexBigInteger((int)TransactionType.EIP1559).HexValue;
447-
var supports1559 = force1559 || (Input.Type == null && Utils.Supports1559(_sdk.Session.ChainId.ToString()));
448-
if (supports1559)
449-
{
450-
if (Input.GasPrice == null)
451-
{
452-
var fees = await Utils.GetGasPriceAsync(_sdk.Session.ChainId, _sdk.Session.Options.clientId, _sdk.Session.Options.bundleId);
453-
if (Input.MaxFeePerGas == null)
454-
Input.MaxFeePerGas = new HexBigInteger(fees.MaxFeePerGas);
455-
if (Input.MaxPriorityFeePerGas == null)
456-
Input.MaxPriorityFeePerGas = new HexBigInteger(fees.MaxPriorityFeePerGas);
457-
}
458-
}
459-
else
460-
{
461-
if (Input.MaxFeePerGas == null && Input.MaxPriorityFeePerGas == null)
462-
{
463-
ThirdwebDebug.Log("Using Legacy Gas Pricing");
464-
Input.GasPrice = new HexBigInteger(await Utils.GetLegacyGasPriceAsync(_sdk.Session.ChainId, _sdk.Session.Options.clientId, _sdk.Session.Options.bundleId));
465-
}
466-
}
467-
468-
string hash;
469489
if (_sdk.Session.ActiveWallet.GetSignerProvider() == WalletProvider.LocalWallet && _sdk.Session.ActiveWallet.GetProvider() != WalletProvider.SmartWallet)
470490
{
471491
hash = await _sdk.Session.Web3.Eth.TransactionManager.SendTransactionAsync(Input);
@@ -475,9 +495,9 @@ private async Task<string> Send()
475495
var ethSendTx = new EthSendTransaction(_sdk.Session.Web3.Client);
476496
hash = await ethSendTx.SendRequestAsync(Input);
477497
}
478-
ThirdwebDebug.Log($"Transaction hash: {hash}");
479-
return hash;
480498
}
499+
ThirdwebDebug.Log($"Transaction hash: {hash}");
500+
return hash;
481501
}
482502

483503
private async Task<string> SendGasless()

Assets/Thirdweb/Core/Scripts/Wallet.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using UnityEngine.Networking;
1414
using Thirdweb.Redcode.Awaiting;
1515
using Newtonsoft.Json;
16+
using Nethereum.RPC.Eth.DTOs;
1617

1718
#pragma warning disable CS0618
1819

@@ -853,6 +854,28 @@ public async Task<bool> IsDeployed()
853854
}
854855
}
855856

857+
public async Task<BigInteger> GetNonce(string blockTag = "pending")
858+
{
859+
var address = await GetAddress();
860+
if (Utils.IsWebGLBuild())
861+
{
862+
return await Bridge.GetNonce(address, blockTag);
863+
}
864+
else
865+
{
866+
var web3 = Utils.GetWeb3(_sdk.Session.ChainId, _sdk.Session.Options.clientId, _sdk.Session.Options.bundleId);
867+
var blockParameter =
868+
blockTag == "pending"
869+
? BlockParameter.CreatePending()
870+
: blockTag == "latest"
871+
? BlockParameter.CreateLatest()
872+
: blockTag == "earliest"
873+
? BlockParameter.CreateEarliest()
874+
: BlockParameter.CreatePending();
875+
return web3.Eth.Transactions.GetTransactionCount.SendRequestAsync(address, blockParameter).Result.Value;
876+
}
877+
}
878+
856879
/// <summary>
857880
/// Sends a raw transaction from the connected wallet.
858881
/// </summary>
@@ -873,7 +896,7 @@ public async Task<string> SendRawTransaction(TransactionRequest transactionReque
873896

874897
transactionRequest.from ??= await GetAddress();
875898

876-
var input = new Nethereum.RPC.Eth.DTOs.TransactionInput(
899+
var input = new TransactionInput(
877900
string.IsNullOrEmpty(transactionRequest.data) ? null : transactionRequest.data,
878901
transactionRequest.to,
879902
transactionRequest.from,

Assets/WebGLTemplates/Thirdweb/lib/thirdweb-unity-bridge.js

Lines changed: 114 additions & 114 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)