diff --git a/src/api/pool.js b/src/api/pool.js index 03bf4d3..91f0c0d 100644 --- a/src/api/pool.js +++ b/src/api/pool.js @@ -6,6 +6,138 @@ import { address, poolBalances, bufferBalances, poolStakes, poolStatsDaily, pool import { getAssetAddress, getAssetAddresses, getLabelForAsset, getChainData } from '@lib/utils' import { showToast, showError } from '@lib/ui' +const POOL_HISTORY_EVENTS = ['PoolDeposit', 'PoolWithdrawal', 'PoolPayIn', 'PoolPayOut']; +const POOL_HISTORY_BLOCK_SPAN = 10000; +const POOL_HISTORY_PAGE_SIZE = 50; +const blockTimestampCache = new Map(); + +function getPoolTransactionType(eventName) { + return { + PoolDeposit: 'deposit', + PoolWithdrawal: 'withdrawal', + PoolPayIn: 'pay in', + PoolPayOut: 'pay out' + }[eventName]; +} + +function isBeforeCursor(item, cursor) { + if (!cursor) return true; + if (item.blockNumber < cursor.blockNumber) return true; + return item.blockNumber == cursor.blockNumber && item.logIndex < cursor.logIndex; +} + +function formatPoolTransaction(log, parsedLog) { + const args = parsedLog.args; + const asset = getLabelForAsset(args.asset) || args.asset; + const decimals = CURRENCY_DECIMALS[asset] || 18; + const explorer = getChainData('explorer'); + const logIndex = log.logIndex == undefined ? 0 : log.logIndex; + + const transaction = { + id: `${log.transactionHash}:${logIndex}`, + type: getPoolTransactionType(parsedLog.name), + user: args.user, + asset, + market: args.market || '', + amount: formatUnits(args.amount, decimals), + poolBalance: formatUnits(args.poolBalance, decimals), + blockNumber: log.blockNumber, + logIndex, + timestamp: 0, + transactionHash: log.transactionHash, + explorerUrl: explorer ? `${explorer}/tx/${log.transactionHash}` : '' + }; + + if (args.bufferBalance != undefined) { + transaction.bufferBalance = formatUnits(args.bufferBalance, decimals); + } + + return transaction; +} + +async function getBlockTimestamp(provider, blockNumber) { + if (blockTimestampCache.has(blockNumber)) return blockTimestampCache.get(blockNumber); + try { + const block = await provider.getBlock(blockNumber); + const timestamp = block && block.timestamp ? block.timestamp : 0; + blockTimestampCache.set(blockNumber, timestamp); + return timestamp; + } catch (e) { + blockTimestampCache.set(blockNumber, 0); + return 0; + } +} + +export async function getPoolTransactions(params = {}) { + const contract = await getContract('Pool'); + if (!contract || !contract.provider || !contract.address) { + return {items: [], nextCursor: null, hasMore: false}; + } + + const pageSize = Math.max(1, Math.min(Number(params.pageSize) || POOL_HISTORY_PAGE_SIZE, 100)); + const provider = contract.provider; + const latestBlock = await provider.getBlockNumber(); + const suppliedCursor = params.cursor; + let cursor = suppliedCursor && Number.isInteger(Number(suppliedCursor.blockNumber)) + ? {blockNumber: Number(suppliedCursor.blockNumber), logIndex: Number(suppliedCursor.logIndex) || 0} + : null; + let toBlock = cursor ? Math.min(cursor.blockNumber, latestBlock) : latestBlock; + let reachedStart = false; + let transactions = []; + const eventTopics = POOL_HISTORY_EVENTS.map((eventName) => contract.interface.getEventTopic(eventName)); + + while (transactions.length < pageSize && toBlock >= 0) { + const fromBlock = Math.max(0, toBlock - POOL_HISTORY_BLOCK_SPAN + 1); + const logs = await provider.getLogs({ + address: contract.address, + fromBlock, + toBlock, + topics: [eventTopics] + }); + + for (const log of logs) { + try { + const parsedLog = contract.interface.parseLog(log); + if (!parsedLog || !getPoolTransactionType(parsedLog.name)) continue; + const transaction = formatPoolTransaction(log, parsedLog); + if (isBeforeCursor(transaction, cursor)) transactions.push(transaction); + } catch (e) { + // Ignore logs that cannot be decoded by this contract ABI. + } + } + + if (fromBlock == 0) { + reachedStart = true; + break; + } + + toBlock = fromBlock - 1; + // The cursor only applies to the first block range. Older ranges are unfiltered. + cursor = null; + } + + transactions.sort((a, b) => { + if (a.blockNumber != b.blockNumber) return b.blockNumber - a.blockNumber; + return b.logIndex - a.logIndex; + }); + + const items = transactions.slice(0, pageSize); + const blockNumbers = [...new Set(items.map((item) => item.blockNumber))]; + const timestamps = await Promise.all(blockNumbers.map(async (blockNumber) => { + return [blockNumber, await getBlockTimestamp(provider, blockNumber)]; + })); + const timestampByBlock = new Map(timestamps); + for (const item of items) item.timestamp = timestampByBlock.get(item.blockNumber) || 0; + + const lastItem = items[items.length - 1]; + const hasMore = !reachedStart || transactions.length > pageSize; + return { + items, + nextCursor: hasMore && lastItem ? {blockNumber: lastItem.blockNumber, logIndex: lastItem.logIndex} : null, + hasMore + }; +} + export async function getPoolBalances() { const contract = await getContract('PoolStore'); const assetAddresses = getAssetAddresses(); diff --git a/src/components/pool/PoolTransactions.svelte b/src/components/pool/PoolTransactions.svelte new file mode 100644 index 0000000..2e3335f --- /dev/null +++ b/src/components/pool/PoolTransactions.svelte @@ -0,0 +1,251 @@ + + + + +
+
+
Pool transactions
+
Deposits, withdrawals, trader pay-ins, and trader pay-outs.
+
+ + {#if error} +
{error}
+ {:else if transactions.length == 0 && !isLoading} +
No pool transactions found.
+ {:else} +
+
+
+
Type
+
Asset
+
Market
+
Amount
+
Pool balance
+
Buffer
+
User / time
+
Transaction
+
+ {#each transactions as item (item.id)} +
+
{item.type}
+
{item.asset}
+
{item.market || '-'}
+
{numberWithCommas(item.amount)} {item.asset}
+
{numberWithCommas(item.poolBalance)} {item.asset}
+
{item.bufferBalance == undefined ? '-' : `${numberWithCommas(item.bufferBalance)} ${item.asset}`}
+
{shortAddress(item.user)}{item.timestamp ? formatDate(item.timestamp) : `Block ${item.blockNumber}`}
+
{#if item.explorerUrl}{item.transactionHash.slice(0, 6)}…{:else}-{/if}
+
+ {/each} +
+
+ + + {/if} + +
+
diff --git a/src/components/pool/Pools.svelte b/src/components/pool/Pools.svelte index 2ad0552..d159094 100644 --- a/src/components/pool/Pools.svelte +++ b/src/components/pool/Pools.svelte @@ -1,6 +1,7 @@