From 1fb3741a23af21c02cbe658d11d0ef893a0f1cd4 Mon Sep 17 00:00:00 2001 From: joungminsung Date: Wed, 20 May 2026 10:43:06 +0900 Subject: [PATCH 1/2] Add pool transactions infinite loading --- src/api/pool.js | 137 +++++++- src/components/pool/Pool.svelte | 4 +- src/components/pool/PoolTransactions.svelte | 357 ++++++++++++++++++++ src/lib/config.js | 9 +- 4 files changed, 504 insertions(+), 3 deletions(-) create mode 100644 src/components/pool/PoolTransactions.svelte diff --git a/src/api/pool.js b/src/api/pool.js index 03bf4d3..1f7fb4f 100644 --- a/src/api/pool.js +++ b/src/api/pool.js @@ -1,7 +1,10 @@ +import { ethers } from 'ethers' import { get } from 'svelte/store' -import { CURRENCY_DECIMALS, BPS_DIVIDER } from '@lib/config' +import { CURRENCY_DECIMALS, BPS_DIVIDER, POOL_TRANSACTIONS_BLOCK_RANGE, POOL_TRANSACTIONS_MAX_RANGES } from '@lib/config' +import { ABIS } from '@lib/abis' import { getContract } from '@lib/contracts' import { formatUnits, parseUnits } from '@lib/formatters' +import { provider } from '@lib/stores' import { address, poolBalances, bufferBalances, poolStakes, poolStatsDaily, poolStatsWeekly, poolWithdrawalFees, poolDepositTaxes, poolWithdrawalTaxes, globalUPLs } from '@lib/stores' import { getAssetAddress, getAssetAddresses, getLabelForAsset, getChainData } from '@lib/utils' import { showToast, showError } from '@lib/ui' @@ -131,3 +134,135 @@ export async function withdraw(_asset, _amount) { showError(e); } } + +function formatPoolTransactionAmount(assetAddress, amount) { + const asset = getLabelForAsset(assetAddress); + return formatUnits(amount, CURRENCY_DECIMALS[asset]); +} + +function normalizePoolTransaction(event, timestamp) { + const args = event.args; + const base = { + id: `${event.transactionHash}-${event.logIndex}`, + type: event.event, + user: args.user, + asset: args.asset, + assetLabel: getLabelForAsset(args.asset), + amount: formatPoolTransactionAmount(args.asset, args.amount), + poolBalance: formatPoolTransactionAmount(args.asset, args.poolBalance), + market: args.market, + timestamp, + blockNumber: event.blockNumber, + logIndex: event.logIndex, + transactionHash: event.transactionHash + }; + + if (args.bufferBalance) base.bufferBalance = formatPoolTransactionAmount(args.asset, args.bufferBalance); + if (args.bufferToPoolAmount) base.bufferToPoolAmount = formatPoolTransactionAmount(args.asset, args.bufferToPoolAmount); + if (args.clpAmount) base.clpAmount = formatUnits(args.clpAmount); + + return base; +} + +async function getBlockTimestamps(_provider, events) { + const timestamps = {}; + const blockNumbers = [...new Set(events.map((event) => event.blockNumber))]; + await Promise.all(blockNumbers.map(async (blockNumber) => { + const block = await _provider.getBlock(blockNumber); + timestamps[blockNumber] = block?.timestamp; + })); + return timestamps; +} + +function withTimeout(promise, timeout = 5000) { + return Promise.race([ + promise, + new Promise((resolve) => setTimeout(() => resolve([]), timeout)) + ]); +} + +async function queryPoolEvents(pool, filters, fromBlock, toBlock) { + let events = []; + for (const filter of filters) { + try { + events = events.concat(await withTimeout(pool.queryFilter(filter, fromBlock, toBlock))); + } catch(e) { + console.warn('Pool transaction event query failed', { fromBlock, toBlock, topic: filter.topics?.[0] }, e); + } + } + return events; +} + +async function getPoolContractForTransactions() { + const currentProvider = get(provider); + const rpc = getChainData('fallbackRpcs')?.[0]; + const dataStoreAddress = getChainData('dataStore'); + if (rpc && dataStoreAddress) { + const fallbackProvider = new ethers.providers.JsonRpcProvider(rpc); + const dataStore = new ethers.Contract(dataStoreAddress, ABIS.DataStore, fallbackProvider); + const poolAddress = await dataStore.getAddress('Pool'); + return { + pool: new ethers.Contract(poolAddress, ABIS.Pool, fallbackProvider), + provider: fallbackProvider + }; + } + + try { + const pool = await getContract('Pool'); + if (pool) return { pool, provider: currentProvider }; + } catch(e) { + console.warn('Default provider failed while loading pool transactions', e); + } + + const defaultRpc = getChainData('rpc'); + if (!defaultRpc || !dataStoreAddress) return {}; + + const fallbackProvider = new ethers.providers.JsonRpcProvider(defaultRpc); + const dataStore = new ethers.Contract(dataStoreAddress, ABIS.DataStore, fallbackProvider); + const poolAddress = await dataStore.getAddress('Pool'); + return { + pool: new ethers.Contract(poolAddress, ABIS.Pool, fallbackProvider), + provider: fallbackProvider + }; +} + +export async function getPoolTransactionsPage(params = {}) { + const { pool, provider: _provider } = await getPoolContractForTransactions(); + if (!_provider || !pool) { + return { + transactions: [], + nextToBlock: params.toBlock, + hasMore: false + }; + } + + let toBlock = params.toBlock || await _provider.getBlockNumber(); + let transactions = []; + let rangesChecked = 0; + + while (toBlock > 0 && transactions.length < (params.count || 1) && rangesChecked < POOL_TRANSACTIONS_MAX_RANGES) { + const fromBlock = Math.max(0, toBlock - POOL_TRANSACTIONS_BLOCK_RANGE + 1); + const filters = [ + pool.filters.PoolDeposit(), + pool.filters.PoolWithdrawal(), + pool.filters.PoolPayIn(), + pool.filters.PoolPayOut() + ]; + + const events = (await queryPoolEvents(pool, filters, fromBlock, toBlock)).sort((a, b) => { + if (a.blockNumber == b.blockNumber) return b.logIndex - a.logIndex; + return b.blockNumber - a.blockNumber; + }); + const timestamps = await getBlockTimestamps(_provider, events); + + transactions = transactions.concat(events.map((event) => normalizePoolTransaction(event, timestamps[event.blockNumber]))); + toBlock = fromBlock - 1; + rangesChecked++; + } + + return { + transactions, + nextToBlock: toBlock, + hasMore: toBlock > 0 + }; +} diff --git a/src/components/pool/Pool.svelte b/src/components/pool/Pool.svelte index 7bc1f66..2bcd2b0 100644 --- a/src/components/pool/Pool.svelte +++ b/src/components/pool/Pool.svelte @@ -1,6 +1,7 @@ @@ -15,5 +16,6 @@
+ -
\ No newline at end of file + diff --git a/src/components/pool/PoolTransactions.svelte b/src/components/pool/PoolTransactions.svelte new file mode 100644 index 0000000..b7ff74b --- /dev/null +++ b/src/components/pool/PoolTransactions.svelte @@ -0,0 +1,357 @@ + + + + +
+
+
Pool Transactions
+
Latest deposits, withdrawals, pay ins, and payouts across all pools.
+
+ +
+
+
+
+
Type
+
Asset
+
Amount
+
Pool Balance
+
Buffer
+
Market / User
+
Time
+
Tx
+
+ +
+ {#if isLoading} +
{@html LOADING_ICON}
+ {:else if error} +
{error}
+ {:else if transactions.length == 0} +
No pool transactions found in the latest scanned blocks.
+ {:else} + {#each transactions as transaction (transaction.id)} +
+
+ {getTypeLabel(transaction.type)} +
+
{transaction.assetLabel || '-'}
+
+ + {formatAmount(transaction.amount)} + {#if formatExtraAmount(transaction)} + {formatExtraAmount(transaction)} + {/if} + +
+
{formatAmount(transaction.poolBalance)}
+
{formatAmount(transaction.bufferBalance)}
+
+ + {#if transaction.market} + {formatMarketName(transaction.market)} + + {shortAddress(transaction.user)} + + {:else if transaction.user} + {shortAddress(transaction.user)} + {:else} + - + {/if} + +
+
{formatDate(transaction.timestamp) || '-'}
+
+ {#if transaction.transactionHash} + View + {:else} + - + {/if} +
+
+ {/each} + {/if} +
+
+
+ + {#if !isLoading && hasMore} +
+ +
+ {/if} +
+
diff --git a/src/lib/config.js b/src/lib/config.js index dbb9e15..925f9bf 100644 --- a/src/lib/config.js +++ b/src/lib/config.js @@ -27,6 +27,12 @@ export const DEFAULT_HISTORY_SORT_KEY = ['timestamp', true]; export const DEFAULT_HISTORY_COUNT = 50; +export const DEFAULT_POOL_TRANSACTIONS_COUNT = 50; + +export const POOL_TRANSACTIONS_BLOCK_RANGE = 9999; + +export const POOL_TRANSACTIONS_MAX_RANGES = 1; + export const EXCLUDED_MARKETS = []; // ['HSI', 'KOSPI', 'USD-CNY', 'USD-JPY', 'USD-KRW', 'WTI-USD', 'XBR-USD', 'SPX500', 'DJI', 'NASDAQ', 'FTSE', 'DAX', 'NIKKEI', 'ASX200']; // dead and non chainlink markets, in private beta only export const CURRENCY_DECIMALS = { @@ -97,6 +103,7 @@ export const CHAINDATA = { label: 'arbitrum', explorer: 'https://arbiscan.io', rpc: 'https://arb1.arbitrum.io/rpc', // for walletconnect + fallbackRpcs: ['https://arbitrum-one.publicnode.com'], dataEndpoint: 'https://data.cap.io/api', dataStore: '0xa64694E51B22A081EA1e4051EF4EA1b715b47026', cap: '0x031d35296154279dc1984dcd93e392b1f946737b', @@ -127,4 +134,4 @@ export const CHAINDATA = { ETH: ADDRESS_ZERO } } -} \ No newline at end of file +} From a5fdbdbc4a955ac894feb6ea5ed39468aa7d1546 Mon Sep 17 00:00:00 2001 From: joungminsung Date: Wed, 20 May 2026 12:18:11 +0900 Subject: [PATCH 2/2] Harden pool transaction fallback RPC handling --- src/api/pool.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/api/pool.js b/src/api/pool.js index 1f7fb4f..39d921e 100644 --- a/src/api/pool.js +++ b/src/api/pool.js @@ -198,13 +198,17 @@ async function getPoolContractForTransactions() { const rpc = getChainData('fallbackRpcs')?.[0]; const dataStoreAddress = getChainData('dataStore'); if (rpc && dataStoreAddress) { - const fallbackProvider = new ethers.providers.JsonRpcProvider(rpc); - const dataStore = new ethers.Contract(dataStoreAddress, ABIS.DataStore, fallbackProvider); - const poolAddress = await dataStore.getAddress('Pool'); - return { - pool: new ethers.Contract(poolAddress, ABIS.Pool, fallbackProvider), - provider: fallbackProvider - }; + try { + const fallbackProvider = new ethers.providers.JsonRpcProvider(rpc); + const dataStore = new ethers.Contract(dataStoreAddress, ABIS.DataStore, fallbackProvider); + const poolAddress = await dataStore.getAddress('Pool'); + return { + pool: new ethers.Contract(poolAddress, ABIS.Pool, fallbackProvider), + provider: fallbackProvider + }; + } catch(e) { + console.warn('Fallback RPC failed while loading pool transactions', e); + } } try {