thanks for the clarification !
What if we build a unity webgl dapp set to run in background ?
Hello again. Thanks for all your help on this. Weâre still struggling to figure out what we might be doing wrong. The code works perfectly in the Unity Editor with the build target set to PC / Mac / Linux stand-alone, but not with WebGL as the build target (both as an actual build and in the editor).
What we see when we run with WebGL as the build target is the following:
- Two new records are created in the DB but are not updated or deleted.
- There is a NullReferenceException thrown on line 108 of UniversalWebClient.cs
- None of the Debug.Log($"***** âŚ") statements appear in the console (although we do see several WebRequest entries in the log).
Hereâs the final test code based on what you posted with just a few minor adjustments to the WebGL portions. (Also, to make the compiler happy, we had to put the MoralisLiveQueryController.cs and MoralisSubscriptionQuery.cs in the WebGL namespace):
Thank you!
using Assets.Scripts;
using MoralisWeb3ApiSdk;
using System;
using UnityEngine;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
#if UNITY_WEBGL
using Cysharp.Threading.Tasks;
using Moralis.WebGL;
using Moralis.WebGL.Platform.Objects;
using Moralis.WebGL.Platform.Queries;
using Moralis.WebGL.Platform.Queries.Live;
#else
using Moralis;
using Moralis.Platform.Objects;
using Moralis.Platform.Queries;
using Moralis.Platform.Queries.Live;
#endif
public class LiveQueryTest
{
#if UNITY_WEBGL
public async void TestLiveQuery()
{
var moralisQueryPlayerData = await MoralisInterface.GetClient().Query<PlayerData>();
// Setup subscription
setupLiveQuerySubscription(moralisQueryPlayerData);
Thread.Sleep(2000);
#else
public async void TestLiveQuery()
{
var moralisQueryPlayerData = MoralisInterface.GetClient().Query<PlayerData>();
// Setup subscription
setupLiveQuerySubscription(moralisQueryPlayerData);
Thread.Sleep(2000);
#endif
System.Random rand = new System.Random((int)DateTime.Now.Ticks);
int x = rand.Next(25) + 3;
PlayerData p1 = MoralisInterface.GetClient().Create<PlayerData>();
p1.Name = GetTestName();
p1.TokenCount = x;
await p1.SaveAsync();
x = rand.Next(25) + 3;
PlayerData p2 = MoralisInterface.GetClient().Create<PlayerData>();
p2.Name = GetTestName();
p2.TokenCount = x;
await p2.SaveAsync();
// Get the records created
IEnumerable<PlayerData> recs = await moralisQueryPlayerData.FindAsync();
// Update data
foreach (PlayerData pd in recs)
{
x = rand.Next(25) + 3;
pd.TokenCount = x;
await pd.SaveAsync();
}
// Delete data
foreach (PlayerData pd in recs)
{
await pd.DeleteAsync();
}
}
private void setupLiveQuerySubscription(MoralisQuery<PlayerData> playerData)
{
MoralisLiveQueryCallbacks<PlayerData> moralisLiveQueryCallbacks = new MoralisLiveQueryCallbacks<PlayerData>();
moralisLiveQueryCallbacks.OnConnectedEvent += (() => { Debug.Log("Connection Established."); });
moralisLiveQueryCallbacks.OnSubscribedEvent += ((requestId) => { Debug.Log($"Subscription {requestId} created."); });
moralisLiveQueryCallbacks.OnUnsubscribedEvent += ((requestId) => { Debug.Log($"Unsubscribed from {requestId}."); });
moralisLiveQueryCallbacks.OnErrorEvent += ((ErrorMessage em) =>
{
Debug.Log($"***** ERROR: code: {em.code}, msg: {em.error}, requestId: {em.requestId}");
});
moralisLiveQueryCallbacks.OnCreateEvent += ((item, requestId) =>
{
Debug.Log($"***** Created ");
});
moralisLiveQueryCallbacks.OnUpdateEvent += ((item, requestId) =>
{
Debug.Log($"***** Updated ");
});
moralisLiveQueryCallbacks.OnDeleteEvent += ((item, requestId) =>
{
Debug.Log($"***** Deleted");
});
moralisLiveQueryCallbacks.OnGeneralMessageEvent += ((text) =>
{
Debug.Log($"***** Websocket message: {text}");
});
MoralisLiveQueryController.AddSubscription<PlayerData>("PlayerData", playerData, moralisLiveQueryCallbacks);
}
private static string GetTestName()
{
string[] names = { "Clem the Great", "Sion the Bold", "Bob", "D@ve", "Oogmar the Deft", "Alesdair the Blessed", "Seviel the Mighty", "Master Adept Xactant", "Semaphore the Beautiful", "Gamemaster Nexnang" };
System.Random rand = new System.Random((int)DateTime.Now.Ticks);
int x = rand.Next(names.Length);
x = rand.Next(names.Length);
return names[x];
}
}
** Software Versions Used
Unity 2020.3.24f1
Moralis Ethereum Unity Boilerplate v1.0.8
Hi there,
took this from the git :
// No parameters
object[] pars = new object[0];
string jsonResult = await f.CallAsync(pars);
show an error :
Argument 1: cannot convert from âobject[]â to âNethereum.RPC.Eth.DTOs.CallInputâ
what am I missing ?
Which Web3API operation are you trying to call?
I will check this out âŚ
Based on this statement you included, while I am setting this up, please check the logs on your server to see if there were any errors (esp. table permissions for update / delete)
- So it looks like create and update were working fine as was the delete. However it looks like for the Delete function the response is null with a status of 200 but the WebGL UniversalWebClient did not handle that properly.
I need to create an update for this. To get you by for now, in the WebGL UniversalWebClient make these two changes:
A) Change line 94 to
string responseText = "{}";
B) Change line 108 to:
responseText = webRequest.downloadHandler == null ? responseText : webRequest.downloadHandler.text;
- The Callbacks are not firing for WebGL, digging into that next.
Hey, some of the readme are out dated
what are you trying to do ?
OK - just checked the logs & they look good. Nothing in there from yesterday or today, just a few unrelated errors that were logged on the 6th. Thanks!
Thank you very much!
Arf Ok !
I am struggling hard to communicate with my marketplace smart contract.
It works well on remix, everything seems ready. But I canât get it to work.
I made a listing of NFTs (with token price and IDs) and trying to fetch infos.
This âoffersâ function takes in one input (the listing ID) and returns the token price that should be displayed within unity.
More generally I need to understand how to use the RunContractFunction
. And the readme is very unclear to me.
Thanks for your support.
Is it on a test network?
If so, please provide the chain, the function ABI, the contract address and a listing ID for which you expect to receive data?
Thank you,
David
itâs on mumbai test net.
The abi is this:
[
{
"inputs": [
{
"internalType": "address",
"name": "_nftCollection",
"type": "address"
},
{
"internalType": "address",
"name": "_tokenCollection",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "ClaimFunds",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "offerId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "token_id",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "price",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bool",
"name": "fulfilled",
"type": "bool"
}
],
"name": "Offer",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "offerId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "OfferCancelled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "offerId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OfferFilled",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"stateMutability": "nonpayable",
"type": "fallback"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_offerId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "price",
"type": "uint256"
}
],
"name": "BuyNFT",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "_tokenURI",
"type": "string"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "price",
"type": "uint256"
}
],
"name": "SetPrice",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "Withdraw",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "offerCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "offers",
"outputs": [
{
"internalType": "uint256",
"name": "offerId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "id",
"type": "uint256"
},
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "uint256",
"name": "price",
"type": "uint256"
},
{
"internalType": "bool",
"name": "fulfilled",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256[]",
"name": "",
"type": "uint256[]"
},
{
"internalType": "uint256[]",
"name": "",
"type": "uint256[]"
},
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"name": "onERC1155BatchReceived",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "",
"type": "bytes"
}
],
"name": "onERC1155Received",
"outputs": [
{
"internalType": "bytes4",
"name": "",
"type": "bytes4"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes4",
"name": "interfaceId",
"type": "bytes4"
}
],
"name": "supportsInterface",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tokenCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
The contract address is : 0x383cAe6B39ad82305242EFcfDa6EC5B2a52B4620
and listing function is called âOffersâ with ID = 1.
Thank you so very much!
First - an issue I see is that the single input for the offers function does not have a name.
I used the contract address you sent on etherscan to find the contract and decompile it. This function decompiles to
def offers(uint256 _param1) payable:
require calldata.size - 4 >=Ⲡ32
require _param1 == _param1
return offers[_param1].field_0,
offers[_param1].field_256,
offers[_param1].field_512,
offers[_param1].field_768,
bool(offers[_param1].field_1024)
_param1 is not the actual name. Guessing based on this statement:
That _param1 is âidâ. Is this correct?
Thanks a lot for the clarification:
That _param1 is indeed the âofferIdâ.
Here is how you will call:
// Function ABI input parameters
object[] inputParams = new object[1];
inputParams[0] = new { internalType = "uint256", name = "id", type = "uint256" };
// Function ABI Output parameters
object[] outputParams = new object[5];
outputParams[0] = new { internalType = "uint256", name = "offerId", type = "uint256" };
outputParams[1] = new { internalType = "uint256", name = "id", type = "uint256" };
outputParams[2] = new { internalType = "address", name = "user", type = "address" };
outputParams[3] = new { internalType = "uint256", name = "price", type = "uint256" };
outputParams[4] = new { internalType = "bool", name = "fulfilled", type = "bool" };
// Function ABI
object[] abi = new object[1];
abi[0] = new { inputs = inputParams, name = "offers", outputs = outputParams, stateMutability = "view", type = "function" };
// Define request object
RunContractDto rcd = new RunContractDto()
{
Abi = abi,
Params = new { id = "1" }
};
object resp = await MoralisInterface.GetClient().Web3Api.Native.RunContractFunction("0x383cAe6B39ad82305242EFcfDa6EC5B2a52B4620", "offers", rcd, ChainList.mumbai);
However I found an issue with the call for functions that return multiple items. Created an update. About to push v1.0.9 that contains this fix and a few other outstanding defects and feature requests. Check the github repo in about 30 minutes (currently 17:34 UTC)
EDITED: Release 1.0.9 has been published.
Woooow it really works !!! :))))) So powerful Thank you so very much !!
Question1: So If I want to fetch only one output parameter from those 5 (lets say âpriceâ), I would have to read through the string output ?
Question2: When I try to use a custom write function like âIncreaseAllowanceâ this error shows :
ApiException: Error calling RunContractFunction: {âcodeâ:141,âerrorâ:âOnly read functions are allowedâ}
The readme says that it âRuns a given function of a contract abi and returns readonly dataâ, is there a way to change that ? I see in the git that there is a GetTokenAllowance function, but is there a way to Set the token allowance ?
Most importantly, I have a custom BuyNFT function in my smart contract that returns the same error (I want users to buy NFTs with my custom ERC20).
Thanks again for the amazing work and support!
Since the result is json you can either use a JsonReader to extract values or (what I prefer) create a response object and deserialize the json to that object. Infact this may be a good future feature, a generic version that allows you to call this as
RunContractFunction<MyResultObject>( ...
Also I think another good feature would be to allow the consumer to pass in the ABI as JSON (since that is how it is usually provided) instead of going through the process of defining the inputs, outputs, etc.
That is correct for the Web3API - only read methods can be called this way. Write functions need to go through Wallet Connect or Web3. The cade in the demo (AwaradableController.cs) provides a good example of how to do this. Here is the relevant code but see the class for the inputs, etc.:
#if UNITY_WEBGL
// Convert token id to hex as this is what the contract call expects
object[] pars = new object[] { bi.ToString() };
// Set gas estimate
HexBigInteger gas = new HexBigInteger(0);
string resp = await MoralisInterface.ExecuteFunction(Constants.MUG_CONTRACT_ADDRESS, Constants.MUG_ABI, Constants.MUG_CLAIM_FUNCTION, pars, new HexBigInteger("0x0"), gas, gas);
#else
// Convert token id to hex as this is what the contract call expects
object[] pars = new object[] { bi.ToString("x") };
// Set gas estimate
HexBigInteger gas = new HexBigInteger(0);
// Call the contract to claim the NFT reward.
string resp = await MoralisInterface.SendEvmTransactionAsync("Rewards", "mumbai", "claimReward", addr, gas, new HexBigInteger("0x0"), pars);
#endif
FYI - The format of the non-WebGL code will be updated to match the format of the WebGL code in the near future.
I know that we can call a contract function using this method
// Calling: function balanceOf(address account, uint256 id) external view returns (uint256);
// Function ABI input parameters
object[] inputParams = new object[1];
inputParams[0] = new { internalType = "address", name = "account", type = "address" };
// Function ABI Output parameters
object[] outputParams = new object[1];
outputParams[0] = new { internalType = "uint256", name = "", type = "uint256" };
// Function ABI
object[] abi = new object[1];
abi[0] = new { inputs = inputParams, name = "balanceOf", outputs = outputParams, stateMutability = "view", type = "function" };
// Define request object
string accountAddress , ERC20ContractAddress;
RunContractDto rcd = new RunContractDto()
{
Abi = abi,
Params = new { account = accountAddress }
};
string resp = await MoralisInterface.GetClient().Web3Api.Native.RunContractFunction(ERC20ContractAddress, "balanceOf", rcd, ChainList.mumbai);
But how can I use array[] as input and get array[] as output for a read-only function? Like this;
function isWhiteListed(address[] calldata addr) external view returns (uint256[] memory result);