Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/adaptors/uniswap-v3/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,8 @@ const main = async (timestamp = null) => {
);
})
);
const [data, bobPools] = await Promise.all([dataPromise, getOnchainPools()]);
data.push(bobPools);
const [data, onchainPools] = await Promise.all([dataPromise, getOnchainPools()]);
data.push(onchainPools);

const pools = await addMerklRewardApy(
data
Expand Down
164 changes: 100 additions & 64 deletions src/adaptors/uniswap-v3/onchain.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,66 @@ const chains = {
blockTime: 2,
ui: 'oku',
},
monad: {
factory: '0x204faca1764b154221e35c0d20abb3c525710498',
fromBlock: 29255827,
blockTime: 0.4,
ui: 'uniswap',
},
};

const EVENTS = {
PoolCreated: 'event PoolCreated(address indexed token0, address indexed token1, uint24 indexed fee, int24 tickSpacing, address pool)',
Swap: 'event Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick)',
};

const CONCURRENT_POOL_SCANS = 5;

const sumSwapUsd = (swapLogs, pool, tokenDecimals, prices) => {
const toUsd = (amount, token) =>
BigNumber(amount)
.times(10 ** (18 - tokenDecimals[token]))
.times(prices.pricesByAddress[token])
.div(1e18);

const sumValues = (values) =>
values.reduce((total, value) => total + value, 0n);

const amounts0 = swapLogs
.map((log) => BigInt(log.args.amount0))
.filter((amount) => amount > 0n);
const amounts1 = swapLogs
.map((log) => BigInt(log.args.amount1))
.filter((amount) => amount > 0n);

const fees0 = sumValues(
amounts0.map((amount) => (amount * pool.fee) / 1_000_000n)
);
const fees1 = sumValues(
amounts1.map((amount) => (amount * pool.fee) / 1_000_000n)
);

return {
feeUsd: toUsd(fees0, pool.token0).plus(toUsd(fees1, pool.token1)),
volumeUsd: toUsd(sumValues(amounts0), pool.token0).plus(
toUsd(sumValues(amounts1), pool.token1)
),
};
};

const getPricesChunked = async (tokens, chain) => {
const pricesByAddress = {};
const pricesBySymbol = {};

for (let start = 0; start < tokens.length; start += 50) {
const prices = await utils.getPrices(tokens.slice(start, start + 50), chain);
Object.assign(pricesByAddress, prices.pricesByAddress);
Object.assign(pricesBySymbol, prices.pricesBySymbol);
}

return { pricesByAddress, pricesBySymbol };
};

const getPools = async (chain) => {
const config = chains[chain];
const dataPools = [];
Expand All @@ -31,17 +84,20 @@ const getPools = async (chain) => {
toBlock: currentBlock.number,
});

const pools = poolCreatedLogs.map((log) => ({
token0: log.args.token0,
token1: log.args.token1,
address: log.args.pool,
fee: BigInt(log.args.fee),
}));
const seenPools = new Set();
const pools = poolCreatedLogs
.map((log) => ({
token0: log.args.token0.toLowerCase(),
token1: log.args.token1.toLowerCase(),
address: log.args.pool.toLowerCase(),
fee: BigInt(log.args.fee),
}))
.filter((p) => !seenPools.has(p.address) && seenPools.add(p.address));

const tokens = getUniqueAddresses(
pools.map((p) => p.token0).concat(pools.map((p) => p.token1))
);
const prices = await utils.getPrices(tokens, chain);
const prices = await getPricesChunked(tokens, chain);

const tokenDecimals = Object.fromEntries(
(
Expand Down Expand Up @@ -95,6 +151,7 @@ const getPools = async (chain) => {
pools[i].balance1 = BigNumber(token1Balances.output[i].output);
});

const pricedPools = [];
for (const pool of pools) {
if (
!prices.pricesByAddress[pool.token0] ||
Expand All @@ -103,88 +160,67 @@ const getPools = async (chain) => {
continue;
}

const swapLogs = await sdk.getEventLogs({
chain,
target: pool.address,
eventAbi: EVENTS.Swap,
fromBlock: currentBlock.number - (24 * 3600) / config.blockTime,
toBlock: currentBlock.number,
});

const parsedSwapLogs = swapLogs.map((log) => log.args);

const totalFee0 = parsedSwapLogs
.map((log) => BigInt(log.amount0))
.filter((x) => x > 0n)
.reduce((sum, x) => sum + (x * pool.fee) / 1_000_000n, 0n);
const totalFee1 = parsedSwapLogs
.map((log) => BigInt(log.amount1))
.filter((x) => x > 0n)
.reduce((sum, x) => sum + (x * pool.fee) / 1_000_000n, 0n);

const feeValue0 = BigNumber(totalFee0)
const tvl0 = pool.balance0
.times(10 ** (18 - tokenDecimals[pool.token0]))
.times(prices.pricesByAddress[pool.token0])
.div(1e18);
const feeValue1 = BigNumber(totalFee1)
const tvl1 = pool.balance1
.times(10 ** (18 - tokenDecimals[pool.token1]))
.times(prices.pricesByAddress[pool.token1])
.div(1e18);

const feeValue = feeValue0.plus(feeValue1);

const totalVolume0 = parsedSwapLogs
.map((log) => BigInt(log.amount0))
.filter((x) => x > 0n)
.reduce((sum, x) => sum + x, 0n);
const totalVolume1 = parsedSwapLogs
.map((log) => BigInt(log.amount1))
.filter((x) => x > 0n)
.reduce((sum, x) => sum + x, 0n);
const tvl = tvl0.plus(tvl1);

const volumeValue0 = BigNumber(totalVolume0)
.times(10 ** (18 - tokenDecimals[pool.token0]))
.times(prices.pricesByAddress[pool.token0])
.div(1e18);
const volumeValue1 = BigNumber(totalVolume1)
.times(10 ** (18 - tokenDecimals[pool.token1]))
.times(prices.pricesByAddress[pool.token1])
.div(1e18);
// skip dust pools before the expensive per-pool swap log scans
if (!(tvl.toNumber() >= utils.MIN_TVL_USD)) {
continue;
}

const volumeValue = volumeValue0.plus(volumeValue1);
pricedPools.push({ ...pool, tvl });
}

const tvl0 = pool.balance0
.times(10 ** (18 - tokenDecimals[pool.token0]))
.times(prices.pricesByAddress[pool.token0])
.div(1e18);
const tvl1 = pool.balance1
.times(10 ** (18 - tokenDecimals[pool.token1]))
.times(prices.pricesByAddress[pool.token1])
.div(1e18);
const blocksPerDay = (24 * 3600) / config.blockTime;

const tvl = tvl0.plus(tvl1);
const scanPool = async (pool) => {
const swapLogs = await sdk.getEventLogs({
chain,
target: pool.address,
eventAbi: EVENTS.Swap,
fromBlock: currentBlock.number - blocksPerDay,
toBlock: currentBlock.number,
});

const apr = feeValue.div(tvl).times(100).times(365);
const apy = utils.aprToApy(apr.toNumber());
const totals = sumSwapUsd(swapLogs, pool, tokenDecimals, prices);
const apr = totals.feeUsd.div(pool.tvl).times(100).times(365);

const poolMeta = `${Number(pool.fee) / 1e4}%`;

dataPools.push({
return {
pool: pool.address,
chain,
project: 'uniswap-v3',
poolMeta,
symbol: [tokenSymbols[pool.token0], tokenSymbols[pool.token1]].join('-'),
tvlUsd: tvl.toNumber(),
apyBase: apy,
tvlUsd: pool.tvl.toNumber(),
apyBase: utils.aprToApy(apr.toNumber()),
underlyingTokens: [pool.token0, pool.token1],
url:
config.ui === 'oku'
? `https://oku.trade/app/${chain}/liquidity/${pool.address}`
: `https://app.uniswap.org/#/add/${pool.token0}/${pool.token1}/${pool.fee}?chain=${chain}`,
volumeUsd1d: volumeValue.toNumber(),
});
}
volumeUsd1d: totals.volumeUsd.toNumber(),
};
};

const scanQueue = [...pricedPools];
const runScanWorker = async () => {
while (scanQueue.length > 0) {
dataPools.push(await scanPool(scanQueue.shift()));
}
};
await Promise.all(
Array.from({ length: CONCURRENT_POOL_SCANS }, runScanWorker)
);

return dataPools;
};
Expand Down
Loading
Loading