Workflow updating files of floethereum
This commit is contained in:
parent
87220ae53e
commit
3a2f5b7ec9
@ -822,56 +822,71 @@
|
|||||||
loadTransactionsPage(ethAddress, floAddress, currentPage);
|
loadTransactionsPage(ethAddress, floAddress, currentPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let allTransactionsCache = [];
|
||||||
|
let currentBalances = { eth: '0', usdc: '0', usdt: '0' };
|
||||||
|
|
||||||
async function loadTransactionsPage(ethAddress, floAddress, page) {
|
async function loadTransactionsPage(ethAddress, floAddress, page) {
|
||||||
buttonLoader('check_balance_button', true);
|
buttonLoader('check_balance_button', true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const results = await Promise.allSettled([
|
let transactions = [];
|
||||||
ethOperator.getBalance(ethAddress),
|
let etherBalance = '0', usdcBalance = '0', usdtBalance = '0';
|
||||||
ethOperator.getTokenBalance(ethAddress, 'usdc'),
|
|
||||||
ethOperator.getTokenBalance(ethAddress, 'usdt'),
|
// Fetch new data if it's the first page (Search or Refresh)
|
||||||
ethOperator.getTransactionHistory(ethAddress, {
|
if (page === 1) {
|
||||||
page: page,
|
const results = await Promise.allSettled([
|
||||||
offset: TRANSACTIONS_PER_PAGE,
|
ethOperator.getBalance(ethAddress),
|
||||||
sort: 'desc'
|
ethOperator.getTokenBalance(ethAddress, 'usdc'),
|
||||||
|
ethOperator.getTokenBalance(ethAddress, 'usdt'),
|
||||||
|
ethOperator.getTransactionHistory(ethAddress, {
|
||||||
|
page: 1, // Always Page 1 of API
|
||||||
|
offset: 500, // Batch size 500
|
||||||
|
sort: 'desc'
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
etherBalance = results[0].status === 'fulfilled' ? results[0].value : '0';
|
||||||
|
usdcBalance = results[1].status === 'fulfilled' ? results[1].value : '0';
|
||||||
|
usdtBalance = results[2].status === 'fulfilled' ? results[2].value : '0';
|
||||||
|
transactions = results[3].status === 'fulfilled' ? results[3].value : [];
|
||||||
|
|
||||||
|
// Update Cache
|
||||||
|
allTransactionsCache = transactions;
|
||||||
|
currentBalances = { eth: etherBalance, usdc: usdcBalance, usdt: usdtBalance };
|
||||||
|
|
||||||
|
// Log warnings
|
||||||
|
if (results[0].status === 'rejected') console.warn('Failed to fetch ETH balance:', results[0].reason);
|
||||||
|
if (results[1].status === 'rejected') console.warn('Failed to fetch USDC balance:', results[1].reason);
|
||||||
|
if (results[2].status === 'rejected') console.warn('Failed to fetch USDT balance:', results[2].reason);
|
||||||
|
if (results[3].status === 'rejected') console.warn('Failed to fetch transaction history:', results[3].reason);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Use Cache for pagination
|
||||||
|
transactions = allTransactionsCache;
|
||||||
|
({ eth: etherBalance, usdc: usdcBalance, usdt: usdtBalance } = currentBalances);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local Pagination / Slicing
|
||||||
|
const startIndex = (page - 1) * TRANSACTIONS_PER_PAGE;
|
||||||
|
const endIndex = startIndex + TRANSACTIONS_PER_PAGE;
|
||||||
|
const paginatedTransactions = transactions.slice(startIndex, endIndex);
|
||||||
|
const hasNextPage = transactions.length > endIndex;
|
||||||
|
|
||||||
|
// Sync Contacts (only on fresh load really, but safe here)
|
||||||
|
if (page === 1) {
|
||||||
|
compactIDB.readData('contacts', floAddress || ethAddress).then(result => {
|
||||||
|
if (result) return
|
||||||
|
compactIDB.addData('contacts', {
|
||||||
|
ethAddress,
|
||||||
|
}, floAddress || ethAddress).then(() => {
|
||||||
|
renderSearchedAddressList()
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
]);
|
|
||||||
|
|
||||||
// Extract balance and transaction data, using defaults if any request failed
|
|
||||||
const etherBalance = results[0].status === 'fulfilled' ? results[0].value : '0';
|
|
||||||
const usdcBalance = results[1].status === 'fulfilled' ? results[1].value : '0';
|
|
||||||
const usdtBalance = results[2].status === 'fulfilled' ? results[2].value : '0';
|
|
||||||
const transactions = results[3].status === 'fulfilled' ? results[3].value : [];
|
|
||||||
|
|
||||||
// Store transactions for filtering
|
|
||||||
allTransactions = transactions;
|
|
||||||
|
|
||||||
// Log warnings if any API requests failed
|
|
||||||
if (results[0].status === 'rejected') {
|
|
||||||
console.warn('Failed to fetch ETH balance:', results[0].reason);
|
|
||||||
}
|
|
||||||
if (results[1].status === 'rejected') {
|
|
||||||
console.warn('Failed to fetch USDC balance:', results[1].reason);
|
|
||||||
}
|
|
||||||
if (results[2].status === 'rejected') {
|
|
||||||
console.warn('Failed to fetch USDT balance:', results[2].reason);
|
|
||||||
}
|
|
||||||
if (results[3].status === 'rejected') {
|
|
||||||
console.warn('Failed to fetch transaction history:', results[3].reason);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
compactIDB.readData('contacts', floAddress || ethAddress).then(result => {
|
renderBalanceAndTransactions(ethAddress, floAddress, etherBalance, usdcBalance, usdtBalance, paginatedTransactions, page, hasNextPage);
|
||||||
if (result) return
|
|
||||||
compactIDB.addData('contacts', {
|
|
||||||
ethAddress,
|
|
||||||
}, floAddress || ethAddress).then(() => {
|
|
||||||
renderSearchedAddressList()
|
|
||||||
}).catch((error) => {
|
|
||||||
console.error(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
renderBalanceAndTransactions(ethAddress, floAddress, etherBalance, usdcBalance, usdtBalance, transactions, page);
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notify(error.message || error, 'error');
|
notify(error.message || error, 'error');
|
||||||
@ -880,7 +895,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderBalanceAndTransactions(ethAddress, floAddress, etherBalance, usdcBalance, usdtBalance, transactions, page) {
|
function renderBalanceAndTransactions(ethAddress, floAddress, etherBalance, usdcBalance, usdtBalance, transactions, page, hasNextPage) {
|
||||||
// Update URL to reflect the current address being viewed
|
// Update URL to reflect the current address being viewed
|
||||||
const url = new URL(window.location);
|
const url = new URL(window.location);
|
||||||
url.searchParams.set('address', ethAddress);
|
url.searchParams.set('address', ethAddress);
|
||||||
@ -890,7 +905,7 @@
|
|||||||
window.history.pushState({}, '', url.pathname + url.search + url.hash);
|
window.history.pushState({}, '', url.pathname + url.search + url.hash);
|
||||||
|
|
||||||
// Determine if pagination buttons should be enabled
|
// Determine if pagination buttons should be enabled
|
||||||
const hasNextPage = transactions.length >= TRANSACTIONS_PER_PAGE;
|
// hasNextPage is now passed in
|
||||||
const hasPrevPage = page > 1;
|
const hasPrevPage = page > 1;
|
||||||
|
|
||||||
renderElem(getRef('eth_balance_wrapper'), html`
|
renderElem(getRef('eth_balance_wrapper'), html`
|
||||||
@ -1124,14 +1139,32 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-0-5">
|
<div class="grid gap-0-5">
|
||||||
<div class="label">To</div>
|
<div class="label">To (Contract)</div>
|
||||||
<sm-copy value=${txDetails.to}></sm-copy>
|
<sm-copy value=${txDetails.to}></sm-copy>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${txDetails.tokenTransfer ? html`
|
${txDetails.tokenTransfers && txDetails.tokenTransfers.length > 0 ? html`
|
||||||
<div class="grid gap-0-5">
|
<div class="grid gap-0-5">
|
||||||
<div class="label">Token Transfer</div>
|
<div class="label">Token Transfers</div>
|
||||||
<strong>${txDetails.tokenTransfer.value} ${txDetails.tokenTransfer.symbol}</strong>
|
<div class="grid gap-0-5" style="padding: 0.75rem; background: rgba(var(--text-color), 0.05); border-radius: 0.5rem;">
|
||||||
|
${txDetails.tokenTransfers.map((transfer, index) => html`
|
||||||
|
<div class="grid gap-0-3" style="${index !== txDetails.tokenTransfers.length - 1 ? 'border-bottom: 1px solid rgba(var(--text-color), 0.1); padding-bottom: 0.75rem; margin-bottom: 0.25rem;' : ''}">
|
||||||
|
<div class="flex space-between align-center">
|
||||||
|
<strong>${transfer.value} ${transfer.symbol}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="grid gap-0-5" style="font-size: 0.8rem;">
|
||||||
|
<div class="grid gap-0">
|
||||||
|
<span class="color-0-7">From</span>
|
||||||
|
<sm-copy value=${transfer.from} style="--font-size: 0.8rem;"></sm-copy>
|
||||||
|
</div>
|
||||||
|
<div class="grid gap-0">
|
||||||
|
<span class="color-0-7">To</span>
|
||||||
|
<sm-copy value=${transfer.to} style="--font-size: 0.8rem;"></sm-copy>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
` : html`
|
` : html`
|
||||||
<div class="grid gap-0-5">
|
<div class="grid gap-0-5">
|
||||||
|
|||||||
@ -417,7 +417,17 @@
|
|||||||
const tokenTransfers = tokenTxData.status === '1' ? tokenTxData.result : [];
|
const tokenTransfers = tokenTxData.status === '1' ? tokenTxData.result : [];
|
||||||
|
|
||||||
// Combine and sort transactions
|
// Combine and sort transactions
|
||||||
const allTransactions = [...normalTxData.result, ...tokenTransfers];
|
// Filter out normal transactions that are already present in token transfers (duplicate hash) AND have 0 value
|
||||||
|
// This prevents showing "0 ETH to Contract" alongside the actual "Token Transfer"
|
||||||
|
const tokenTxHashes = new Set(tokenTransfers.map(tx => tx.hash));
|
||||||
|
const uniqueNormalTxs = normalTxData.result.filter(tx => {
|
||||||
|
if (tokenTxHashes.has(tx.hash) && tx.value === '0') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const allTransactions = [...uniqueNormalTxs, ...tokenTransfers];
|
||||||
|
|
||||||
// Sort by timestamp (descending)
|
// Sort by timestamp (descending)
|
||||||
allTransactions.sort((a, b) => parseInt(b.timeStamp) - parseInt(a.timeStamp));
|
allTransactions.sort((a, b) => parseInt(b.timeStamp) - parseInt(a.timeStamp));
|
||||||
@ -504,33 +514,51 @@
|
|||||||
parseFloat(ethers.utils.formatEther(gasUsed.mul(effectiveGasPrice))) : null;
|
parseFloat(ethers.utils.formatEther(gasUsed.mul(effectiveGasPrice))) : null;
|
||||||
|
|
||||||
// Check if it's a token transfer by examining logs
|
// Check if it's a token transfer by examining logs
|
||||||
let tokenTransfer = null;
|
let tokenTransfers = [];
|
||||||
|
// Simple in-memory cache for token metadata to avoid rate limiting
|
||||||
|
const TOKEN_METADATA_CACHE = ethOperator.TOKEN_METADATA_CACHE || {};
|
||||||
|
ethOperator.TOKEN_METADATA_CACHE = TOKEN_METADATA_CACHE;
|
||||||
|
|
||||||
if (receipt && receipt.logs.length > 0) {
|
if (receipt && receipt.logs.length > 0) {
|
||||||
// Try to decode ERC20 Transfer event
|
// Try to decode ERC20 Transfer event
|
||||||
const transferEventSignature = ethers.utils.id('Transfer(address,address,uint256)');
|
const transferEventSignature = ethers.utils.id('Transfer(address,address,uint256)');
|
||||||
const transferLog = receipt.logs.find(log => log.topics[0] === transferEventSignature);
|
const transferLogs = receipt.logs.filter(log => log.topics[0] === transferEventSignature);
|
||||||
|
|
||||||
if (transferLog) {
|
if (transferLogs.length > 0) {
|
||||||
try {
|
try {
|
||||||
const tokenContract = new ethers.Contract(transferLog.address, ERC20ABI, provider);
|
// Process all transfer logs
|
||||||
const [symbol, decimals] = await Promise.all([
|
tokenTransfers = await Promise.all(transferLogs.map(async (transferLog) => {
|
||||||
tokenContract.symbol().catch(() => 'TOKEN'),
|
const contractAddress = transferLog.address;
|
||||||
tokenContract.decimals().catch(() => 18)
|
let symbol, decimals;
|
||||||
]);
|
|
||||||
|
|
||||||
const from = ethers.utils.getAddress('0x' + transferLog.topics[1].slice(26));
|
// Check cache first
|
||||||
const to = ethers.utils.getAddress('0x' + transferLog.topics[2].slice(26));
|
if (TOKEN_METADATA_CACHE[contractAddress]) {
|
||||||
const value = parseFloat(ethers.utils.formatUnits(transferLog.data, decimals));
|
({ symbol, decimals } = TOKEN_METADATA_CACHE[contractAddress]);
|
||||||
|
} else {
|
||||||
|
// Fetch from network if not cached
|
||||||
|
const tokenContract = new ethers.Contract(contractAddress, ERC20ABI, provider);
|
||||||
|
[symbol, decimals] = await Promise.all([
|
||||||
|
tokenContract.symbol().catch(() => 'TOKEN'),
|
||||||
|
tokenContract.decimals().catch(() => 18)
|
||||||
|
]);
|
||||||
|
// Store in cache
|
||||||
|
TOKEN_METADATA_CACHE[contractAddress] = { symbol, decimals };
|
||||||
|
}
|
||||||
|
|
||||||
tokenTransfer = {
|
const from = ethers.utils.getAddress('0x' + transferLog.topics[1].slice(26));
|
||||||
from,
|
const to = ethers.utils.getAddress('0x' + transferLog.topics[2].slice(26));
|
||||||
to,
|
const value = parseFloat(ethers.utils.formatUnits(transferLog.data, decimals));
|
||||||
value,
|
|
||||||
symbol,
|
return {
|
||||||
contractAddress: transferLog.address
|
from,
|
||||||
};
|
to,
|
||||||
|
value,
|
||||||
|
symbol,
|
||||||
|
contractAddress
|
||||||
|
};
|
||||||
|
}));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Could not decode token transfer:', e);
|
console.warn('Could not decode token transfers:', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -552,7 +580,7 @@
|
|||||||
input: tx.data,
|
input: tx.data,
|
||||||
status: receipt ? (receipt.status === 1 ? 'success' : 'failed') : 'pending',
|
status: receipt ? (receipt.status === 1 ? 'success' : 'failed') : 'pending',
|
||||||
isError: receipt ? receipt.status !== 1 : false,
|
isError: receipt ? receipt.status !== 1 : false,
|
||||||
tokenTransfer: tokenTransfer,
|
tokenTransfers: tokenTransfers,
|
||||||
logs: receipt ? receipt.logs : [],
|
logs: receipt ? receipt.logs : [],
|
||||||
type: tx.type
|
type: tx.type
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user