Workflow updating files of floethereum

This commit is contained in:
RanchiMall Dev 2026-01-31 12:37:06 +00:00
parent 87220ae53e
commit 3a2f5b7ec9
2 changed files with 154 additions and 93 deletions

View File

@ -822,56 +822,71 @@
loadTransactionsPage(ethAddress, floAddress, currentPage);
}
let allTransactionsCache = [];
let currentBalances = { eth: '0', usdc: '0', usdt: '0' };
async function loadTransactionsPage(ethAddress, floAddress, page) {
buttonLoader('check_balance_button', true);
try {
const results = await Promise.allSettled([
ethOperator.getBalance(ethAddress),
ethOperator.getTokenBalance(ethAddress, 'usdc'),
ethOperator.getTokenBalance(ethAddress, 'usdt'),
ethOperator.getTransactionHistory(ethAddress, {
page: page,
offset: TRANSACTIONS_PER_PAGE,
sort: 'desc'
let transactions = [];
let etherBalance = '0', usdcBalance = '0', usdtBalance = '0';
// Fetch new data if it's the first page (Search or Refresh)
if (page === 1) {
const results = await Promise.allSettled([
ethOperator.getBalance(ethAddress),
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 => {
if (result) return
compactIDB.addData('contacts', {
ethAddress,
}, floAddress || ethAddress).then(() => {
renderSearchedAddressList()
}).catch((error) => {
console.error(error)
})
})
renderBalanceAndTransactions(ethAddress, floAddress, etherBalance, usdcBalance, usdtBalance, transactions, page);
renderBalanceAndTransactions(ethAddress, floAddress, etherBalance, usdcBalance, usdtBalance, paginatedTransactions, page, hasNextPage);
} catch (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
const url = new URL(window.location);
url.searchParams.set('address', ethAddress);
@ -890,7 +905,7 @@
window.history.pushState({}, '', url.pathname + url.search + url.hash);
// Determine if pagination buttons should be enabled
const hasNextPage = transactions.length >= TRANSACTIONS_PER_PAGE;
// hasNextPage is now passed in
const hasPrevPage = page > 1;
renderElem(getRef('eth_balance_wrapper'), html`
@ -1124,14 +1139,32 @@
</div>
<div class="grid gap-0-5">
<div class="label">To</div>
<div class="label">To (Contract)</div>
<sm-copy value=${txDetails.to}></sm-copy>
</div>
${txDetails.tokenTransfer ? html`
${txDetails.tokenTransfers && txDetails.tokenTransfers.length > 0 ? html`
<div class="grid gap-0-5">
<div class="label">Token Transfer</div>
<strong>${txDetails.tokenTransfer.value} ${txDetails.tokenTransfer.symbol}</strong>
<div class="label">Token Transfers</div>
<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>
` : html`
<div class="grid gap-0-5">

View File

@ -257,7 +257,7 @@
try {
if (!address || !isValidAddress(address))
return new Error('Invalid address');
// Use read-only provider (public RPC) for balance checks
const provider = getProvider(true);
const balanceWei = await provider.getBalance(address);
@ -274,7 +274,7 @@
return new Error("Token not specified");
if (!CONTRACT_ADDRESSES[token] && contractAddress)
return new Error('Contract address of token not available')
// Use read-only provider (public RPC) for token balance checks
const provider = getProvider(true);
const tokenAddress = CONTRACT_ADDRESSES[token] || contractAddress;
@ -307,17 +307,17 @@
const provider = getProvider();
const signer = new ethers.Wallet(privateKey, provider);
const limit = await estimateGas({ privateKey, receiver, amount })
// Get current fee data from the network
const feeData = await provider.getFeeData();
// Calculate priority fee (tip to miners) - use 1.5 gwei or the network's suggested priority fee, whichever is higher
const priorityFee = feeData.maxPriorityFeePerGas || ethers.utils.parseUnits("1.5", "gwei");
// Calculate max fee per gas (base fee + priority fee)
// Use the network's suggested maxFeePerGas or calculate it manually
let maxFee = feeData.maxFeePerGas;
// If maxFeePerGas is not available or is less than priority fee, calculate it
if (!maxFee || maxFee.lt(priorityFee)) {
// Get the base fee from the latest block and add our priority fee
@ -326,13 +326,13 @@
// maxFee = (baseFee * 2) + priorityFee to account for potential base fee increases
maxFee = baseFee.mul(2).add(priorityFee);
}
// Ensure maxFee is at least 1.5x the priority fee for safety
const minMaxFee = priorityFee.mul(15).div(10); // 1.5x priority fee
if (maxFee.lt(minMaxFee)) {
maxFee = minMaxFee;
}
// Creating and sending the transaction object
return signer.sendTransaction({
to: receiver,
@ -365,8 +365,8 @@
return tokenContract.transfer(receiver, amountWei)
}
const ETHERSCAN_API_KEY = 'M3YBAHI21FVE7VS2FEKU6ZFGRA128WUVQK';
const ETHERSCAN_API_KEY = 'M3YBAHI21FVE7VS2FEKU6ZFGRA128WUVQK';
/**
* Get transaction history for an Ethereum address
@ -390,7 +390,7 @@
// Fetch normal transactions using V2 API
const normalTxUrl = `https://api.etherscan.io/v2/api?chainid=1&module=account&action=txlist&address=${address}&startblock=${startBlock}&endblock=${endBlock}&page=${page}&offset=${offset}&sort=${sort}&apikey=${ETHERSCAN_API_KEY}`;
const normalTxResponse = await fetch(normalTxUrl);
const normalTxData = await normalTxResponse.json();
@ -410,15 +410,25 @@
// Fetch ERC20 token transfers using V2 API
const tokenTxUrl = `https://api.etherscan.io/v2/api?chainid=1&module=account&action=tokentx&address=${address}&startblock=${startBlock}&endblock=${endBlock}&page=${page}&offset=${offset}&sort=${sort}&apikey=${ETHERSCAN_API_KEY}`;
const tokenTxResponse = await fetch(tokenTxUrl);
const tokenTxData = await tokenTxResponse.json();
const tokenTransfers = tokenTxData.status === '1' ? tokenTxData.result : [];
// 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)
allTransactions.sort((a, b) => parseInt(b.timeStamp) - parseInt(a.timeStamp));
@ -427,9 +437,9 @@
return allTransactions.map(tx => {
const isTokenTransfer = tx.tokenSymbol !== undefined;
const isReceived = tx.to.toLowerCase() === address.toLowerCase();
let value, symbol, decimals;
if (isTokenTransfer) {
decimals = parseInt(tx.tokenDecimal) || 18;
value = parseFloat(ethers.utils.formatUnits(tx.value, decimals));
@ -480,57 +490,75 @@
// Use read-only provider for fetching transaction details
const provider = getProvider(true);
// Get transaction details
const tx = await provider.getTransaction(txHash);
if (!tx) {
throw new Error('Transaction not found');
}
// Get transaction receipt for status and gas used
const receipt = await provider.getTransactionReceipt(txHash);
// Get current block number for confirmations
const currentBlock = await provider.getBlockNumber();
// Get block details for timestamp
const block = await provider.getBlock(tx.blockNumber);
// Calculate gas fee
const gasUsed = receipt ? receipt.gasUsed : null;
const effectiveGasPrice = receipt ? receipt.effectiveGasPrice : tx.gasPrice;
const gasFee = gasUsed && effectiveGasPrice ?
const gasFee = gasUsed && effectiveGasPrice ?
parseFloat(ethers.utils.formatEther(gasUsed.mul(effectiveGasPrice))) : null;
// 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) {
// Try to decode ERC20 Transfer event
const transferEventSignature = ethers.utils.id('Transfer(address,address,uint256)');
const transferLog = receipt.logs.find(log => log.topics[0] === transferEventSignature);
if (transferLog) {
const transferLogs = receipt.logs.filter(log => log.topics[0] === transferEventSignature);
if (transferLogs.length > 0) {
try {
const tokenContract = new ethers.Contract(transferLog.address, ERC20ABI, provider);
const [symbol, decimals] = await Promise.all([
tokenContract.symbol().catch(() => 'TOKEN'),
tokenContract.decimals().catch(() => 18)
]);
const from = ethers.utils.getAddress('0x' + transferLog.topics[1].slice(26));
const to = ethers.utils.getAddress('0x' + transferLog.topics[2].slice(26));
const value = parseFloat(ethers.utils.formatUnits(transferLog.data, decimals));
tokenTransfer = {
from,
to,
value,
symbol,
contractAddress: transferLog.address
};
// Process all transfer logs
tokenTransfers = await Promise.all(transferLogs.map(async (transferLog) => {
const contractAddress = transferLog.address;
let symbol, decimals;
// Check cache first
if (TOKEN_METADATA_CACHE[contractAddress]) {
({ 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 };
}
const from = ethers.utils.getAddress('0x' + transferLog.topics[1].slice(26));
const to = ethers.utils.getAddress('0x' + transferLog.topics[2].slice(26));
const value = parseFloat(ethers.utils.formatUnits(transferLog.data, decimals));
return {
from,
to,
value,
symbol,
contractAddress
};
}));
} 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,
status: receipt ? (receipt.status === 1 ? 'success' : 'failed') : 'pending',
isError: receipt ? receipt.status !== 1 : false,
tokenTransfer: tokenTransfer,
tokenTransfers: tokenTransfers,
logs: receipt ? receipt.logs : [],
type: tx.type
};