Workflow updating files of floethereum
This commit is contained in:
parent
87220ae53e
commit
3a2f5b7ec9
@ -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">
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user