From 2f54e02f0500f283d667cd24ad3082adadd6a39b Mon Sep 17 00:00:00 2001 From: Sebastian Pape <0xneo11@gmail.com> Date: Tue, 22 Oct 2024 20:58:37 +0200 Subject: [PATCH] v7.4.1: fix race conditions --- dist/esm/index.evm.js | 156 +++++++++++++++-------------- dist/esm/index.js | 156 +++++++++++++++-------------- dist/esm/index.solana.js | 156 +++++++++++++++-------------- dist/umd/index.evm.js | 156 +++++++++++++++-------------- dist/umd/index.js | 156 +++++++++++++++-------------- dist/umd/index.solana.js | 156 +++++++++++++++-------------- package.evm.json | 6 +- package.json | 14 +-- package.solana.json | 6 +- src/dripAssets.js | 153 +++++++++++++++------------- tests/units/dripAssets.evm.spec.js | 99 +++++++++++++----- tests/units/dripAssets.spec.js | 99 +++++++++++++----- yarn.lock | 35 ++++--- 13 files changed, 756 insertions(+), 592 deletions(-) diff --git a/dist/esm/index.evm.js b/dist/esm/index.evm.js index aff1b54..2b56756 100644 --- a/dist/esm/index.evm.js +++ b/dist/esm/index.evm.js @@ -130,44 +130,32 @@ const sortPriorities = (priorities, a,b)=>{ return 0 }; +const promiseWithTimeout = (promise, timeout = 10000) => { + return Promise.race([ + promise, + new Promise((resolve) => setTimeout(() => resolve(null), timeout)) // Resolve with null on timeout + ]); +}; + var dripAssets = async (options) => { if(options === undefined) { options = { accounts: {}, priority: [] }; } let assets = []; let dripped = []; let promises = []; - let priorities = _optionalChain([options, 'optionalAccess', _ => _.priority, 'optionalAccess', _2 => _2.map, 'call', _3 => _3((priority)=>[priority.blockchain, priority.address.toLowerCase()].join(''))]); - let drippedIndex = 0; - let dripQueue = []; + let priorities = Array.isArray(options.priority) ? options.priority.map(priority => [priority.blockchain, priority.address.toLowerCase()].join('')) : []; - const drip = (asset, recursive = true)=>{ - if(typeof options.drip !== 'function') { return } + const drip = (asset)=>{ + if (!asset || typeof options.drip !== 'function') { return } // Ensure asset is valid const assetAsKey = [asset.blockchain, asset.address.toLowerCase()].join(''); if(dripped.indexOf(assetAsKey) > -1) { return } - if(priorities && priorities.length && priorities.indexOf(assetAsKey) === drippedIndex) { - dripped.push(assetAsKey); - options.drip(asset); - drippedIndex += 1; - if(!recursive){ return } - dripQueue.forEach((asset)=>drip(asset, false)); - } else if(!priorities || priorities.length === 0 || drippedIndex >= priorities.length) { - if(!priorities || priorities.length === 0 || priorities.indexOf(assetAsKey) === -1) { - dripped.push(assetAsKey); - options.drip(asset); - } else if (drippedIndex >= priorities.length) { - dripped.push(assetAsKey); - options.drip(asset); - } - } else if(!dripQueue.find((queued)=>queued.blockchain === asset.blockchain && queued.address.toLowerCase() === asset.address.toLowerCase())) { - dripQueue.push(asset); - dripQueue.sort((a,b)=>sortPriorities(priorities, a, b)); - } + dripped.push(assetAsKey); + options.drip(asset); }; // Prioritized Assets - promises = promises.concat((options.priority || []).map((asset)=>{ - return new Promise(async (resolve, reject)=>{ + return promiseWithTimeout(new Promise(async (resolve) => { try { let token = new Token(asset); let completedAsset = Object.assign({}, @@ -181,85 +169,105 @@ var dripAssets = async (options) => { } ); if(completedAsset.balance != '0') { - if(exists({ assets, asset })) { return resolve() } + if(exists({ assets, asset })) { return resolve(null) } // Resolve with null if already exists assets.push(completedAsset); - drip(completedAsset); resolve(completedAsset); } else { - resolve(); + resolve(null); } - } catch (e) { - resolve(); + } catch (error) { + console.error('Error fetching prioritized asset:', asset, error); + resolve(null); // Resolve with null to prevent blocking } - }) + })) })); - Promise.all(promises).then(()=>{ - drippedIndex = _optionalChain([priorities, 'optionalAccess', _4 => _4.length]) || 0; - dripQueue.forEach((asset)=>drip(asset, false)); - }); - + // Major Tokens - let majorTokens = []; for (var blockchain in options.accounts){ Blockchains.findByName(blockchain).tokens.forEach((token)=>{ if(isFiltered({ options, address: token.address, blockchain })){ return } - if(_optionalChain([options, 'optionalAccess', _5 => _5.priority, 'optionalAccess', _6 => _6.find, 'call', _7 => _7((priority)=>priority.blockchain === blockchain && priority.address.toLowerCase() === token.address.toLowerCase())])){ return } + if(_optionalChain([options, 'optionalAccess', _ => _.priority, 'optionalAccess', _2 => _2.find, 'call', _3 => _3((priority)=>priority.blockchain === blockchain && priority.address.toLowerCase() === token.address.toLowerCase())])){ return } majorTokens.push(Object.assign({}, token, { blockchain })); }); } + promises = promises.concat((majorTokens.map((asset)=>{ - return new Promise((resolve, reject)=>{ + return promiseWithTimeout(new Promise((resolve) => { new Token(asset).balance(options.accounts[asset.blockchain]) .then((balance)=>{ - if(exists({ assets, asset })) { return resolve() } + if(exists({ assets, asset })) { return resolve(null) } // Resolve with null if already exists const assetWithBalance = reduceAssetWithBalance(asset, balance); if(assetWithBalance.balance != '0') { assets.push(assetWithBalance); - drip(assetWithBalance); resolve(assetWithBalance); } else { - resolve(); - }}).catch(()=>{ resolve(); }); - }) + resolve(null); + }}).catch((error)=>{ + console.error('Error fetching major token balance:', asset, error); + resolve(null); // Resolve with null on error + }); + })) }))); // All other assets - if(options.only == undefined || Object.keys(options.only).every((list)=>list.length == 0)) { - let allAssets = await getAssets(options); - promises = promises.concat((allAssets.map((asset)=>{ - return new Promise((resolve, reject)=>{ - const token = new Token(asset); - return token.balance(options.accounts[asset.blockchain]) - .then(async(balance)=>{ - if(exists({ assets, asset })) { return resolve() } - const assetWithBalance = reduceAssetWithBalance(asset, balance); - if(assetWithBalance.balance != '0') { - if(assetWithBalance.name === undefined) { - assetWithBalance.name = await token.name(); - } - if(assetWithBalance.symbol === undefined) { - assetWithBalance.symbol = await token.symbol(); - } - if(assetWithBalance.decimals === undefined) { - assetWithBalance.decimals = await token.decimals(); - } - assets.push(assetWithBalance); - drip(assetWithBalance); - resolve(assetWithBalance); - } else { - resolve(); - }}).catch(()=>{ resolve(); }) - }) - }))); + try { + let allAssets = await getAssets(options); + promises = promises.concat((allAssets.map((asset)=>{ + return promiseWithTimeout(new Promise((resolve) => { + const token = new Token(asset); + return token.balance(options.accounts[asset.blockchain]) + .then(async(balance)=>{ + if(exists({ assets, asset })) { return resolve(null) } // Resolve with null if already exists + const assetWithBalance = reduceAssetWithBalance(asset, balance); + if(assetWithBalance.balance != '0') { + if(assetWithBalance.name === undefined) { + assetWithBalance.name = await token.name(); + } + if(assetWithBalance.symbol === undefined) { + assetWithBalance.symbol = await token.symbol(); + } + if(assetWithBalance.decimals === undefined) { + assetWithBalance.decimals = await token.decimals(); + } + assets.push(assetWithBalance); + resolve(assetWithBalance); + } else { + resolve(null); + }}).catch((error)=>{ + console.error('Error fetching asset balance:', asset, error); + resolve(null); // Resolve with null on error + }) + })) + }))); + } catch (error) { + console.error('Error fetching all assets:', error); + } } - await Promise.all(promises); + // Ensure all promises are resolved + const resolvedAssets = await Promise.all(promises); - assets.sort((a,b)=>sortPriorities(priorities, a, b)); + // Drip prioritized assets first in their defined order + priorities.forEach(priorityKey => { + const asset = resolvedAssets.find(a => a && [a.blockchain, a.address.toLowerCase()].join('') === priorityKey); // Ensure valid asset + if (asset) { + drip(asset); + } + }); + + // Drip non-prioritized assets + resolvedAssets.forEach(asset => { + if (!asset) return // Ensure valid asset + const assetKey = [asset.blockchain, asset.address.toLowerCase()].join(''); + if (!priorities.includes(assetKey)) { + drip(asset); + } + }); - dripQueue.forEach((asset)=>drip(asset, false)); + // Sort assets based on priorities before returning + assets.sort((a,b)=>sortPriorities(priorities, a, b)); return assets }; diff --git a/dist/esm/index.js b/dist/esm/index.js index 2bc0cc7..34504a1 100644 --- a/dist/esm/index.js +++ b/dist/esm/index.js @@ -130,44 +130,32 @@ const sortPriorities = (priorities, a,b)=>{ return 0 }; +const promiseWithTimeout = (promise, timeout = 10000) => { + return Promise.race([ + promise, + new Promise((resolve) => setTimeout(() => resolve(null), timeout)) // Resolve with null on timeout + ]); +}; + var dripAssets = async (options) => { if(options === undefined) { options = { accounts: {}, priority: [] }; } let assets = []; let dripped = []; let promises = []; - let priorities = _optionalChain([options, 'optionalAccess', _ => _.priority, 'optionalAccess', _2 => _2.map, 'call', _3 => _3((priority)=>[priority.blockchain, priority.address.toLowerCase()].join(''))]); - let drippedIndex = 0; - let dripQueue = []; + let priorities = Array.isArray(options.priority) ? options.priority.map(priority => [priority.blockchain, priority.address.toLowerCase()].join('')) : []; - const drip = (asset, recursive = true)=>{ - if(typeof options.drip !== 'function') { return } + const drip = (asset)=>{ + if (!asset || typeof options.drip !== 'function') { return } // Ensure asset is valid const assetAsKey = [asset.blockchain, asset.address.toLowerCase()].join(''); if(dripped.indexOf(assetAsKey) > -1) { return } - if(priorities && priorities.length && priorities.indexOf(assetAsKey) === drippedIndex) { - dripped.push(assetAsKey); - options.drip(asset); - drippedIndex += 1; - if(!recursive){ return } - dripQueue.forEach((asset)=>drip(asset, false)); - } else if(!priorities || priorities.length === 0 || drippedIndex >= priorities.length) { - if(!priorities || priorities.length === 0 || priorities.indexOf(assetAsKey) === -1) { - dripped.push(assetAsKey); - options.drip(asset); - } else if (drippedIndex >= priorities.length) { - dripped.push(assetAsKey); - options.drip(asset); - } - } else if(!dripQueue.find((queued)=>queued.blockchain === asset.blockchain && queued.address.toLowerCase() === asset.address.toLowerCase())) { - dripQueue.push(asset); - dripQueue.sort((a,b)=>sortPriorities(priorities, a, b)); - } + dripped.push(assetAsKey); + options.drip(asset); }; // Prioritized Assets - promises = promises.concat((options.priority || []).map((asset)=>{ - return new Promise(async (resolve, reject)=>{ + return promiseWithTimeout(new Promise(async (resolve) => { try { let token = new Token(asset); let completedAsset = Object.assign({}, @@ -181,85 +169,105 @@ var dripAssets = async (options) => { } ); if(completedAsset.balance != '0') { - if(exists({ assets, asset })) { return resolve() } + if(exists({ assets, asset })) { return resolve(null) } // Resolve with null if already exists assets.push(completedAsset); - drip(completedAsset); resolve(completedAsset); } else { - resolve(); + resolve(null); } - } catch (e) { - resolve(); + } catch (error) { + console.error('Error fetching prioritized asset:', asset, error); + resolve(null); // Resolve with null to prevent blocking } - }) + })) })); - Promise.all(promises).then(()=>{ - drippedIndex = _optionalChain([priorities, 'optionalAccess', _4 => _4.length]) || 0; - dripQueue.forEach((asset)=>drip(asset, false)); - }); - + // Major Tokens - let majorTokens = []; for (var blockchain in options.accounts){ Blockchains.findByName(blockchain).tokens.forEach((token)=>{ if(isFiltered({ options, address: token.address, blockchain })){ return } - if(_optionalChain([options, 'optionalAccess', _5 => _5.priority, 'optionalAccess', _6 => _6.find, 'call', _7 => _7((priority)=>priority.blockchain === blockchain && priority.address.toLowerCase() === token.address.toLowerCase())])){ return } + if(_optionalChain([options, 'optionalAccess', _ => _.priority, 'optionalAccess', _2 => _2.find, 'call', _3 => _3((priority)=>priority.blockchain === blockchain && priority.address.toLowerCase() === token.address.toLowerCase())])){ return } majorTokens.push(Object.assign({}, token, { blockchain })); }); } + promises = promises.concat((majorTokens.map((asset)=>{ - return new Promise((resolve, reject)=>{ + return promiseWithTimeout(new Promise((resolve) => { new Token(asset).balance(options.accounts[asset.blockchain]) .then((balance)=>{ - if(exists({ assets, asset })) { return resolve() } + if(exists({ assets, asset })) { return resolve(null) } // Resolve with null if already exists const assetWithBalance = reduceAssetWithBalance(asset, balance); if(assetWithBalance.balance != '0') { assets.push(assetWithBalance); - drip(assetWithBalance); resolve(assetWithBalance); } else { - resolve(); - }}).catch(()=>{ resolve(); }); - }) + resolve(null); + }}).catch((error)=>{ + console.error('Error fetching major token balance:', asset, error); + resolve(null); // Resolve with null on error + }); + })) }))); // All other assets - if(options.only == undefined || Object.keys(options.only).every((list)=>list.length == 0)) { - let allAssets = await getAssets(options); - promises = promises.concat((allAssets.map((asset)=>{ - return new Promise((resolve, reject)=>{ - const token = new Token(asset); - return token.balance(options.accounts[asset.blockchain]) - .then(async(balance)=>{ - if(exists({ assets, asset })) { return resolve() } - const assetWithBalance = reduceAssetWithBalance(asset, balance); - if(assetWithBalance.balance != '0') { - if(assetWithBalance.name === undefined) { - assetWithBalance.name = await token.name(); - } - if(assetWithBalance.symbol === undefined) { - assetWithBalance.symbol = await token.symbol(); - } - if(assetWithBalance.decimals === undefined) { - assetWithBalance.decimals = await token.decimals(); - } - assets.push(assetWithBalance); - drip(assetWithBalance); - resolve(assetWithBalance); - } else { - resolve(); - }}).catch(()=>{ resolve(); }) - }) - }))); + try { + let allAssets = await getAssets(options); + promises = promises.concat((allAssets.map((asset)=>{ + return promiseWithTimeout(new Promise((resolve) => { + const token = new Token(asset); + return token.balance(options.accounts[asset.blockchain]) + .then(async(balance)=>{ + if(exists({ assets, asset })) { return resolve(null) } // Resolve with null if already exists + const assetWithBalance = reduceAssetWithBalance(asset, balance); + if(assetWithBalance.balance != '0') { + if(assetWithBalance.name === undefined) { + assetWithBalance.name = await token.name(); + } + if(assetWithBalance.symbol === undefined) { + assetWithBalance.symbol = await token.symbol(); + } + if(assetWithBalance.decimals === undefined) { + assetWithBalance.decimals = await token.decimals(); + } + assets.push(assetWithBalance); + resolve(assetWithBalance); + } else { + resolve(null); + }}).catch((error)=>{ + console.error('Error fetching asset balance:', asset, error); + resolve(null); // Resolve with null on error + }) + })) + }))); + } catch (error) { + console.error('Error fetching all assets:', error); + } } - await Promise.all(promises); + // Ensure all promises are resolved + const resolvedAssets = await Promise.all(promises); - assets.sort((a,b)=>sortPriorities(priorities, a, b)); + // Drip prioritized assets first in their defined order + priorities.forEach(priorityKey => { + const asset = resolvedAssets.find(a => a && [a.blockchain, a.address.toLowerCase()].join('') === priorityKey); // Ensure valid asset + if (asset) { + drip(asset); + } + }); + + // Drip non-prioritized assets + resolvedAssets.forEach(asset => { + if (!asset) return // Ensure valid asset + const assetKey = [asset.blockchain, asset.address.toLowerCase()].join(''); + if (!priorities.includes(assetKey)) { + drip(asset); + } + }); - dripQueue.forEach((asset)=>drip(asset, false)); + // Sort assets based on priorities before returning + assets.sort((a,b)=>sortPriorities(priorities, a, b)); return assets }; diff --git a/dist/esm/index.solana.js b/dist/esm/index.solana.js index 381b516..f81c68f 100644 --- a/dist/esm/index.solana.js +++ b/dist/esm/index.solana.js @@ -130,44 +130,32 @@ const sortPriorities = (priorities, a,b)=>{ return 0 }; +const promiseWithTimeout = (promise, timeout = 10000) => { + return Promise.race([ + promise, + new Promise((resolve) => setTimeout(() => resolve(null), timeout)) // Resolve with null on timeout + ]); +}; + var dripAssets = async (options) => { if(options === undefined) { options = { accounts: {}, priority: [] }; } let assets = []; let dripped = []; let promises = []; - let priorities = _optionalChain([options, 'optionalAccess', _ => _.priority, 'optionalAccess', _2 => _2.map, 'call', _3 => _3((priority)=>[priority.blockchain, priority.address.toLowerCase()].join(''))]); - let drippedIndex = 0; - let dripQueue = []; + let priorities = Array.isArray(options.priority) ? options.priority.map(priority => [priority.blockchain, priority.address.toLowerCase()].join('')) : []; - const drip = (asset, recursive = true)=>{ - if(typeof options.drip !== 'function') { return } + const drip = (asset)=>{ + if (!asset || typeof options.drip !== 'function') { return } // Ensure asset is valid const assetAsKey = [asset.blockchain, asset.address.toLowerCase()].join(''); if(dripped.indexOf(assetAsKey) > -1) { return } - if(priorities && priorities.length && priorities.indexOf(assetAsKey) === drippedIndex) { - dripped.push(assetAsKey); - options.drip(asset); - drippedIndex += 1; - if(!recursive){ return } - dripQueue.forEach((asset)=>drip(asset, false)); - } else if(!priorities || priorities.length === 0 || drippedIndex >= priorities.length) { - if(!priorities || priorities.length === 0 || priorities.indexOf(assetAsKey) === -1) { - dripped.push(assetAsKey); - options.drip(asset); - } else if (drippedIndex >= priorities.length) { - dripped.push(assetAsKey); - options.drip(asset); - } - } else if(!dripQueue.find((queued)=>queued.blockchain === asset.blockchain && queued.address.toLowerCase() === asset.address.toLowerCase())) { - dripQueue.push(asset); - dripQueue.sort((a,b)=>sortPriorities(priorities, a, b)); - } + dripped.push(assetAsKey); + options.drip(asset); }; // Prioritized Assets - promises = promises.concat((options.priority || []).map((asset)=>{ - return new Promise(async (resolve, reject)=>{ + return promiseWithTimeout(new Promise(async (resolve) => { try { let token = new Token(asset); let completedAsset = Object.assign({}, @@ -181,85 +169,105 @@ var dripAssets = async (options) => { } ); if(completedAsset.balance != '0') { - if(exists({ assets, asset })) { return resolve() } + if(exists({ assets, asset })) { return resolve(null) } // Resolve with null if already exists assets.push(completedAsset); - drip(completedAsset); resolve(completedAsset); } else { - resolve(); + resolve(null); } - } catch (e) { - resolve(); + } catch (error) { + console.error('Error fetching prioritized asset:', asset, error); + resolve(null); // Resolve with null to prevent blocking } - }) + })) })); - Promise.all(promises).then(()=>{ - drippedIndex = _optionalChain([priorities, 'optionalAccess', _4 => _4.length]) || 0; - dripQueue.forEach((asset)=>drip(asset, false)); - }); - + // Major Tokens - let majorTokens = []; for (var blockchain in options.accounts){ Blockchains.findByName(blockchain).tokens.forEach((token)=>{ if(isFiltered({ options, address: token.address, blockchain })){ return } - if(_optionalChain([options, 'optionalAccess', _5 => _5.priority, 'optionalAccess', _6 => _6.find, 'call', _7 => _7((priority)=>priority.blockchain === blockchain && priority.address.toLowerCase() === token.address.toLowerCase())])){ return } + if(_optionalChain([options, 'optionalAccess', _ => _.priority, 'optionalAccess', _2 => _2.find, 'call', _3 => _3((priority)=>priority.blockchain === blockchain && priority.address.toLowerCase() === token.address.toLowerCase())])){ return } majorTokens.push(Object.assign({}, token, { blockchain })); }); } + promises = promises.concat((majorTokens.map((asset)=>{ - return new Promise((resolve, reject)=>{ + return promiseWithTimeout(new Promise((resolve) => { new Token(asset).balance(options.accounts[asset.blockchain]) .then((balance)=>{ - if(exists({ assets, asset })) { return resolve() } + if(exists({ assets, asset })) { return resolve(null) } // Resolve with null if already exists const assetWithBalance = reduceAssetWithBalance(asset, balance); if(assetWithBalance.balance != '0') { assets.push(assetWithBalance); - drip(assetWithBalance); resolve(assetWithBalance); } else { - resolve(); - }}).catch(()=>{ resolve(); }); - }) + resolve(null); + }}).catch((error)=>{ + console.error('Error fetching major token balance:', asset, error); + resolve(null); // Resolve with null on error + }); + })) }))); // All other assets - if(options.only == undefined || Object.keys(options.only).every((list)=>list.length == 0)) { - let allAssets = await getAssets(options); - promises = promises.concat((allAssets.map((asset)=>{ - return new Promise((resolve, reject)=>{ - const token = new Token(asset); - return token.balance(options.accounts[asset.blockchain]) - .then(async(balance)=>{ - if(exists({ assets, asset })) { return resolve() } - const assetWithBalance = reduceAssetWithBalance(asset, balance); - if(assetWithBalance.balance != '0') { - if(assetWithBalance.name === undefined) { - assetWithBalance.name = await token.name(); - } - if(assetWithBalance.symbol === undefined) { - assetWithBalance.symbol = await token.symbol(); - } - if(assetWithBalance.decimals === undefined) { - assetWithBalance.decimals = await token.decimals(); - } - assets.push(assetWithBalance); - drip(assetWithBalance); - resolve(assetWithBalance); - } else { - resolve(); - }}).catch(()=>{ resolve(); }) - }) - }))); + try { + let allAssets = await getAssets(options); + promises = promises.concat((allAssets.map((asset)=>{ + return promiseWithTimeout(new Promise((resolve) => { + const token = new Token(asset); + return token.balance(options.accounts[asset.blockchain]) + .then(async(balance)=>{ + if(exists({ assets, asset })) { return resolve(null) } // Resolve with null if already exists + const assetWithBalance = reduceAssetWithBalance(asset, balance); + if(assetWithBalance.balance != '0') { + if(assetWithBalance.name === undefined) { + assetWithBalance.name = await token.name(); + } + if(assetWithBalance.symbol === undefined) { + assetWithBalance.symbol = await token.symbol(); + } + if(assetWithBalance.decimals === undefined) { + assetWithBalance.decimals = await token.decimals(); + } + assets.push(assetWithBalance); + resolve(assetWithBalance); + } else { + resolve(null); + }}).catch((error)=>{ + console.error('Error fetching asset balance:', asset, error); + resolve(null); // Resolve with null on error + }) + })) + }))); + } catch (error) { + console.error('Error fetching all assets:', error); + } } - await Promise.all(promises); + // Ensure all promises are resolved + const resolvedAssets = await Promise.all(promises); - assets.sort((a,b)=>sortPriorities(priorities, a, b)); + // Drip prioritized assets first in their defined order + priorities.forEach(priorityKey => { + const asset = resolvedAssets.find(a => a && [a.blockchain, a.address.toLowerCase()].join('') === priorityKey); // Ensure valid asset + if (asset) { + drip(asset); + } + }); + + // Drip non-prioritized assets + resolvedAssets.forEach(asset => { + if (!asset) return // Ensure valid asset + const assetKey = [asset.blockchain, asset.address.toLowerCase()].join(''); + if (!priorities.includes(assetKey)) { + drip(asset); + } + }); - dripQueue.forEach((asset)=>drip(asset, false)); + // Sort assets based on priorities before returning + assets.sort((a,b)=>sortPriorities(priorities, a, b)); return assets }; diff --git a/dist/umd/index.evm.js b/dist/umd/index.evm.js index 2ca4902..69176d0 100644 --- a/dist/umd/index.evm.js +++ b/dist/umd/index.evm.js @@ -137,44 +137,32 @@ return 0 }; + const promiseWithTimeout = (promise, timeout = 10000) => { + return Promise.race([ + promise, + new Promise((resolve) => setTimeout(() => resolve(null), timeout)) // Resolve with null on timeout + ]); + }; + var dripAssets = async (options) => { if(options === undefined) { options = { accounts: {}, priority: [] }; } let assets = []; let dripped = []; let promises = []; - let priorities = _optionalChain([options, 'optionalAccess', _ => _.priority, 'optionalAccess', _2 => _2.map, 'call', _3 => _3((priority)=>[priority.blockchain, priority.address.toLowerCase()].join(''))]); - let drippedIndex = 0; - let dripQueue = []; + let priorities = Array.isArray(options.priority) ? options.priority.map(priority => [priority.blockchain, priority.address.toLowerCase()].join('')) : []; - const drip = (asset, recursive = true)=>{ - if(typeof options.drip !== 'function') { return } + const drip = (asset)=>{ + if (!asset || typeof options.drip !== 'function') { return } // Ensure asset is valid const assetAsKey = [asset.blockchain, asset.address.toLowerCase()].join(''); if(dripped.indexOf(assetAsKey) > -1) { return } - if(priorities && priorities.length && priorities.indexOf(assetAsKey) === drippedIndex) { - dripped.push(assetAsKey); - options.drip(asset); - drippedIndex += 1; - if(!recursive){ return } - dripQueue.forEach((asset)=>drip(asset, false)); - } else if(!priorities || priorities.length === 0 || drippedIndex >= priorities.length) { - if(!priorities || priorities.length === 0 || priorities.indexOf(assetAsKey) === -1) { - dripped.push(assetAsKey); - options.drip(asset); - } else if (drippedIndex >= priorities.length) { - dripped.push(assetAsKey); - options.drip(asset); - } - } else if(!dripQueue.find((queued)=>queued.blockchain === asset.blockchain && queued.address.toLowerCase() === asset.address.toLowerCase())) { - dripQueue.push(asset); - dripQueue.sort((a,b)=>sortPriorities(priorities, a, b)); - } + dripped.push(assetAsKey); + options.drip(asset); }; // Prioritized Assets - promises = promises.concat((options.priority || []).map((asset)=>{ - return new Promise(async (resolve, reject)=>{ + return promiseWithTimeout(new Promise(async (resolve) => { try { let token = new Token__default['default'](asset); let completedAsset = Object.assign({}, @@ -188,85 +176,105 @@ } ); if(completedAsset.balance != '0') { - if(exists({ assets, asset })) { return resolve() } + if(exists({ assets, asset })) { return resolve(null) } // Resolve with null if already exists assets.push(completedAsset); - drip(completedAsset); resolve(completedAsset); } else { - resolve(); + resolve(null); } - } catch (e) { - resolve(); + } catch (error) { + console.error('Error fetching prioritized asset:', asset, error); + resolve(null); // Resolve with null to prevent blocking } - }) + })) })); - Promise.all(promises).then(()=>{ - drippedIndex = _optionalChain([priorities, 'optionalAccess', _4 => _4.length]) || 0; - dripQueue.forEach((asset)=>drip(asset, false)); - }); - + // Major Tokens - let majorTokens = []; for (var blockchain in options.accounts){ Blockchains__default['default'].findByName(blockchain).tokens.forEach((token)=>{ if(isFiltered({ options, address: token.address, blockchain })){ return } - if(_optionalChain([options, 'optionalAccess', _5 => _5.priority, 'optionalAccess', _6 => _6.find, 'call', _7 => _7((priority)=>priority.blockchain === blockchain && priority.address.toLowerCase() === token.address.toLowerCase())])){ return } + if(_optionalChain([options, 'optionalAccess', _ => _.priority, 'optionalAccess', _2 => _2.find, 'call', _3 => _3((priority)=>priority.blockchain === blockchain && priority.address.toLowerCase() === token.address.toLowerCase())])){ return } majorTokens.push(Object.assign({}, token, { blockchain })); }); } + promises = promises.concat((majorTokens.map((asset)=>{ - return new Promise((resolve, reject)=>{ + return promiseWithTimeout(new Promise((resolve) => { new Token__default['default'](asset).balance(options.accounts[asset.blockchain]) .then((balance)=>{ - if(exists({ assets, asset })) { return resolve() } + if(exists({ assets, asset })) { return resolve(null) } // Resolve with null if already exists const assetWithBalance = reduceAssetWithBalance(asset, balance); if(assetWithBalance.balance != '0') { assets.push(assetWithBalance); - drip(assetWithBalance); resolve(assetWithBalance); } else { - resolve(); - }}).catch(()=>{ resolve(); }); - }) + resolve(null); + }}).catch((error)=>{ + console.error('Error fetching major token balance:', asset, error); + resolve(null); // Resolve with null on error + }); + })) }))); // All other assets - if(options.only == undefined || Object.keys(options.only).every((list)=>list.length == 0)) { - let allAssets = await getAssets(options); - promises = promises.concat((allAssets.map((asset)=>{ - return new Promise((resolve, reject)=>{ - const token = new Token__default['default'](asset); - return token.balance(options.accounts[asset.blockchain]) - .then(async(balance)=>{ - if(exists({ assets, asset })) { return resolve() } - const assetWithBalance = reduceAssetWithBalance(asset, balance); - if(assetWithBalance.balance != '0') { - if(assetWithBalance.name === undefined) { - assetWithBalance.name = await token.name(); - } - if(assetWithBalance.symbol === undefined) { - assetWithBalance.symbol = await token.symbol(); - } - if(assetWithBalance.decimals === undefined) { - assetWithBalance.decimals = await token.decimals(); - } - assets.push(assetWithBalance); - drip(assetWithBalance); - resolve(assetWithBalance); - } else { - resolve(); - }}).catch(()=>{ resolve(); }) - }) - }))); + try { + let allAssets = await getAssets(options); + promises = promises.concat((allAssets.map((asset)=>{ + return promiseWithTimeout(new Promise((resolve) => { + const token = new Token__default['default'](asset); + return token.balance(options.accounts[asset.blockchain]) + .then(async(balance)=>{ + if(exists({ assets, asset })) { return resolve(null) } // Resolve with null if already exists + const assetWithBalance = reduceAssetWithBalance(asset, balance); + if(assetWithBalance.balance != '0') { + if(assetWithBalance.name === undefined) { + assetWithBalance.name = await token.name(); + } + if(assetWithBalance.symbol === undefined) { + assetWithBalance.symbol = await token.symbol(); + } + if(assetWithBalance.decimals === undefined) { + assetWithBalance.decimals = await token.decimals(); + } + assets.push(assetWithBalance); + resolve(assetWithBalance); + } else { + resolve(null); + }}).catch((error)=>{ + console.error('Error fetching asset balance:', asset, error); + resolve(null); // Resolve with null on error + }) + })) + }))); + } catch (error) { + console.error('Error fetching all assets:', error); + } } - await Promise.all(promises); + // Ensure all promises are resolved + const resolvedAssets = await Promise.all(promises); - assets.sort((a,b)=>sortPriorities(priorities, a, b)); + // Drip prioritized assets first in their defined order + priorities.forEach(priorityKey => { + const asset = resolvedAssets.find(a => a && [a.blockchain, a.address.toLowerCase()].join('') === priorityKey); // Ensure valid asset + if (asset) { + drip(asset); + } + }); + + // Drip non-prioritized assets + resolvedAssets.forEach(asset => { + if (!asset) return // Ensure valid asset + const assetKey = [asset.blockchain, asset.address.toLowerCase()].join(''); + if (!priorities.includes(assetKey)) { + drip(asset); + } + }); - dripQueue.forEach((asset)=>drip(asset, false)); + // Sort assets based on priorities before returning + assets.sort((a,b)=>sortPriorities(priorities, a, b)); return assets }; diff --git a/dist/umd/index.js b/dist/umd/index.js index a1f61fb..f8768de 100644 --- a/dist/umd/index.js +++ b/dist/umd/index.js @@ -137,44 +137,32 @@ return 0 }; + const promiseWithTimeout = (promise, timeout = 10000) => { + return Promise.race([ + promise, + new Promise((resolve) => setTimeout(() => resolve(null), timeout)) // Resolve with null on timeout + ]); + }; + var dripAssets = async (options) => { if(options === undefined) { options = { accounts: {}, priority: [] }; } let assets = []; let dripped = []; let promises = []; - let priorities = _optionalChain([options, 'optionalAccess', _ => _.priority, 'optionalAccess', _2 => _2.map, 'call', _3 => _3((priority)=>[priority.blockchain, priority.address.toLowerCase()].join(''))]); - let drippedIndex = 0; - let dripQueue = []; + let priorities = Array.isArray(options.priority) ? options.priority.map(priority => [priority.blockchain, priority.address.toLowerCase()].join('')) : []; - const drip = (asset, recursive = true)=>{ - if(typeof options.drip !== 'function') { return } + const drip = (asset)=>{ + if (!asset || typeof options.drip !== 'function') { return } // Ensure asset is valid const assetAsKey = [asset.blockchain, asset.address.toLowerCase()].join(''); if(dripped.indexOf(assetAsKey) > -1) { return } - if(priorities && priorities.length && priorities.indexOf(assetAsKey) === drippedIndex) { - dripped.push(assetAsKey); - options.drip(asset); - drippedIndex += 1; - if(!recursive){ return } - dripQueue.forEach((asset)=>drip(asset, false)); - } else if(!priorities || priorities.length === 0 || drippedIndex >= priorities.length) { - if(!priorities || priorities.length === 0 || priorities.indexOf(assetAsKey) === -1) { - dripped.push(assetAsKey); - options.drip(asset); - } else if (drippedIndex >= priorities.length) { - dripped.push(assetAsKey); - options.drip(asset); - } - } else if(!dripQueue.find((queued)=>queued.blockchain === asset.blockchain && queued.address.toLowerCase() === asset.address.toLowerCase())) { - dripQueue.push(asset); - dripQueue.sort((a,b)=>sortPriorities(priorities, a, b)); - } + dripped.push(assetAsKey); + options.drip(asset); }; // Prioritized Assets - promises = promises.concat((options.priority || []).map((asset)=>{ - return new Promise(async (resolve, reject)=>{ + return promiseWithTimeout(new Promise(async (resolve) => { try { let token = new Token__default['default'](asset); let completedAsset = Object.assign({}, @@ -188,85 +176,105 @@ } ); if(completedAsset.balance != '0') { - if(exists({ assets, asset })) { return resolve() } + if(exists({ assets, asset })) { return resolve(null) } // Resolve with null if already exists assets.push(completedAsset); - drip(completedAsset); resolve(completedAsset); } else { - resolve(); + resolve(null); } - } catch (e) { - resolve(); + } catch (error) { + console.error('Error fetching prioritized asset:', asset, error); + resolve(null); // Resolve with null to prevent blocking } - }) + })) })); - Promise.all(promises).then(()=>{ - drippedIndex = _optionalChain([priorities, 'optionalAccess', _4 => _4.length]) || 0; - dripQueue.forEach((asset)=>drip(asset, false)); - }); - + // Major Tokens - let majorTokens = []; for (var blockchain in options.accounts){ Blockchains__default['default'].findByName(blockchain).tokens.forEach((token)=>{ if(isFiltered({ options, address: token.address, blockchain })){ return } - if(_optionalChain([options, 'optionalAccess', _5 => _5.priority, 'optionalAccess', _6 => _6.find, 'call', _7 => _7((priority)=>priority.blockchain === blockchain && priority.address.toLowerCase() === token.address.toLowerCase())])){ return } + if(_optionalChain([options, 'optionalAccess', _ => _.priority, 'optionalAccess', _2 => _2.find, 'call', _3 => _3((priority)=>priority.blockchain === blockchain && priority.address.toLowerCase() === token.address.toLowerCase())])){ return } majorTokens.push(Object.assign({}, token, { blockchain })); }); } + promises = promises.concat((majorTokens.map((asset)=>{ - return new Promise((resolve, reject)=>{ + return promiseWithTimeout(new Promise((resolve) => { new Token__default['default'](asset).balance(options.accounts[asset.blockchain]) .then((balance)=>{ - if(exists({ assets, asset })) { return resolve() } + if(exists({ assets, asset })) { return resolve(null) } // Resolve with null if already exists const assetWithBalance = reduceAssetWithBalance(asset, balance); if(assetWithBalance.balance != '0') { assets.push(assetWithBalance); - drip(assetWithBalance); resolve(assetWithBalance); } else { - resolve(); - }}).catch(()=>{ resolve(); }); - }) + resolve(null); + }}).catch((error)=>{ + console.error('Error fetching major token balance:', asset, error); + resolve(null); // Resolve with null on error + }); + })) }))); // All other assets - if(options.only == undefined || Object.keys(options.only).every((list)=>list.length == 0)) { - let allAssets = await getAssets(options); - promises = promises.concat((allAssets.map((asset)=>{ - return new Promise((resolve, reject)=>{ - const token = new Token__default['default'](asset); - return token.balance(options.accounts[asset.blockchain]) - .then(async(balance)=>{ - if(exists({ assets, asset })) { return resolve() } - const assetWithBalance = reduceAssetWithBalance(asset, balance); - if(assetWithBalance.balance != '0') { - if(assetWithBalance.name === undefined) { - assetWithBalance.name = await token.name(); - } - if(assetWithBalance.symbol === undefined) { - assetWithBalance.symbol = await token.symbol(); - } - if(assetWithBalance.decimals === undefined) { - assetWithBalance.decimals = await token.decimals(); - } - assets.push(assetWithBalance); - drip(assetWithBalance); - resolve(assetWithBalance); - } else { - resolve(); - }}).catch(()=>{ resolve(); }) - }) - }))); + try { + let allAssets = await getAssets(options); + promises = promises.concat((allAssets.map((asset)=>{ + return promiseWithTimeout(new Promise((resolve) => { + const token = new Token__default['default'](asset); + return token.balance(options.accounts[asset.blockchain]) + .then(async(balance)=>{ + if(exists({ assets, asset })) { return resolve(null) } // Resolve with null if already exists + const assetWithBalance = reduceAssetWithBalance(asset, balance); + if(assetWithBalance.balance != '0') { + if(assetWithBalance.name === undefined) { + assetWithBalance.name = await token.name(); + } + if(assetWithBalance.symbol === undefined) { + assetWithBalance.symbol = await token.symbol(); + } + if(assetWithBalance.decimals === undefined) { + assetWithBalance.decimals = await token.decimals(); + } + assets.push(assetWithBalance); + resolve(assetWithBalance); + } else { + resolve(null); + }}).catch((error)=>{ + console.error('Error fetching asset balance:', asset, error); + resolve(null); // Resolve with null on error + }) + })) + }))); + } catch (error) { + console.error('Error fetching all assets:', error); + } } - await Promise.all(promises); + // Ensure all promises are resolved + const resolvedAssets = await Promise.all(promises); - assets.sort((a,b)=>sortPriorities(priorities, a, b)); + // Drip prioritized assets first in their defined order + priorities.forEach(priorityKey => { + const asset = resolvedAssets.find(a => a && [a.blockchain, a.address.toLowerCase()].join('') === priorityKey); // Ensure valid asset + if (asset) { + drip(asset); + } + }); + + // Drip non-prioritized assets + resolvedAssets.forEach(asset => { + if (!asset) return // Ensure valid asset + const assetKey = [asset.blockchain, asset.address.toLowerCase()].join(''); + if (!priorities.includes(assetKey)) { + drip(asset); + } + }); - dripQueue.forEach((asset)=>drip(asset, false)); + // Sort assets based on priorities before returning + assets.sort((a,b)=>sortPriorities(priorities, a, b)); return assets }; diff --git a/dist/umd/index.solana.js b/dist/umd/index.solana.js index de257b4..23743bf 100644 --- a/dist/umd/index.solana.js +++ b/dist/umd/index.solana.js @@ -137,44 +137,32 @@ return 0 }; + const promiseWithTimeout = (promise, timeout = 10000) => { + return Promise.race([ + promise, + new Promise((resolve) => setTimeout(() => resolve(null), timeout)) // Resolve with null on timeout + ]); + }; + var dripAssets = async (options) => { if(options === undefined) { options = { accounts: {}, priority: [] }; } let assets = []; let dripped = []; let promises = []; - let priorities = _optionalChain([options, 'optionalAccess', _ => _.priority, 'optionalAccess', _2 => _2.map, 'call', _3 => _3((priority)=>[priority.blockchain, priority.address.toLowerCase()].join(''))]); - let drippedIndex = 0; - let dripQueue = []; + let priorities = Array.isArray(options.priority) ? options.priority.map(priority => [priority.blockchain, priority.address.toLowerCase()].join('')) : []; - const drip = (asset, recursive = true)=>{ - if(typeof options.drip !== 'function') { return } + const drip = (asset)=>{ + if (!asset || typeof options.drip !== 'function') { return } // Ensure asset is valid const assetAsKey = [asset.blockchain, asset.address.toLowerCase()].join(''); if(dripped.indexOf(assetAsKey) > -1) { return } - if(priorities && priorities.length && priorities.indexOf(assetAsKey) === drippedIndex) { - dripped.push(assetAsKey); - options.drip(asset); - drippedIndex += 1; - if(!recursive){ return } - dripQueue.forEach((asset)=>drip(asset, false)); - } else if(!priorities || priorities.length === 0 || drippedIndex >= priorities.length) { - if(!priorities || priorities.length === 0 || priorities.indexOf(assetAsKey) === -1) { - dripped.push(assetAsKey); - options.drip(asset); - } else if (drippedIndex >= priorities.length) { - dripped.push(assetAsKey); - options.drip(asset); - } - } else if(!dripQueue.find((queued)=>queued.blockchain === asset.blockchain && queued.address.toLowerCase() === asset.address.toLowerCase())) { - dripQueue.push(asset); - dripQueue.sort((a,b)=>sortPriorities(priorities, a, b)); - } + dripped.push(assetAsKey); + options.drip(asset); }; // Prioritized Assets - promises = promises.concat((options.priority || []).map((asset)=>{ - return new Promise(async (resolve, reject)=>{ + return promiseWithTimeout(new Promise(async (resolve) => { try { let token = new Token__default['default'](asset); let completedAsset = Object.assign({}, @@ -188,85 +176,105 @@ } ); if(completedAsset.balance != '0') { - if(exists({ assets, asset })) { return resolve() } + if(exists({ assets, asset })) { return resolve(null) } // Resolve with null if already exists assets.push(completedAsset); - drip(completedAsset); resolve(completedAsset); } else { - resolve(); + resolve(null); } - } catch (e) { - resolve(); + } catch (error) { + console.error('Error fetching prioritized asset:', asset, error); + resolve(null); // Resolve with null to prevent blocking } - }) + })) })); - Promise.all(promises).then(()=>{ - drippedIndex = _optionalChain([priorities, 'optionalAccess', _4 => _4.length]) || 0; - dripQueue.forEach((asset)=>drip(asset, false)); - }); - + // Major Tokens - let majorTokens = []; for (var blockchain in options.accounts){ Blockchains__default['default'].findByName(blockchain).tokens.forEach((token)=>{ if(isFiltered({ options, address: token.address, blockchain })){ return } - if(_optionalChain([options, 'optionalAccess', _5 => _5.priority, 'optionalAccess', _6 => _6.find, 'call', _7 => _7((priority)=>priority.blockchain === blockchain && priority.address.toLowerCase() === token.address.toLowerCase())])){ return } + if(_optionalChain([options, 'optionalAccess', _ => _.priority, 'optionalAccess', _2 => _2.find, 'call', _3 => _3((priority)=>priority.blockchain === blockchain && priority.address.toLowerCase() === token.address.toLowerCase())])){ return } majorTokens.push(Object.assign({}, token, { blockchain })); }); } + promises = promises.concat((majorTokens.map((asset)=>{ - return new Promise((resolve, reject)=>{ + return promiseWithTimeout(new Promise((resolve) => { new Token__default['default'](asset).balance(options.accounts[asset.blockchain]) .then((balance)=>{ - if(exists({ assets, asset })) { return resolve() } + if(exists({ assets, asset })) { return resolve(null) } // Resolve with null if already exists const assetWithBalance = reduceAssetWithBalance(asset, balance); if(assetWithBalance.balance != '0') { assets.push(assetWithBalance); - drip(assetWithBalance); resolve(assetWithBalance); } else { - resolve(); - }}).catch(()=>{ resolve(); }); - }) + resolve(null); + }}).catch((error)=>{ + console.error('Error fetching major token balance:', asset, error); + resolve(null); // Resolve with null on error + }); + })) }))); // All other assets - if(options.only == undefined || Object.keys(options.only).every((list)=>list.length == 0)) { - let allAssets = await getAssets(options); - promises = promises.concat((allAssets.map((asset)=>{ - return new Promise((resolve, reject)=>{ - const token = new Token__default['default'](asset); - return token.balance(options.accounts[asset.blockchain]) - .then(async(balance)=>{ - if(exists({ assets, asset })) { return resolve() } - const assetWithBalance = reduceAssetWithBalance(asset, balance); - if(assetWithBalance.balance != '0') { - if(assetWithBalance.name === undefined) { - assetWithBalance.name = await token.name(); - } - if(assetWithBalance.symbol === undefined) { - assetWithBalance.symbol = await token.symbol(); - } - if(assetWithBalance.decimals === undefined) { - assetWithBalance.decimals = await token.decimals(); - } - assets.push(assetWithBalance); - drip(assetWithBalance); - resolve(assetWithBalance); - } else { - resolve(); - }}).catch(()=>{ resolve(); }) - }) - }))); + try { + let allAssets = await getAssets(options); + promises = promises.concat((allAssets.map((asset)=>{ + return promiseWithTimeout(new Promise((resolve) => { + const token = new Token__default['default'](asset); + return token.balance(options.accounts[asset.blockchain]) + .then(async(balance)=>{ + if(exists({ assets, asset })) { return resolve(null) } // Resolve with null if already exists + const assetWithBalance = reduceAssetWithBalance(asset, balance); + if(assetWithBalance.balance != '0') { + if(assetWithBalance.name === undefined) { + assetWithBalance.name = await token.name(); + } + if(assetWithBalance.symbol === undefined) { + assetWithBalance.symbol = await token.symbol(); + } + if(assetWithBalance.decimals === undefined) { + assetWithBalance.decimals = await token.decimals(); + } + assets.push(assetWithBalance); + resolve(assetWithBalance); + } else { + resolve(null); + }}).catch((error)=>{ + console.error('Error fetching asset balance:', asset, error); + resolve(null); // Resolve with null on error + }) + })) + }))); + } catch (error) { + console.error('Error fetching all assets:', error); + } } - await Promise.all(promises); + // Ensure all promises are resolved + const resolvedAssets = await Promise.all(promises); - assets.sort((a,b)=>sortPriorities(priorities, a, b)); + // Drip prioritized assets first in their defined order + priorities.forEach(priorityKey => { + const asset = resolvedAssets.find(a => a && [a.blockchain, a.address.toLowerCase()].join('') === priorityKey); // Ensure valid asset + if (asset) { + drip(asset); + } + }); + + // Drip non-prioritized assets + resolvedAssets.forEach(asset => { + if (!asset) return // Ensure valid asset + const assetKey = [asset.blockchain, asset.address.toLowerCase()].join(''); + if (!priorities.includes(assetKey)) { + drip(asset); + } + }); - dripQueue.forEach((asset)=>drip(asset, false)); + // Sort assets based on priorities before returning + assets.sort((a,b)=>sortPriorities(priorities, a, b)); return assets }; diff --git a/package.evm.json b/package.evm.json index 2823232..57ac7d5 100644 --- a/package.evm.json +++ b/package.evm.json @@ -1,7 +1,7 @@ { "name": "@depay/web3-assets-evm", "moduleName": "Web3Assets", - "version": "7.4.0", + "version": "7.4.1", "description": "JavaScript library to retrieve Web3 assets of a given or connected wallet/account.", "main": "dist/umd/index.evm.js", "module": "dist/esm/index.evm.js", @@ -25,8 +25,8 @@ "private": false, "dependencies": {}, "peerDependencies": { - "@depay/web3-blockchains": "^9.6.5", - "@depay/web3-client-evm": "^10.19.1", + "@depay/web3-blockchains": "^9.7.0", + "@depay/web3-client-evm": "^10.19.2", "@depay/web3-tokens-evm": "^10.4.3" }, "engines": { diff --git a/package.json b/package.json index 3070ac4..d4bbcca 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@depay/web3-assets", "moduleName": "Web3Assets", - "version": "7.4.0", + "version": "7.4.1", "description": "JavaScript library to retrieve Web3 assets of a given or connected wallet/account.", "main": "dist/umd/index.js", "module": "dist/esm/index.js", @@ -35,8 +35,8 @@ "dependencies": {}, "peerDependencies": { "@depay/solana-web3.js": "^1.26.0", - "@depay/web3-blockchains": "^9.6.5", - "@depay/web3-client": "^10.19.1", + "@depay/web3-blockchains": "^9.7.0", + "@depay/web3-client": "^10.19.2", "@depay/web3-tokens": "^10.4.3" }, "engines": { @@ -46,10 +46,10 @@ "@babel/core": "^7.12.9", "@babel/preset-env": "^7.12.7", "@depay/solana-web3.js": "^1.26.0", - "@depay/web3-blockchains": "^9.6.5", - "@depay/web3-client": "^10.19.1", - "@depay/web3-client-evm": "^10.19.1", - "@depay/web3-client-solana": "^10.19.1", + "@depay/web3-blockchains": "^9.7.0", + "@depay/web3-client": "^10.19.2", + "@depay/web3-client-evm": "^10.19.2", + "@depay/web3-client-solana": "^10.19.2", "@depay/web3-mock": "^14.19.1", "@depay/web3-tokens": "^10.4.3", "@depay/web3-tokens-evm": "^10.4.3", diff --git a/package.solana.json b/package.solana.json index da04f95..d7d7cf1 100644 --- a/package.solana.json +++ b/package.solana.json @@ -1,7 +1,7 @@ { "name": "@depay/web3-assets-solana", "moduleName": "Web3Assets", - "version": "7.4.0", + "version": "7.4.1", "description": "JavaScript library to retrieve Web3 assets of a given or connected wallet/account.", "main": "dist/umd/index.solana.js", "module": "dist/esm/index.solana.js", @@ -26,8 +26,8 @@ "dependencies": {}, "peerDependencies": { "@depay/solana-web3.js": "^1.26.0", - "@depay/web3-blockchains": "^9.6.5", - "@depay/web3-client-solana": "^10.19.1", + "@depay/web3-blockchains": "^9.7.0", + "@depay/web3-client-solana": "^10.19.2", "@depay/web3-tokens-solana": "^10.4.3" }, "engines": { diff --git a/src/dripAssets.js b/src/dripAssets.js index 93530b7..1611975 100644 --- a/src/dripAssets.js +++ b/src/dripAssets.js @@ -61,44 +61,34 @@ const sortPriorities = (priorities, a,b)=>{ return 0 } +const promiseWithTimeout = (promise, timeout = 10000) => { + return Promise.race([ + promise, + new Promise((resolve) => setTimeout(() => resolve(null), timeout)) // Resolve with null on timeout + ]); +}; + export default async (options) => { if(options === undefined) { options = { accounts: {}, priority: [] } } let assets = [] let dripped = [] let promises = [] - let priorities = options?.priority?.map((priority)=>[priority.blockchain, priority.address.toLowerCase()].join('')) + let priorities = Array.isArray(options.priority) ? options.priority.map(priority => [priority.blockchain, priority.address.toLowerCase()].join('')) : [] let drippedIndex = 0 let dripQueue = [] - const drip = (asset, recursive = true)=>{ - if(typeof options.drip !== 'function') { return } + const drip = (asset)=>{ + if (!asset || typeof options.drip !== 'function') { return } // Ensure asset is valid const assetAsKey = [asset.blockchain, asset.address.toLowerCase()].join('') if(dripped.indexOf(assetAsKey) > -1) { return } - if(priorities && priorities.length && priorities.indexOf(assetAsKey) === drippedIndex) { - dripped.push(assetAsKey) - options.drip(asset) - drippedIndex += 1 - if(!recursive){ return } - dripQueue.forEach((asset)=>drip(asset, false)) - } else if(!priorities || priorities.length === 0 || drippedIndex >= priorities.length) { - if(!priorities || priorities.length === 0 || priorities.indexOf(assetAsKey) === -1) { - dripped.push(assetAsKey) - options.drip(asset) - } else if (drippedIndex >= priorities.length) { - dripped.push(assetAsKey) - options.drip(asset) - } - } else if(!dripQueue.find((queued)=>queued.blockchain === asset.blockchain && queued.address.toLowerCase() === asset.address.toLowerCase())) { - dripQueue.push(asset) - dripQueue.sort((a,b)=>sortPriorities(priorities, a, b)) - } + dripped.push(assetAsKey) + options.drip(asset) } // Prioritized Assets - promises = promises.concat((options.priority || []).map((asset)=>{ - return new Promise(async (resolve, reject)=>{ + return promiseWithTimeout(new Promise(async (resolve) => { try { let token = new Token(asset) let completedAsset = Object.assign({}, @@ -112,25 +102,20 @@ export default async (options) => { } ) if(completedAsset.balance != '0') { - if(exists({ assets, asset })) { return resolve() } + if(exists({ assets, asset })) { return resolve(null) } // Resolve with null if already exists assets.push(completedAsset) - drip(completedAsset) resolve(completedAsset) } else { - resolve() + resolve(null) } - } catch { - resolve() + } catch (error) { + console.error('Error fetching prioritized asset:', asset, error) + resolve(null) // Resolve with null to prevent blocking } - }) + })) })) - Promise.all(promises).then(()=>{ - drippedIndex = priorities?.length || 0 - dripQueue.forEach((asset)=>drip(asset, false)) - }) - + // Major Tokens - let majorTokens = [] for (var blockchain in options.accounts){ Blockchains.findByName(blockchain).tokens.forEach((token)=>{ @@ -139,59 +124,83 @@ export default async (options) => { majorTokens.push(Object.assign({}, token, { blockchain })) }) } + promises = promises.concat((majorTokens.map((asset)=>{ - return new Promise((resolve, reject)=>{ - let requestOptions + return promiseWithTimeout(new Promise((resolve) => { new Token(asset).balance(options.accounts[asset.blockchain]) .then((balance)=>{ - if(exists({ assets, asset })) { return resolve() } + if(exists({ assets, asset })) { return resolve(null) } // Resolve with null if already exists const assetWithBalance = reduceAssetWithBalance(asset, balance) if(assetWithBalance.balance != '0') { assets.push(assetWithBalance) - drip(assetWithBalance) resolve(assetWithBalance) } else { - resolve() - }}).catch(()=>{ resolve() }) - }) + resolve(null) + }}).catch((error)=>{ + console.error('Error fetching major token balance:', asset, error) + resolve(null) // Resolve with null on error + }) + })) }))) // All other assets - if(options.only == undefined || Object.keys(options.only).every((list)=>list.length == 0)) { - let allAssets = await getAssets(options) - promises = promises.concat((allAssets.map((asset)=>{ - return new Promise((resolve, reject)=>{ - const token = new Token(asset) - return token.balance(options.accounts[asset.blockchain]) - .then(async(balance)=>{ - if(exists({ assets, asset })) { return resolve() } - const assetWithBalance = reduceAssetWithBalance(asset, balance) - if(assetWithBalance.balance != '0') { - if(assetWithBalance.name === undefined) { - assetWithBalance.name = await token.name() - } - if(assetWithBalance.symbol === undefined) { - assetWithBalance.symbol = await token.symbol() - } - if(assetWithBalance.decimals === undefined) { - assetWithBalance.decimals = await token.decimals() - } - assets.push(assetWithBalance) - drip(assetWithBalance) - resolve(assetWithBalance) - } else { - resolve() - }}).catch(()=>{ resolve() }) - }) - }))) + try { + let allAssets = await getAssets(options) + promises = promises.concat((allAssets.map((asset)=>{ + return promiseWithTimeout(new Promise((resolve) => { + const token = new Token(asset) + return token.balance(options.accounts[asset.blockchain]) + .then(async(balance)=>{ + if(exists({ assets, asset })) { return resolve(null) } // Resolve with null if already exists + const assetWithBalance = reduceAssetWithBalance(asset, balance) + if(assetWithBalance.balance != '0') { + if(assetWithBalance.name === undefined) { + assetWithBalance.name = await token.name() + } + if(assetWithBalance.symbol === undefined) { + assetWithBalance.symbol = await token.symbol() + } + if(assetWithBalance.decimals === undefined) { + assetWithBalance.decimals = await token.decimals() + } + assets.push(assetWithBalance) + resolve(assetWithBalance) + } else { + resolve(null) + }}).catch((error)=>{ + console.error('Error fetching asset balance:', asset, error) + resolve(null) // Resolve with null on error + }) + })) + }))) + } catch (error) { + console.error('Error fetching all assets:', error) + } } - await Promise.all(promises) + // Ensure all promises are resolved + const resolvedAssets = await Promise.all(promises) - assets.sort((a,b)=>sortPriorities(priorities, a, b)) + // Drip prioritized assets first in their defined order + priorities.forEach(priorityKey => { + const asset = resolvedAssets.find(a => a && [a.blockchain, a.address.toLowerCase()].join('') === priorityKey) // Ensure valid asset + if (asset) { + drip(asset) + } + }) - dripQueue.forEach((asset)=>drip(asset, false)) + // Drip non-prioritized assets + resolvedAssets.forEach(asset => { + if (!asset) return // Ensure valid asset + const assetKey = [asset.blockchain, asset.address.toLowerCase()].join('') + if (!priorities.includes(assetKey)) { + drip(asset) + } + }) + + // Sort assets based on priorities before returning + assets.sort((a,b)=>sortPriorities(priorities, a, b)) return assets } diff --git a/tests/units/dripAssets.evm.spec.js b/tests/units/dripAssets.evm.spec.js index e482a76..07856bc 100644 --- a/tests/units/dripAssets.evm.spec.js +++ b/tests/units/dripAssets.evm.spec.js @@ -97,6 +97,7 @@ describe('dripAssets', ()=>{ }) expect(dripsCount).toEqual(27) + expect(allAssets.length).toEqual(27) let expectedAssets = [{ address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', @@ -343,8 +344,21 @@ describe('dripAssets', ()=>{ } ] - expect(drippedAssets).toEqual(expectedAssets) - expect(allAssets).toEqual(expectedAssets) + // Ensure all expected assets are present in drippedAssets without enforcing a fixed order + expectedAssets.forEach(asset => { + expect(drippedAssets).toContainEqual(asset) + }) + + // Ensure no extra assets are dripped + expect(drippedAssets.length).toEqual(expectedAssets.length) + + // Ensure all expected assets are present in allAssets without enforcing a fixed order + expectedAssets.forEach(asset => { + expect(allAssets).toContainEqual(asset) + }) + + // Ensure no extra assets in allAssets + expect(allAssets.length).toEqual(expectedAssets.length) }) }) @@ -407,8 +421,11 @@ describe('dripAssets', ()=>{ }) expect(dripsCount).toEqual(16) + expect(allAssets.length).toEqual(16) - let expectedAssets = [{ + // Expected assets that are not excluded, no fixed order + let expectedAssets = [ + { address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', symbol: 'ETH', name: 'Ether', @@ -472,13 +489,13 @@ describe('dripAssets', ()=>{ balance: '123456789' }, { - address: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", - balance: "123456789", - blockchain: "ethereum", + address: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', + symbol: 'WBTC', + name: 'Wrapped BTC', decimals: 8, - name: "Wrapped BTC", - symbol: "WBTC", - type: "20", + type: '20', + blockchain: 'ethereum', + balance: '123456789' }, { address: '0xa0bEd124a09ac2Bd941b10349d8d224fe3c955eb', @@ -554,8 +571,21 @@ describe('dripAssets', ()=>{ } ] - expect(drippedAssets).toEqual(expectedAssets) - expect(allAssets).toEqual(expectedAssets) + // The actual drippedAssets should contain the expected assets (no fixed order) + expectedAssets.forEach(asset => { + expect(drippedAssets).toContainEqual(asset) + }) + + // Ensure no extra assets are dripped + expect(drippedAssets.length).toEqual(expectedAssets.length) + + // Also check that allAssets contain the expected assets + expectedAssets.forEach(asset => { + expect(allAssets).toContainEqual(asset) + }) + + // Ensure no extra assets in the final allAssets + expect(allAssets.length).toEqual(expectedAssets.length) }) }) @@ -598,8 +628,10 @@ describe('dripAssets', ()=>{ }) expect(dripsCount).toEqual(17) + expect(allAssets.length).toEqual(17) - let expectedAssets = [ + // Expect the prioritized assets to be in the correct order + let prioritizedAssets = [ { address: '0xa0bEd124a09ac2Bd941b10349d8d224fe3c955eb', symbol: 'DEPAY', @@ -626,23 +658,28 @@ describe('dripAssets', ()=>{ type: '20', blockchain: 'bsc', balance: '123456789' - }, + } + ] + + expect(drippedAssets.slice(0, 3)).toEqual(prioritizedAssets) + + let remainingAssets = [ { address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', - symbol: 'ETH', - name: 'Ether', + symbol: 'BNB', + name: 'Binance Coin', decimals: 18, type: 'NATIVE', - blockchain: 'ethereum', + blockchain: 'bsc', balance: '123456789' }, { address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', - symbol: 'BNB', - name: 'Binance Coin', + symbol: 'ETH', + name: 'Ether', decimals: 18, type: 'NATIVE', - blockchain: 'bsc', + blockchain: 'ethereum', balance: '123456789' }, { @@ -706,7 +743,7 @@ describe('dripAssets', ()=>{ decimals: 8, name: "Wrapped BTC", symbol: "WBTC", - type: "20", + type: "20" }, { address: '0x55d398326f99059fF775485246999027B3197955', @@ -752,11 +789,25 @@ describe('dripAssets', ()=>{ type: '20', blockchain: 'bsc', balance: '123456789' - }, + } ] - - expect(drippedAssets).toEqual(expectedAssets) - expect(allAssets).toEqual(expectedAssets) + + // Ensure the remaining assets are present in the full asset list + remainingAssets.forEach(asset => { + expect(drippedAssets).toContainEqual(asset) + }) + + // The first 3 assets in allAssets should match the prioritized ones in the correct order + expect(allAssets.slice(0, 3)).toEqual(prioritizedAssets) + + // The remaining assets (after the prioritized ones) should match the rest, but in any order + let remainingAllAssets = allAssets.slice(3) + remainingAssets.forEach(asset => { + expect(remainingAllAssets).toContainEqual(asset) + }) + + // Ensure no extra assets exist + expect(remainingAllAssets.length).toEqual(remainingAssets.length) }) }) diff --git a/tests/units/dripAssets.spec.js b/tests/units/dripAssets.spec.js index e40ecf6..cc45fd0 100644 --- a/tests/units/dripAssets.spec.js +++ b/tests/units/dripAssets.spec.js @@ -97,6 +97,7 @@ describe('dripAssets', ()=>{ }) expect(dripsCount).toEqual(27) + expect(allAssets.length).toEqual(27) let expectedAssets = [{ address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', @@ -343,8 +344,21 @@ describe('dripAssets', ()=>{ } ] - expect(drippedAssets).toEqual(expectedAssets) - expect(allAssets).toEqual(expectedAssets) + // Ensure all expected assets are present in drippedAssets without enforcing a fixed order + expectedAssets.forEach(asset => { + expect(drippedAssets).toContainEqual(asset) + }) + + // Ensure no extra assets are dripped + expect(drippedAssets.length).toEqual(expectedAssets.length) + + // Ensure all expected assets are present in allAssets without enforcing a fixed order + expectedAssets.forEach(asset => { + expect(allAssets).toContainEqual(asset) + }) + + // Ensure no extra assets in allAssets + expect(allAssets.length).toEqual(expectedAssets.length) }) }) @@ -407,8 +421,11 @@ describe('dripAssets', ()=>{ }) expect(dripsCount).toEqual(16) + expect(allAssets.length).toEqual(16) - let expectedAssets = [{ + // Expected assets that are not excluded, no fixed order + let expectedAssets = [ + { address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', symbol: 'ETH', name: 'Ether', @@ -472,13 +489,13 @@ describe('dripAssets', ()=>{ balance: '123456789' }, { - address: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", - balance: "123456789", - blockchain: "ethereum", + address: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', + symbol: 'WBTC', + name: 'Wrapped BTC', decimals: 8, - name: "Wrapped BTC", - symbol: "WBTC", - type: "20", + type: '20', + blockchain: 'ethereum', + balance: '123456789' }, { address: '0xa0bEd124a09ac2Bd941b10349d8d224fe3c955eb', @@ -554,8 +571,21 @@ describe('dripAssets', ()=>{ } ] - expect(drippedAssets).toEqual(expectedAssets) - expect(allAssets).toEqual(expectedAssets) + // The actual drippedAssets should contain the expected assets (no fixed order) + expectedAssets.forEach(asset => { + expect(drippedAssets).toContainEqual(asset) + }) + + // Ensure no extra assets are dripped + expect(drippedAssets.length).toEqual(expectedAssets.length) + + // Also check that allAssets contain the expected assets + expectedAssets.forEach(asset => { + expect(allAssets).toContainEqual(asset) + }) + + // Ensure no extra assets in the final allAssets + expect(allAssets.length).toEqual(expectedAssets.length) }) }) @@ -598,8 +628,10 @@ describe('dripAssets', ()=>{ }) expect(dripsCount).toEqual(17) + expect(allAssets.length).toEqual(17) - let expectedAssets = [ + // Expect the prioritized assets to be in the correct order + let prioritizedAssets = [ { address: '0xa0bEd124a09ac2Bd941b10349d8d224fe3c955eb', symbol: 'DEPAY', @@ -626,23 +658,28 @@ describe('dripAssets', ()=>{ type: '20', blockchain: 'bsc', balance: '123456789' - }, + } + ] + + expect(drippedAssets.slice(0, 3)).toEqual(prioritizedAssets) + + let remainingAssets = [ { address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', - symbol: 'ETH', - name: 'Ether', + symbol: 'BNB', + name: 'Binance Coin', decimals: 18, type: 'NATIVE', - blockchain: 'ethereum', + blockchain: 'bsc', balance: '123456789' }, { address: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE', - symbol: 'BNB', - name: 'Binance Coin', + symbol: 'ETH', + name: 'Ether', decimals: 18, type: 'NATIVE', - blockchain: 'bsc', + blockchain: 'ethereum', balance: '123456789' }, { @@ -706,7 +743,7 @@ describe('dripAssets', ()=>{ decimals: 8, name: "Wrapped BTC", symbol: "WBTC", - type: "20", + type: "20" }, { address: '0x55d398326f99059fF775485246999027B3197955', @@ -752,11 +789,25 @@ describe('dripAssets', ()=>{ type: '20', blockchain: 'bsc', balance: '123456789' - }, + } ] - - expect(drippedAssets).toEqual(expectedAssets) - expect(allAssets).toEqual(expectedAssets) + + // Ensure the remaining assets are present in the full asset list + remainingAssets.forEach(asset => { + expect(drippedAssets).toContainEqual(asset) + }) + + // The first 3 assets in allAssets should match the prioritized ones in the correct order + expect(allAssets.slice(0, 3)).toEqual(prioritizedAssets) + + // The remaining assets (after the prioritized ones) should match the rest, but in any order + let remainingAllAssets = allAssets.slice(3) + remainingAssets.forEach(asset => { + expect(remainingAllAssets).toContainEqual(asset) + }) + + // Ensure no extra assets exist + expect(remainingAllAssets.length).toEqual(remainingAssets.length) }) }) diff --git a/yarn.lock b/yarn.lock index 3e11a27..5e3b826 100644 --- a/yarn.lock +++ b/yarn.lock @@ -903,25 +903,30 @@ dependencies: bs58 "^5.0.0" -"@depay/web3-blockchains@^9.6.1", "@depay/web3-blockchains@^9.6.5": +"@depay/web3-blockchains@^9.6.1": version "9.6.5" resolved "https://registry.yarnpkg.com/@depay/web3-blockchains/-/web3-blockchains-9.6.5.tgz#29557ccbd352bbbb8218af34b422e6f3a4df4e73" integrity sha512-rxqLr29Zo2cfClgE7JmViollzE1i154ORF89j7zO7RMbn1E8hXJXJ6aRDuA8ArtH+f17buu91865RYoXTZZzdQ== -"@depay/web3-client-evm@^10.19.1": - version "10.19.1" - resolved "https://registry.yarnpkg.com/@depay/web3-client-evm/-/web3-client-evm-10.19.1.tgz#88091c94bc9603e2afe7bfb25dceed322ce76181" - integrity sha512-smxAPoq+x6bnp3t4ak6g0AJlkHzjD4WuMwOHhTJ4ct2aLs8EiMu0MA54Zebetk5Udpi7B4QgaZKIpwjEL/0J7A== - -"@depay/web3-client-solana@^10.19.1": - version "10.19.1" - resolved "https://registry.yarnpkg.com/@depay/web3-client-solana/-/web3-client-solana-10.19.1.tgz#398389d304272c310a9ebeb674fd8856e7db6e3b" - integrity sha512-p7RvbTYL9XCN8JM20TsScI8it1Hho6m+fHfFWSzvpvFCtvT/pWjTxrB9tGb/jDecrNlIhYcBD01Cwo4DZgOlpw== - -"@depay/web3-client@^10.19.1": - version "10.19.1" - resolved "https://registry.yarnpkg.com/@depay/web3-client/-/web3-client-10.19.1.tgz#7211084c65b9d4a1b1aefb6b684a7a5f3750aee6" - integrity sha512-GLSW3w3DQCpwu0RBvm+HBc/Sn856C7IKLLM1hgoGCMg/oq3gROEOw0A0ODEzetRRkeBOixFHGqfNpqtXq+ULtQ== +"@depay/web3-blockchains@^9.7.0": + version "9.7.0" + resolved "https://registry.yarnpkg.com/@depay/web3-blockchains/-/web3-blockchains-9.7.0.tgz#653655b8153165dc6626fa67762420506485283e" + integrity sha512-UzNXg4WbHHXLAa58VA6f5oq4OHXDEXIn1bigfWqAbe0jKWJR36QXLWDZsm4ufoC3ExyXOiOVyNfKOSnJ0xdTww== + +"@depay/web3-client-evm@^10.19.2": + version "10.19.2" + resolved "https://registry.yarnpkg.com/@depay/web3-client-evm/-/web3-client-evm-10.19.2.tgz#8fbe63a2f12a33a89e3219521bdef6afe68c2d1d" + integrity sha512-JAFgimz0T8TmdRMR0ib5yoi41KOcn/4SlyH/A3Oj1YzboCtlOxs2Cv7C8HXqN/MzgODVfywtu8GUZZqOQOG7qw== + +"@depay/web3-client-solana@^10.19.2": + version "10.19.2" + resolved "https://registry.yarnpkg.com/@depay/web3-client-solana/-/web3-client-solana-10.19.2.tgz#dadf0893066fe3b3a69d9d6b2295b1981807d62d" + integrity sha512-epb/nlf2s9pXaFdtMlsfObcYlifT6DAvEVLvxSBVu7p1z9JtYjYk0QpMPE+gc/0pH1ASXQMRw0tK5R4AsiqCIw== + +"@depay/web3-client@^10.19.2": + version "10.19.2" + resolved "https://registry.yarnpkg.com/@depay/web3-client/-/web3-client-10.19.2.tgz#e8de1dcf4432597dab2a40362401586f7c48a112" + integrity sha512-i3ryYA5CCU+f0T/rOZcJpYo9vrqFC2KZnd9T6Oj1j7DiYb5boKw///z6/dbBtd1g8Gww8i/9lC9ouWGBxmKXJA== "@depay/web3-mock@^14.19.1": version "14.19.1"