Skip to content

Commit

Permalink
Transaction.Populate & Cross-Platform Unification (#209)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xFirekeeper authored Jul 25, 2024
1 parent a2e2960 commit d6173d9
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 204 deletions.
23 changes: 23 additions & 0 deletions Assets/Thirdweb/Core/Plugin/thirdweb.jslib
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,29 @@ var plugin = {
dynCall_viii(cb, idPtr, null, buffer);
});
},
ThirdwebGetNonce: async function (taskId, address, blockTag, cb) {
// convert taskId from pointer to str and allocate it to keep in memory
var id = UTF8ToString(taskId);
var idSize = lengthBytesUTF8(id) + 1;
var idPtr = _malloc(idSize);
stringToUTF8(id, idPtr, idSize);
// execute bridge call
window.bridge
.getNonce(UTF8ToString(address), UTF8ToString(blockTag))
.then((returnStr) => {
var bufferSize = lengthBytesUTF8(returnStr) + 1;
var buffer = _malloc(bufferSize);
stringToUTF8(returnStr, buffer, bufferSize);
dynCall_viii(cb, idPtr, buffer, null);
})
.catch((err) => {
var msg = err.message;
var bufferSize = lengthBytesUTF8(msg) + 1;
var buffer = _malloc(bufferSize);
stringToUTF8(msg, buffer, bufferSize);
dynCall_viii(cb, idPtr, null, buffer);
});
},
ThirdwebResolveENSFromAddress: async function (taskId, address, cb) {
// convert taskId from pointer to str and allocate it to keep in memory
var id = UTF8ToString(taskId);
Expand Down
19 changes: 19 additions & 0 deletions Assets/Thirdweb/Core/Scripts/Bridge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,23 @@ public static async Task<bool> SmartWalletIsDeployed()
return JsonConvert.DeserializeObject<Result<bool>>(result).result;
}

public static async Task<int> GetNonce(string address, string blockTag)
{
if (!Utils.IsWebGLBuild())
{
ThirdwebDebug.LogWarning("Interacting with the thirdweb SDK is not fully supported in the editor.");
return -1;
}
string taskId = Guid.NewGuid().ToString();
var task = new TaskCompletionSource<string>();
taskMap[taskId] = task;
#if UNITY_WEBGL
ThirdwebGetNonce(taskId, address, blockTag, jsCallback);
#endif
string result = await task.Task;
return JsonConvert.DeserializeObject<Result<int>>(result).result;
}

public static async Task<string> ResolveENSFromAddress(string address)
{
if (!Utils.IsWebGLBuild())
Expand Down Expand Up @@ -508,6 +525,8 @@ public static async Task CopyBuffer(string text)
[DllImport("__Internal")]
private static extern string ThirdwebSmartWalletIsDeployed(string taskId, Action<string, string, string> cb);
[DllImport("__Internal")]
private static extern string ThirdwebGetNonce(string taskId, string address, string blockTag, Action<string, string, string> cb);
[DllImport("__Internal")]
private static extern string ThirdwebResolveENSFromAddress(string taskId, string address, Action<string, string, string> cb);
[DllImport("__Internal")]
private static extern string ThirdwebResolveAddressFromENS(string taskId, string ens, Action<string, string, string> cb);
Expand Down
35 changes: 5 additions & 30 deletions Assets/Thirdweb/Core/Scripts/Contract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,36 +104,11 @@ public async Task<CurrencyValue> GetBalance()
/// <returns>A <see cref="Transaction"/> object representing the prepared transaction.</returns>
public async Task<Transaction> Prepare(string functionName, params object[] args)
{
return await Prepare(functionName, null, args);
}

/// <summary>
/// Prepare a transaction by creating a <see cref="Transaction"/> object.
/// </summary>
/// <param name="functionName">The name of the contract function.</param>
/// <param name="from">The address to send the transaction from.</param>
/// <param name="args">Optional function arguments.</param>
/// <returns>A <see cref="Transaction"/> object representing the prepared transaction.</returns>
public async Task<Transaction> Prepare(string functionName, string from = null, params object[] args)
{
var initialInput = new TransactionInput();
if (Utils.IsWebGLBuild())
{
initialInput.From = from ?? await _sdk.Wallet.GetAddress();
initialInput.To = Address;
}
else
{
if (this.ABI == null)
this.ABI = await FetchAbi(this.Address, await _sdk.Wallet.GetChainId());

var web3 = Utils.GetWeb3(_sdk.Session.ChainId, _sdk.Session.Options.clientId, _sdk.Session.Options.bundleId);
var contract = web3.Eth.GetContract(this.ABI, this.Address);
var function = Utils.GetFunctionMatchSignature(contract, functionName, args);
var fromAddress = from ?? await _sdk.Wallet.GetAddress();
initialInput = function.CreateTransactionInput(fromAddress, args);
}

this.ABI ??= await FetchAbi(this.Address, await _sdk.Wallet.GetChainId());
var contract = new Nethereum.Contracts.Contract(null, this.ABI, this.Address);
var function = Utils.GetFunctionMatchSignature(contract, functionName, args);
var fromAddress = await _sdk.Wallet.GetAddress();
var initialInput = function.CreateTransactionInput(fromAddress, args);
return new Transaction(this, initialInput, functionName, args);
}

Expand Down
138 changes: 79 additions & 59 deletions Assets/Thirdweb/Core/Scripts/Transaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,21 @@ public Transaction(ThirdwebSDK sdk, TransactionInput txInput)
/// <returns>The JSON string representation of the transaction input.</returns>
public override string ToString()
{
return JsonConvert.SerializeObject(Input);
var readableInput = new
{
from = Input.From,
to = Input.To,
value = Input.Value?.Value.ToString(),
gas = Input.Gas?.Value.ToString(),
gasPrice = Input.GasPrice?.Value.ToString(),
data = Input.Data,
nonce = Input.Nonce?.Value.ToString(),
chainId = Input.ChainId?.Value.ToString(),
maxFeePerGas = Input.MaxFeePerGas?.Value.ToString(),
maxPriorityFeePerGas = Input.MaxPriorityFeePerGas?.Value.ToString(),
type = Input.Type?.Value.ToString()
};
return JsonConvert.SerializeObject(readableInput);
}

/// <summary>
Expand Down Expand Up @@ -206,17 +220,10 @@ public Transaction SetNonce(string nonce)
/// <returns>The modified <see cref="Transaction"/> object.</returns>
public Transaction SetArgs(params object[] args)
{
if (Utils.IsWebGLBuild())
{
this.FunctionArgs = args;
}
else
{
var web3 = Utils.GetWeb3(_sdk.Session.ChainId, _sdk.Session.Options.clientId, _sdk.Session.Options.bundleId);
var contract = web3.Eth.GetContract(Contract.ABI, Contract.Address);
var function = Utils.GetFunctionMatchSignature(contract, FunctionName, args);
Input.Data = function.GetData(args);
}
this.FunctionArgs = args;
var contract = new Nethereum.Contracts.Contract(null, Contract.ABI, Contract.Address);
var function = Utils.GetFunctionMatchSignature(contract, FunctionName, args);
Input.Data = function.GetData(args);
return this;
}

Expand All @@ -237,6 +244,18 @@ public async Task<BigInteger> GetGasPrice()
}
}

public async Task<GasPriceParameters> GetGasFees()
{
if (Utils.IsWebGLBuild())
{
return await Bridge.InvokeRoute<GasPriceParameters>(GetTxBuilderRoute("getGasFees"), Utils.ToJsonStringArray(Input, FunctionName, FunctionArgs));
}
else
{
return await Utils.GetGasPriceAsync(_sdk.Session.ChainId, _sdk.Session.Options.clientId, _sdk.Session.Options.bundleId);
}
}

/// <summary>
/// Estimates the gas limit for the transaction asynchronously.
/// </summary>
Expand Down Expand Up @@ -328,34 +347,57 @@ public async Task<string> Sign()
}

/// <summary>
/// Sends the transaction asynchronously.
/// Populates the transaction asynchronously, setting the gas limit, gas price, nonce, and other parameters.
/// </summary>
/// <param name="gasless">Specifies whether to send the transaction as a gasless transaction. Default is null (uses gasless if set up).</param>
/// <returns>The transaction hash as a string.</returns>
public async Task<string> Send(bool? gasless = null)
/// <returns>The prepared <see cref="Transaction"/> object.</returns>
/// <remarks> There is no guarantee the gas and nonce values will be preserved when using Account Abstraction.</remarks>
public async Task<Transaction> Populate()
{
if (Utils.IsWebGLBuild())
Input.Gas ??= new HexBigInteger(await EstimateGasLimit());

Input.Value ??= new HexBigInteger(0);

Input.Nonce ??= new HexBigInteger(await _sdk.Wallet.GetNonce());

var force1559 = Input.Type != null && Input.Type.HexValue == new HexBigInteger((int)TransactionType.EIP1559).HexValue;
var supports1559 = force1559 || (Input.Type == null && Utils.Supports1559(_sdk.Session.ChainId.ToString()));
if (supports1559)
{
if (gasless == null || gasless == false)
return await Send();
else
return await SendGasless();
if (Input.GasPrice == null)
{
var fees = await GetGasFees();
Input.MaxFeePerGas ??= new HexBigInteger(fees.MaxFeePerGas);
Input.MaxPriorityFeePerGas ??= new HexBigInteger(fees.MaxPriorityFeePerGas);
}
}
else
{
if (Input.Gas == null)
await EstimateAndSetGasLimitAsync();
if (Input.Value == null)
Input.Value = new HexBigInteger(0);
bool isGaslessSetup = _sdk.Session.Options.gasless.HasValue && !string.IsNullOrEmpty(_sdk.Session.Options.gasless?.engine.relayerUrl);
if (gasless != null && gasless.Value && !isGaslessSetup)
throw new UnityException("Gasless relayer transactions are not enabled. Please enable them in the SDK options.");
bool sendGaslessly = gasless == null ? isGaslessSetup : gasless.Value;
if (sendGaslessly)
return await SendGasless();
else
return await Send();
if (Input.MaxFeePerGas == null && Input.MaxPriorityFeePerGas == null)
{
ThirdwebDebug.Log("Using Legacy Gas Pricing");
var gasPrice = await GetGasPrice();
Input.GasPrice = new HexBigInteger(gasPrice);
}
}
return this;
}

/// <summary>
/// Sends the transaction asynchronously.
/// </summary>
/// <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>
/// <returns>The transaction hash as a string.</returns>
public async Task<string> Send(bool? gasless = null)
{
var tx = await Populate();
bool isGaslessSetup = _sdk.Session.Options.gasless.HasValue && !string.IsNullOrEmpty(_sdk.Session.Options.gasless?.engine.relayerUrl);
if (gasless != null && gasless.Value && !isGaslessSetup)
throw new UnityException("Gasless relayer transactions are not enabled. Please enable them in the SDK options.");
bool sendGaslessly = gasless == null ? isGaslessSetup : gasless.Value;
if (sendGaslessly)
return await tx.SendGasless();
else
return await tx.Send();
}

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

private async Task<string> Send()
{
string hash;
if (Utils.IsWebGLBuild())
{
return await Bridge.InvokeRoute<string>(GetTxBuilderRoute("send"), Utils.ToJsonStringArray(Input, FunctionName, FunctionArgs));
hash = await Bridge.InvokeRoute<string>(GetTxBuilderRoute("send"), Utils.ToJsonStringArray(Input, FunctionName, FunctionArgs));
}
else
{
var force1559 = Input.Type != null && Input.Type.HexValue == new HexBigInteger((int)TransactionType.EIP1559).HexValue;
var supports1559 = force1559 || (Input.Type == null && Utils.Supports1559(_sdk.Session.ChainId.ToString()));
if (supports1559)
{
if (Input.GasPrice == null)
{
var fees = await Utils.GetGasPriceAsync(_sdk.Session.ChainId, _sdk.Session.Options.clientId, _sdk.Session.Options.bundleId);
if (Input.MaxFeePerGas == null)
Input.MaxFeePerGas = new HexBigInteger(fees.MaxFeePerGas);
if (Input.MaxPriorityFeePerGas == null)
Input.MaxPriorityFeePerGas = new HexBigInteger(fees.MaxPriorityFeePerGas);
}
}
else
{
if (Input.MaxFeePerGas == null && Input.MaxPriorityFeePerGas == null)
{
ThirdwebDebug.Log("Using Legacy Gas Pricing");
Input.GasPrice = new HexBigInteger(await Utils.GetLegacyGasPriceAsync(_sdk.Session.ChainId, _sdk.Session.Options.clientId, _sdk.Session.Options.bundleId));
}
}

string hash;
if (_sdk.Session.ActiveWallet.GetSignerProvider() == WalletProvider.LocalWallet && _sdk.Session.ActiveWallet.GetProvider() != WalletProvider.SmartWallet)
{
hash = await _sdk.Session.Web3.Eth.TransactionManager.SendTransactionAsync(Input);
Expand All @@ -475,9 +495,9 @@ private async Task<string> Send()
var ethSendTx = new EthSendTransaction(_sdk.Session.Web3.Client);
hash = await ethSendTx.SendRequestAsync(Input);
}
ThirdwebDebug.Log($"Transaction hash: {hash}");
return hash;
}
ThirdwebDebug.Log($"Transaction hash: {hash}");
return hash;
}

private async Task<string> SendGasless()
Expand Down
25 changes: 24 additions & 1 deletion Assets/Thirdweb/Core/Scripts/Wallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using UnityEngine.Networking;
using Thirdweb.Redcode.Awaiting;
using Newtonsoft.Json;
using Nethereum.RPC.Eth.DTOs;

#pragma warning disable CS0618

Expand Down Expand Up @@ -853,6 +854,28 @@ public async Task<bool> IsDeployed()
}
}

public async Task<BigInteger> GetNonce(string blockTag = "pending")
{
var address = await GetAddress();
if (Utils.IsWebGLBuild())
{
return await Bridge.GetNonce(address, blockTag);
}
else
{
var web3 = Utils.GetWeb3(_sdk.Session.ChainId, _sdk.Session.Options.clientId, _sdk.Session.Options.bundleId);
var blockParameter =
blockTag == "pending"
? BlockParameter.CreatePending()
: blockTag == "latest"
? BlockParameter.CreateLatest()
: blockTag == "earliest"
? BlockParameter.CreateEarliest()
: BlockParameter.CreatePending();
return web3.Eth.Transactions.GetTransactionCount.SendRequestAsync(address, blockParameter).Result.Value;
}
}

/// <summary>
/// Sends a raw transaction from the connected wallet.
/// </summary>
Expand All @@ -873,7 +896,7 @@ public async Task<string> SendRawTransaction(TransactionRequest transactionReque

transactionRequest.from ??= await GetAddress();

var input = new Nethereum.RPC.Eth.DTOs.TransactionInput(
var input = new TransactionInput(
string.IsNullOrEmpty(transactionRequest.data) ? null : transactionRequest.data,
transactionRequest.to,
transactionRequest.from,
Expand Down
228 changes: 114 additions & 114 deletions Assets/WebGLTemplates/Thirdweb/lib/thirdweb-unity-bridge.js

Large diffs are not rendered by default.

0 comments on commit d6173d9

Please sign in to comment.