Skip to content

Commit 437716a

Browse files
committed
Use Multicall3 for ERC721.GetAll, ERC1155.GetAll, ERC1155.GetOwned when available
1 parent dff75c9 commit 437716a

File tree

3 files changed

+145
-35
lines changed

3 files changed

+145
-35
lines changed

Assets/Thirdweb/Core/Scripts/ERC1155.cs

Lines changed: 88 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -85,21 +85,49 @@ public async Task<List<NFT>> GetAll(QueryAllParams queryParams = null)
8585
else
8686
{
8787
int totalCount = await TotalCount();
88-
int start;
89-
int end;
90-
if (queryParams != null)
88+
int start = queryParams?.start ?? 0;
89+
int count = queryParams?.count + 1 ?? totalCount;
90+
int end = Math.Min(start + count, totalCount);
91+
List<NFT> allNfts = new();
92+
try
9193
{
92-
start = queryParams.start;
93-
end = queryParams.start + queryParams.count;
94+
var uriFunctions = Enumerable.Range(start, end - start).Select(i => new TokenERC1155Contract.UriFunction() { TokenId = new BigInteger(i) }).ToArray();
95+
var uriResults = await TransactionManager.ThirdwebMulticallRead<TokenERC1155Contract.UriFunction, TokenERC1155Contract.UriOutputDTO>(contractAddress, uriFunctions);
96+
var metadataFetchTasks = new List<Task<NFTMetadata>>();
97+
for (int i = 0; i < uriResults.Length; i++)
98+
{
99+
var tokenUri = uriResults[i].ReturnValue1.Replace("0x{id}", uriFunctions[i].TokenId.ToString()).ReplaceIPFS();
100+
metadataFetchTasks.Add(ThirdwebManager.Instance.SDK.storage.DownloadText<NFTMetadata>(tokenUri));
101+
}
102+
var metadataResults = await Task.WhenAll(metadataFetchTasks);
103+
allNfts = new List<NFT>();
104+
for (int i = 0; i < uriResults.Length; i++)
105+
{
106+
var tokenId = uriFunctions[i].TokenId.ToString();
107+
var metadata = metadataResults[i];
108+
metadata.image = metadata.image.ReplaceIPFS();
109+
metadata.id = tokenId;
110+
metadata.uri = uriResults[i].ReturnValue1.ReplaceIPFS();
111+
112+
var nft = new NFT
113+
{
114+
owner = "",
115+
type = "ERC1155",
116+
supply = await TotalSupply(tokenId),
117+
quantityOwned = 404,
118+
metadata = metadata
119+
};
120+
121+
allNfts.Add(nft);
122+
}
94123
}
95-
else
124+
catch
96125
{
97-
start = 0;
98-
end = totalCount - 1;
126+
ThirdwebDebug.LogWarning("Unable to fetch using Multicall3, likely not deployed on this chain, falling back to single queries.");
127+
allNfts = new List<NFT>();
128+
for (int i = start; i <= end; i++)
129+
allNfts.Add(await Get(i.ToString()));
99130
}
100-
var allNfts = new List<NFT>();
101-
for (int i = start; i <= end; i++)
102-
allNfts.Add(await Get(i.ToString()));
103131
return allNfts;
104132
}
105133
}
@@ -118,20 +146,59 @@ public async Task<List<NFT>> GetOwned(string address = null)
118146
{
119147
string owner = address ?? await ThirdwebManager.Instance.SDK.wallet.GetAddress();
120148
int totalCount = await TotalCount();
121-
var ownedNfts = new List<NFT>();
122-
for (int i = 0; i < totalCount; i++)
149+
List<NFT> ownedNfts = new();
150+
151+
try
123152
{
124-
BigInteger ownedBalance = BigInteger.Parse(await BalanceOf(owner, i.ToString()));
125-
if (ownedBalance == 0)
153+
var balanceFunctions = Enumerable.Range(0, totalCount).Select(i => new TokenERC1155Contract.BalanceOfFunction() { Account = owner, Id = new BigInteger(i) }).ToArray();
154+
var balanceResults = await TransactionManager.ThirdwebMulticallRead<TokenERC1155Contract.BalanceOfFunction, TokenERC1155Contract.BalanceOfOutputDTO>(
155+
contractAddress,
156+
balanceFunctions
157+
);
158+
var nonZeroBalanceTokenIds = balanceResults.Select((result, index) => (Balance: result.ReturnValue1, TokenId: index)).Where(x => x.Balance > 0).ToList();
159+
var uriFunctions = nonZeroBalanceTokenIds.Select(x => new TokenERC1155Contract.UriFunction() { TokenId = new BigInteger(x.TokenId) }).ToArray();
160+
var uriResults = await TransactionManager.ThirdwebMulticallRead<TokenERC1155Contract.UriFunction, TokenERC1155Contract.UriOutputDTO>(contractAddress, uriFunctions);
161+
var metadataFetchTasks = uriResults.Select(uriResult => ThirdwebManager.Instance.SDK.storage.DownloadText<NFTMetadata>(uriResult.ReturnValue1.ReplaceIPFS())).ToList();
162+
var metadataResults = await Task.WhenAll(metadataFetchTasks);
163+
ownedNfts = new List<NFT>();
164+
for (int i = 0; i < nonZeroBalanceTokenIds.Count; i++)
126165
{
127-
continue;
166+
var tokenId = nonZeroBalanceTokenIds[i].TokenId.ToString();
167+
var balance = nonZeroBalanceTokenIds[i].Balance;
168+
var metadata = metadataResults[i];
169+
metadata.image = metadata.image.ReplaceIPFS();
170+
metadata.id = tokenId;
171+
metadata.uri = uriResults[i].ReturnValue1.ReplaceIPFS();
172+
173+
ownedNfts.Add(
174+
new NFT
175+
{
176+
owner = owner,
177+
type = "ERC1155",
178+
supply = await TotalSupply(tokenId),
179+
quantityOwned = (int)balance,
180+
metadata = metadata
181+
}
182+
);
128183
}
129-
else
184+
}
185+
catch
186+
{
187+
ThirdwebDebug.LogWarning("Unable to fetch using Multicall3, likely not deployed on this chain, falling back to single queries.");
188+
for (int i = 0; i < totalCount; i++)
130189
{
131-
NFT tempNft = await Get(i.ToString());
132-
tempNft.owner = owner;
133-
tempNft.quantityOwned = (int)ownedBalance;
134-
ownedNfts.Add(tempNft);
190+
BigInteger ownedBalance = BigInteger.Parse(await BalanceOf(owner, i.ToString()));
191+
if (ownedBalance == 0)
192+
{
193+
continue;
194+
}
195+
else
196+
{
197+
NFT tempNft = await Get(i.ToString());
198+
tempNft.owner = owner;
199+
tempNft.quantityOwned = (int)ownedBalance;
200+
ownedNfts.Add(tempNft);
201+
}
135202
}
136203
}
137204
return ownedNfts;

Assets/Thirdweb/Core/Scripts/ERC721.cs

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using DropERC721Contract = Thirdweb.Contracts.DropERC721.ContractDefinition;
99
using ERC721AQueryable = Thirdweb.Contracts.ERC721AQueryableUpgradeable.ContractDefinition;
1010
using SignatureDropContract = Thirdweb.Contracts.SignatureDrop.ContractDefinition;
11+
using System.Linq;
1112

1213
namespace Thirdweb
1314
{
@@ -85,23 +86,48 @@ public async Task<List<NFT>> GetAll(QueryAllParams queryParams = null)
8586
}
8687
else
8788
{
88-
int totalSupply = await TotalCount();
89-
int start;
90-
int end;
91-
if (queryParams != null)
89+
int totalCount = await TotalCount();
90+
int start = queryParams?.start ?? 0;
91+
int count = queryParams?.count + 1 ?? totalCount;
92+
int end = Math.Min(start + count, totalCount);
93+
List<NFT> allNfts = new();
94+
try
9295
{
93-
start = queryParams.start;
94-
end = queryParams.start + queryParams.count;
96+
var uriFunctions = Enumerable.Range(start, end - start).Select(i => new TokenERC721Contract.TokenURIFunction() { TokenId = new BigInteger(i) }).ToArray();
97+
var uriResults = await TransactionManager.ThirdwebMulticallRead<TokenERC721Contract.TokenURIFunction, TokenERC721Contract.TokenURIOutputDTO>(contractAddress, uriFunctions);
98+
var metadataFetchTasks = new List<Task<NFTMetadata>>();
99+
for (int i = 0; i < uriResults.Length; i++)
100+
{
101+
var tokenUri = uriResults[i].ReturnValue1.Replace("0x{id}", uriFunctions[i].TokenId.ToString()).ReplaceIPFS();
102+
metadataFetchTasks.Add(ThirdwebManager.Instance.SDK.storage.DownloadText<NFTMetadata>(tokenUri));
103+
}
104+
var metadataResults = await Task.WhenAll(metadataFetchTasks);
105+
allNfts = new List<NFT>();
106+
for (int i = 0; i < metadataResults.Length; i++)
107+
{
108+
var tokenId = uriFunctions[i].TokenId.ToString();
109+
var metadata = metadataResults[i];
110+
metadata.image = metadata.image.ReplaceIPFS();
111+
metadata.id = tokenId;
112+
metadata.uri = uriResults[i].ReturnValue1.ReplaceIPFS();
113+
var nft = new NFT
114+
{
115+
owner = await OwnerOf(tokenId),
116+
type = "ERC721",
117+
supply = 1,
118+
quantityOwned = 1,
119+
metadata = metadata
120+
};
121+
122+
allNfts.Add(nft);
123+
}
95124
}
96-
else
125+
catch
97126
{
98-
start = 0;
99-
end = totalSupply - 1;
127+
ThirdwebDebug.LogWarning("Unable to fetch using Multicall3, likely not deployed on this chain, falling back to single queries.");
128+
for (int i = start; i <= end; i++)
129+
allNfts.Add(await Get(i.ToString()));
100130
}
101-
102-
var allNfts = new List<NFT>();
103-
for (int i = start; i <= end; i++)
104-
allNfts.Add(await Get(i.ToString()));
105131
return allNfts;
106132
}
107133
}

Assets/Thirdweb/Core/Scripts/TransactionManager.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88
using Newtonsoft.Json;
99
using Thirdweb.Contracts.Forwarder.ContractDefinition;
1010
using Nethereum.RPC.Eth.Transactions;
11-
using Nethereum.Web3;
1211
using Thirdweb.Redcode.Awaiting;
12+
using Nethereum.ABI.FunctionEncoding.Attributes;
13+
using Nethereum.Contracts.QueryHandlers.MultiCall;
14+
using System.Collections.Generic;
15+
using System.Linq;
1316

1417
#pragma warning disable CS0618
1518

@@ -39,6 +42,20 @@ public static async Task<TWResult> ThirdwebRead<TWFunction, TWResult>(string con
3942
return await queryHandler.QueryAsync<TWResult>(contractAddress, functionMessage);
4043
}
4144

45+
public static async Task<TWResult[]> ThirdwebMulticallRead<TWFunction, TWResult>(string contractAddress, TWFunction[] functionMessages)
46+
where TWFunction : FunctionMessage, new()
47+
where TWResult : IFunctionOutputDTO, new()
48+
{
49+
MultiQueryHandler multiqueryHandler = Utils.GetWeb3().Eth.GetMultiQueryHandler();
50+
var calls = new List<MulticallInputOutput<TWFunction, TWResult>>();
51+
for (int i = 0; i < functionMessages.Length; i++)
52+
{
53+
calls.Add(new MulticallInputOutput<TWFunction, TWResult>(functionMessages[i], contractAddress));
54+
}
55+
var results = await multiqueryHandler.MultiCallAsync(MultiQueryHandler.DEFAULT_CALLS_PER_REQUEST, calls.ToArray()).ConfigureAwait(false);
56+
return calls.Select(x => x.Output).ToArray();
57+
}
58+
4259
public static async Task<TransactionResult> ThirdwebWrite<TWFunction>(string contractAddress, TWFunction functionMessage, BigInteger? weiValue = null, BigInteger? gasOverride = null)
4360
where TWFunction : FunctionMessage, new()
4461
{

0 commit comments

Comments
 (0)