-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature/fetch real data for wallet hardcoded wallet address (#39)
* Rename a test and make another test less flaky by waiting longer for UI to update * Stubbed out NftContentFetcher - specifically the constructor * Rename a test and make another test less flaky by waiting longer for UI to update * Ripped out Nethereum. Prepare dependencies for package exporting * Wrap editor scripts in #if UNITY_EDITOR preprocessor defines so that they don't interfere with the build process * Remove unneeded code * Removed unneeded file * Make UI in sample scene scale based on the screen size * ContentFetcher implementation - including implementations for NftContentFetcher and TokenContentFetcher, which are essentially wrappers for ContentFetcher * Fetch real fungible tokens. * Added some logging to requests. Added error handling for HTTP requests sent by Indexer. Fixed error with content fetching by removing an unsupported Chain * Fix tests - were checking for mock fetchers in the wrong place * Fetch real nfts prototype * Move SpriteFetcher.cs * Finish fetching tokens for a chain first, then start processing collections asynchronously * Retry failed requests to indexer due to rate limits. Fix some bugs * Cleaner logging * Move to next chain when receiving errors fetching from indexer - handles case when the indexer is down for a given chain. This logs a warning and points to the indexer status page as a first thing to check * More informative logging * Fix some bugs with fetching nfts. Uncaught exceptions were causing the async messages to abort silently * Feed up fetch speed by adding nfts and tokens (fetching their images as well) to the lists in parallel * Conducted trials with different number of tokens fetched at once to figure out an ideal starting place for devs to work with * Fix broken UI tests * Fixed bug with MockTransactionDetailsFetcher that was causing theUnity to freeze (infinite loop) when clicking on any of the wallet elements and trying to open their info page * Destroy grid children on awake as opposed to everytime we open the walletpage * Refresh ContentFetcher when re-opening the WalletPage * Fixed bug where MockTransactionDetailsFetcher wasn't fetching transactions correctly * Use my thread-safe implementation of DelayTask as opposed to the default Task.Delay * Add basic end to end test for token and nft fetching and wallet page population. Fix memory leaks associated with web requests * Don't swallow error when converting JSON objects * Clean up ContentFetcher implementation * Don't display NFTs that have the default sprite as their image in the wallet - i.e. those where we couldn't fetch the image * Add icons in preparation for merge
- Loading branch information
1 parent
3d9b411
commit 64b231f
Showing
33 changed files
with
6,820 additions
and
17,569 deletions.
There are no files selected for viewing
2,758 changes: 2,477 additions & 281 deletions
2,758
Assets/SequenceExamples/Prefabs/WalletPanel.prefab
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,369 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Numerics; | ||
using System.Threading.Tasks; | ||
using Sequence.Utils; | ||
using UnityEngine; | ||
using UnityEngine.Networking; | ||
using Vector2 = UnityEngine.Vector2; | ||
|
||
namespace Sequence.Demo | ||
{ | ||
public class ContentFetcher : IContentFetcher | ||
{ | ||
private Queue<TokenElement> _tokenQueue = new Queue<TokenElement>(); | ||
private Queue<NftElement> _nftQueue = new Queue<NftElement>(); | ||
private List<Chain> _includeChains; | ||
private List<IIndexer> _indexers; | ||
private Address _address; | ||
private bool _more = true; | ||
private bool _isFetching = false; | ||
private Queue<TokenBalance>[] _collectionsToProcess; | ||
|
||
private int _chainIndex; | ||
|
||
public ContentFetcher(Address address, params Chain[] includeChains) | ||
{ | ||
_address = address; | ||
|
||
_includeChains = includeChains.ConvertToList(); | ||
|
||
_indexers = new List<IIndexer>(); | ||
int chains = _includeChains.Count; | ||
for (int i = 0; i < chains; i++) | ||
{ | ||
_indexers.Add(new ChainIndexer((int)_includeChains[i])); | ||
} | ||
|
||
_collectionsToProcess = new Queue<TokenBalance>[chains]; | ||
for (int i = 0; i < chains; i++) | ||
{ | ||
_collectionsToProcess[i] = new Queue<TokenBalance>(); | ||
} | ||
} | ||
|
||
public event Action<FetchContentResult> OnContentFetch; | ||
public event Action<CollectionProcessingResult> OnCollectionProcessing; | ||
|
||
public async Task FetchContent(int pageSize) | ||
{ | ||
_isFetching = true; | ||
_chainIndex = 0; | ||
int pageNumber = 0; | ||
int indexers = _indexers.Count; | ||
Debug.Log("Fetching content..."); | ||
while (_more) | ||
{ | ||
GetTokenBalancesArgs args = new GetTokenBalancesArgs( | ||
_address, | ||
true, | ||
new Page { page = pageNumber, pageSize = pageSize }); | ||
GetTokenBalancesReturn balances = await _indexers[_chainIndex].GetTokenBalances(args); | ||
if (balances == null) | ||
{ | ||
Debug.LogWarning( | ||
$"Received an error from indexer when fetching token balances with args: {args}\nCheck chain status here: https://status.sequence.info/"); | ||
|
||
pageNumber = 0; | ||
IncrementChainIndex(indexers); | ||
|
||
continue; | ||
} | ||
Page returnedPage = balances.page; | ||
AddTokensToQueues(balances.balances, _chainIndex); | ||
if (returnedPage.more) | ||
{ | ||
pageNumber = returnedPage.page; | ||
} | ||
else | ||
{ | ||
pageNumber = 0; | ||
IncrementChainIndex(indexers); | ||
} | ||
OnContentFetch?.Invoke(new FetchContentResult(balances.balances, _more)); | ||
} | ||
|
||
for (int i = 0; i < indexers; i++) | ||
{ | ||
await ProcessCollectionsFromChain(i, pageSize); | ||
} | ||
} | ||
|
||
private void IncrementChainIndex(int indexers) | ||
{ | ||
_chainIndex++; | ||
if (_chainIndex >= indexers) | ||
{ | ||
Debug.Log("No more chains to fetch from."); | ||
_more = false; | ||
} | ||
else | ||
{ | ||
Debug.Log($"Moving to next chain... {(Chain)(int)_indexers[_chainIndex].GetChainID()}"); | ||
} | ||
} | ||
|
||
private async Task AddTokensToQueues(TokenBalance[] tokenBalances, int indexerIndex) | ||
{ | ||
int items = tokenBalances.Length; | ||
for (int i = 0; i < items; i++) | ||
{ | ||
if (tokenBalances[i].IsToken()) | ||
{ | ||
TokenElement token = await BuildTokenElement(tokenBalances[i]); | ||
if (token != null) | ||
{ | ||
_tokenQueue.Enqueue(token); | ||
} | ||
}else if (tokenBalances[i].IsNft()) | ||
{ | ||
_collectionsToProcess[indexerIndex].Enqueue(tokenBalances[i]); | ||
} | ||
} | ||
} | ||
|
||
private async Task<TokenElement> BuildTokenElement(TokenBalance tokenBalance) | ||
{ | ||
Sprite tokenIconSprite = await FetchIconSprite(tokenBalance); | ||
|
||
ContractInfo contractInfo = tokenBalance.contractInfo; | ||
if (contractInfo == null) | ||
{ | ||
Debug.LogWarning($"No contractInfo found for given token: {tokenBalance}"); | ||
return null; | ||
} | ||
|
||
BigInteger balance = tokenBalance.balance / (BigInteger)Math.Pow(10, (int)contractInfo.decimals); | ||
|
||
try | ||
{ | ||
return new TokenElement(tokenBalance.contractAddress, tokenIconSprite, contractInfo.name, | ||
(Chain)(int)contractInfo.chainId, (uint)balance, contractInfo.symbol, | ||
new MockCurrencyConverter()); // Todo replace MockCurrencyConverter with real implementation | ||
} | ||
catch (Exception e) | ||
{ | ||
Debug.LogError($"Failed to build token element for token: {tokenBalance}\nError: {e.Message}"); | ||
return null; | ||
} | ||
} | ||
|
||
private async Task ProcessCollectionsFromChain(int chainIndex, int pageSize) | ||
{ | ||
Queue<TokenBalance> toProcess = _collectionsToProcess[chainIndex]; | ||
while (toProcess.TryDequeue(out TokenBalance tokenBalance)) | ||
{ | ||
Debug.Log($"Processing collections from {(Chain)(int)_indexers[chainIndex].GetChainID()}. Collections to process: {_collectionsToProcess[chainIndex].Count}"); | ||
await ProcessCollection(tokenBalance, _indexers[chainIndex], pageSize); | ||
} | ||
} | ||
|
||
private async Task ProcessCollection(TokenBalance tokenBalance, IIndexer indexer, int pageSize) | ||
{ | ||
bool more = true; | ||
int pageNumber = 0; | ||
int nftsFound = 0; | ||
while (more) | ||
{ | ||
GetTokenBalancesReturn balances = await indexer.GetTokenBalances( | ||
new GetTokenBalancesArgs( | ||
_address, | ||
tokenBalance.contractAddress, | ||
true, | ||
new Page { page = pageNumber, pageSize = pageSize })); | ||
if (balances == null) | ||
{ | ||
Debug.LogError($"Failed to finish processing collection: {tokenBalance}"); | ||
break; | ||
} | ||
Page returnedPage = balances.page; | ||
if (returnedPage.more) | ||
{ | ||
pageNumber = returnedPage.page; | ||
} | ||
else | ||
{ | ||
more = false; | ||
} | ||
|
||
nftsFound += balances.balances.Length; | ||
|
||
OnCollectionProcessing?.Invoke(new CollectionProcessingResult(balances.balances, more)); | ||
AddNftsToQueue(balances.balances); | ||
} | ||
} | ||
|
||
private async Task AddNftsToQueue(TokenBalance[] tokenBalances) | ||
{ | ||
int items = tokenBalances.Length; | ||
for (int i = 0; i < items; i++) | ||
{ | ||
if (tokenBalances[i].IsNft()) | ||
{ | ||
NftElement nft = await BuildNftElement(tokenBalances[i]); | ||
if (nft != null) | ||
{ | ||
_nftQueue.Enqueue(nft); | ||
} | ||
} | ||
else | ||
{ | ||
Debug.LogError($"Only ERC721/ERC1155s should be provided to this method! Given {tokenBalances[i]}"); | ||
} | ||
} | ||
} | ||
|
||
private async Task<NftElement> BuildNftElement(TokenBalance tokenBalance) | ||
{ | ||
Sprite collectionIconSprite = await FetchIconSprite(tokenBalance); | ||
Sprite nftIconSprite = await FetchNftImageSprite(tokenBalance); | ||
|
||
ContractInfo contractInfo = tokenBalance.contractInfo; | ||
if (contractInfo == null) | ||
{ | ||
Debug.LogWarning($"No contractInfo found for given token: {tokenBalance}"); | ||
return null; | ||
} | ||
|
||
TokenMetadata metadata = tokenBalance.tokenMetadata; | ||
if (metadata == null) | ||
{ | ||
Debug.LogWarning($"No metadata found for given token: {tokenBalance}"); | ||
return null; | ||
} | ||
|
||
try | ||
{ | ||
BigInteger balance = tokenBalance.balance; | ||
if (contractInfo.decimals != 0) | ||
{ | ||
balance = tokenBalance.balance / (BigInteger)Math.Pow(10, (int)contractInfo.decimals); | ||
} | ||
return new NftElement(new Address(tokenBalance.contractAddress), nftIconSprite, metadata.name, | ||
collectionIconSprite, contractInfo.name, metadata.tokenId, (Chain)(int)contractInfo.chainId, | ||
(uint)balance, 1, | ||
new MockCurrencyConverter()); // Todo replace MockCurrencyConverter with real implementation | ||
// Todo figure out ethValue | ||
} | ||
catch (Exception e) | ||
{ | ||
Debug.LogWarning($"Failed to build NFT element for token: {tokenBalance}\nError: {e.Message}"); | ||
return null; | ||
} | ||
} | ||
|
||
private async Task<Sprite> FetchIconSprite(TokenBalance tokenBalance) | ||
{ | ||
string metadataUrl = ""; | ||
ContractInfo contractInfo = tokenBalance.contractInfo; | ||
if (contractInfo != null && contractInfo.logoURI != null && contractInfo.logoURI.Length > 0) | ||
{ | ||
metadataUrl = contractInfo.logoURI; | ||
} | ||
else | ||
{ | ||
Debug.LogWarning($"No metadata URL found for given token: {tokenBalance}"); | ||
} | ||
|
||
Sprite iconSprite = await SpriteFetcher.Fetch(metadataUrl); | ||
return iconSprite; | ||
} | ||
|
||
private async Task<Sprite> FetchNftImageSprite(TokenBalance tokenBalance) | ||
{ | ||
string metadataUrl = ""; | ||
TokenMetadata metadata = tokenBalance.tokenMetadata; | ||
if (metadata != null && metadata.image != null && metadata.image.Length > 0) | ||
{ | ||
metadataUrl = metadata.image; | ||
} | ||
else | ||
{ | ||
Debug.Log($"No metadata URL found for given token: {tokenBalance}"); | ||
} | ||
|
||
Sprite iconSprite = await SpriteFetcher.Fetch(metadataUrl); | ||
return iconSprite; | ||
} | ||
|
||
public async Task<FetchTokenContentResult> FetchTokenContent(int maxToFetch) | ||
{ | ||
int tokensFetched = _tokenQueue.Count; | ||
while (tokensFetched < maxToFetch && _more) | ||
{ | ||
if (!_isFetching) | ||
{ | ||
FetchContent(maxToFetch); | ||
} | ||
await Task.Yield(); | ||
tokensFetched = _tokenQueue.Count; | ||
} | ||
|
||
TokenElement[] tokens = new TokenElement[tokensFetched]; | ||
for (int i = 0; i < tokensFetched; i++) | ||
{ | ||
tokens[i] = _tokenQueue.Dequeue(); | ||
} | ||
|
||
return new FetchTokenContentResult(tokens, _more || _tokenQueue.Count > 0); | ||
} | ||
|
||
public async Task<FetchNftContentResult> FetchNftContent(int maxToFetch) | ||
{ | ||
int nftsFetched = _nftQueue.Count; | ||
while (nftsFetched < maxToFetch && (_more || CollectionsLeftToProcess())) | ||
{ | ||
if (!_isFetching) | ||
{ | ||
FetchContent(maxToFetch); | ||
} | ||
await Task.Yield(); | ||
nftsFetched = _nftQueue.Count; | ||
} | ||
NftElement[] nfts = new NftElement[nftsFetched]; | ||
for (int i = 0; i < nftsFetched; i++) | ||
{ | ||
nfts[i] = _nftQueue.Dequeue(); | ||
} | ||
|
||
return new FetchNftContentResult(nfts, _more || _nftQueue.Count > 0 || CollectionsLeftToProcess()); | ||
} | ||
|
||
public Address GetAddress() | ||
{ | ||
return _address; | ||
} | ||
|
||
public void RefreshTokens() | ||
{ | ||
_tokenQueue = new Queue<TokenElement>(); | ||
_more = true; | ||
_isFetching = false; | ||
} | ||
|
||
public void RefreshNfts() | ||
{ | ||
_nftQueue = new Queue<NftElement>(); | ||
int chains = _includeChains.Count; | ||
_collectionsToProcess = new Queue<TokenBalance>[chains]; | ||
for (int i = 0; i < chains; i++) | ||
{ | ||
_collectionsToProcess[i] = new Queue<TokenBalance>(); | ||
} | ||
} | ||
|
||
private bool CollectionsLeftToProcess() | ||
{ | ||
int chains = _collectionsToProcess.Length; | ||
for (int i = 0; i < chains; i++) | ||
{ | ||
if (_collectionsToProcess[i].Count > 0) | ||
{ | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.