Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
140 changes: 7 additions & 133 deletions src/adaptors/swell-liquid-restaking/index.js
Original file line number Diff line number Diff line change
@@ -1,63 +1,21 @@
const axios = require('axios');
const sdk = require('@defillama/sdk');
const { BigNumber } = require('ethers');

const abi = require('./abi.json');
const { getPriceApiData } = require('../utils');
const { getRepriceApy, getTvl } = require('./reprice');

const rswETH = '0xFAe103DC9cf190eD75350761e95403b7b8aFa6c0';

const EVENTS = {
Reprice: 'event Reprice(uint256 newEthReserves, uint256 newRswETHToETHRate, uint256 nodeOperatorRewards, uint256 swellTreasuryRewards, uint256 totalETHDeposited)',
};

const apy = async () => {
const totalSupply =
(
await sdk.api.abi.call({
target: rswETH,
abi: abi.find((m) => m.name === 'totalSupply'),
})
).output / 1e18;

const repriceEvents = await get7dRepriceEvents()
// sort by blockNumber descending
repriceEvents.sort((a,b) => Number(b.blockNumber) - Number(a.blockNumber));

const eventNow = repriceEvents[0];
const eventPrev = repriceEvents[1];
const { closestBefore, closestAfter, timestamp7DaysAgo } = await getCloseestTo7dAgo(repriceEvents);

const apr1d = await calcRate(eventNow, eventPrev);

let apr7d;
if (closestBefore && closestAfter) {
const interpolatedRate = await interpolate7d(closestBefore, closestAfter, timestamp7DaysAgo);
const endTime = await sdk.api.util.getTimestamp(eventNow.blockNumber, "ethereum");
const currentRate = BigNumber.from(eventNow.decoded.newRswETHToETHRate.toString());
const sevenDayRateChange = currentRate.mul(BigNumber.from(10).pow(18)).div(interpolatedRate).sub(BigNumber.from(10).pow(18));
const timeElapsed = BigNumber.from(endTime - timestamp7DaysAgo);
apr7d = sevenDayRateChange.mul(365 * 86400).div(timeElapsed).mul(100).toString() / 1e18;
}

const priceKey = `ethereum:${rswETH}`;
const ethPrice = (await getPriceApiData(`/prices/current/${priceKey}`)).coins[priceKey].price;

const rate = (await sdk.api.abi.call({
target: rswETH,
abi: abi.find((m) => m.name === 'getRate'),
})).output / 1e18;
const tvlUsd = totalSupply * rate * ethPrice;
const [apyFields, { tvlUsd, rate }] = await Promise.all([
getRepriceApy(rswETH, 'newRswETHToETHRate'),
getTvl(rswETH),
]);

return [
{
pool: rswETH,
project: 'swell-liquid-restaking',
chain: 'Ethereum',
symbol: 'rswETH',
tvlUsd: tvlUsd,
apyBase: apr1d,
...(apr7d !== undefined && { apyBase7d: apr7d }),
tvlUsd,
...apyFields,
...(rate > 0 && { pricePerShare: rate }),
underlyingTokens: ['0x0000000000000000000000000000000000000000'],
isIntrinsicSource: true,
Expand All @@ -71,87 +29,3 @@ module.exports = {
timetravel: false,
url: 'https://app.swellnetwork.io/stake/rsweth',
};

async function get7dRepriceEvents() {

const timestampNow = Math.floor(Date.now() / 1000);
// going with 14 days to allow buffer
const timestamp14dayAgo = timestampNow - 86400 * 14;

const blockNow = await sdk.api.util.getLatestBlock("ethereum")
const block14dayAgo = (await getPriceApiData(`/block/ethereum/${timestamp14dayAgo}`)).height;

const repriceEvents = await sdk.getEventLogs({
target: rswETH,
eventAbi: EVENTS.Reprice,
fromBlock: block14dayAgo,
toBlock: blockNow.number,
chain: "ethereum",
});

// Map args to decoded for backwards compatibility with calcRate and interpolate functions
return repriceEvents.map((ev) => {
ev.decoded = ev.args;
return ev;
});
}

async function calcRate(
eventNow,
eventPrev
) {
const rateDelta = (Number(eventNow.decoded[1])/Number(eventPrev.decoded[1])) - 1;
const blockDelta = Number(eventNow.blockNumber) - Number(eventPrev.blockNumber);
const timeDeltaSeconds = blockDelta*12; // assuming 12 seconds per block
const apr1d = (rateDelta*365*100)/(timeDeltaSeconds/86400);

return apr1d;
}


async function getCloseestTo7dAgo(repriceEvents) {
const timestampNow = Math.floor(Date.now() / 1000);
const timestamp7DaysAgo = timestampNow - 86400 * 7;

let closestBefore = null;
let closestAfter = null;
let minDiffBefore = Infinity;
let minDiffAfter = Infinity;

for (const repriceEvent of repriceEvents) {
const eventTimestamp = await sdk.api.util.getTimestamp(repriceEvent.blockNumber, "ethereum");
const timeDiff = eventTimestamp - timestamp7DaysAgo;

if (timeDiff <= 0 && Math.abs(timeDiff) < minDiffBefore) {
minDiffBefore = Math.abs(timeDiff);
closestBefore = repriceEvent;
} else if (timeDiff > 0 && timeDiff < minDiffAfter) {
minDiffAfter = timeDiff;
closestAfter = repriceEvent;
}
}

//console.log("Closest event before 7 days ago:", closestBefore);
//console.log("Closest event after 7 days ago:", closestAfter);

return { closestBefore, closestAfter, timestamp7DaysAgo };
}

async function interpolate7d(closestBefore, closestAfter, timestamp7DaysAgo) {
const beforeTimestamp = await sdk.api.util.getTimestamp(closestBefore.blockNumber, "ethereum");
const afterTimestamp = await sdk.api.util.getTimestamp(closestAfter.blockNumber, "ethereum");

const timeDiff = afterTimestamp - beforeTimestamp;
const rateBefore = BigNumber.from(closestBefore.decoded.newRswETHToETHRate.toString());
const rateAfter = BigNumber.from(closestAfter.decoded.newRswETHToETHRate.toString());

const rateDiff = rateAfter.sub(rateBefore);
const timeRatio = BigNumber.from(Math.floor((timestamp7DaysAgo - beforeTimestamp) / timeDiff * 1e18).toString());

const interpolatedRateBN = rateBefore.add(
rateDiff.mul(timeRatio).div(BigNumber.from(10).pow(18))
);

//console.log("Interpolated rate:", interpolatedRateBN.toString());
return interpolatedRateBN;
}
122 changes: 122 additions & 0 deletions src/adaptors/swell-liquid-restaking/reprice.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
const sdk = require('@defillama/sdk');
const { BigNumber } = require('ethers');

const { getPriceApiData } = require('../utils');

const WAD = 10n ** 18n;

const repriceEvent = (rateField) =>
`event Reprice(uint256 newEthReserves, uint256 ${rateField}, uint256 nodeOperatorRewards, uint256 swellTreasuryRewards, uint256 totalETHDeposited)`;

async function getRepriceEvents(token, rateField) {
const timestampNow = Math.floor(Date.now() / 1000);
const timestamp14dayAgo = timestampNow - 86400 * 14;

const blockNow = await sdk.api.util.getLatestBlock('ethereum');
const block14dayAgo = (await getPriceApiData(`/block/ethereum/${timestamp14dayAgo}`)).height;

const [repriceEvents, treasuryPct, nodeOperatorPct] = await Promise.all([
sdk.getEventLogs({
target: token,
eventAbi: repriceEvent(rateField),
fromBlock: block14dayAgo,
toBlock: blockNow.number,
chain: 'ethereum',
}),
...['swellTreasuryRewardPercentage', 'nodeOperatorRewardPercentage'].map(async (name) =>
BigInt((await sdk.api.abi.call({ target: token, abi: `uint256:${name}` })).output)
),
]);
const chargesFees = treasuryPct + nodeOperatorPct > 0n;

repriceEvents.sort((a, b) => Number(a.blockNumber) - Number(b.blockNumber));
let prevRate;
let unbacked = WAD;
return repriceEvents.map((ev) => {
const rate = BigInt(ev.args[rateField]);
const feeShares = BigInt(ev.args.nodeOperatorRewards) + BigInt(ev.args.swellTreasuryRewards);
if (prevRate !== undefined && chargesFees && feeShares === 0n) unbacked = (unbacked * rate) / prevRate;
prevRate = rate;
ev.backedRate = ((rate * WAD) / unbacked).toString();
return ev;
});
}

function calcRate(eventNow, eventPrev) {
const rateDelta = Number(eventNow.backedRate) / Number(eventPrev.backedRate) - 1;
const blockDelta = Number(eventNow.blockNumber) - Number(eventPrev.blockNumber);
const timeDeltaSeconds = blockDelta * 12;
return (rateDelta * 365 * 100) / (timeDeltaSeconds / 86400);
}

async function getClosestTo7dAgo(repriceEvents) {
const timestampNow = Math.floor(Date.now() / 1000);
const timestamp7DaysAgo = timestampNow - 86400 * 7;

let closestBefore = null;
let closestAfter = null;
let minDiffBefore = Infinity;
let minDiffAfter = Infinity;

for (const repriceEvent of repriceEvents) {
const eventTimestamp = await sdk.api.util.getTimestamp(repriceEvent.blockNumber, 'ethereum');
const timeDiff = eventTimestamp - timestamp7DaysAgo;

if (timeDiff <= 0 && Math.abs(timeDiff) < minDiffBefore) {
minDiffBefore = Math.abs(timeDiff);
closestBefore = repriceEvent;
} else if (timeDiff > 0 && timeDiff < minDiffAfter) {
minDiffAfter = timeDiff;
closestAfter = repriceEvent;
}
}

return { closestBefore, closestAfter, timestamp7DaysAgo };
}

async function interpolate7d(closestBefore, closestAfter, timestamp7DaysAgo) {
const beforeTimestamp = await sdk.api.util.getTimestamp(closestBefore.blockNumber, 'ethereum');
const afterTimestamp = await sdk.api.util.getTimestamp(closestAfter.blockNumber, 'ethereum');

const timeDiff = afterTimestamp - beforeTimestamp;
const rateBefore = BigNumber.from(closestBefore.backedRate);
const rateAfter = BigNumber.from(closestAfter.backedRate);

const rateDiff = rateAfter.sub(rateBefore);
const timeRatio = BigNumber.from(Math.floor((timestamp7DaysAgo - beforeTimestamp) / timeDiff * 1e18).toString());

return rateBefore.add(rateDiff.mul(timeRatio).div(BigNumber.from(10).pow(18)));
}

async function getRepriceApy(token, rateField) {
const repriceEvents = await getRepriceEvents(token, rateField);
if (repriceEvents.length < 2) return { apyBase: 0 };
repriceEvents.sort((a, b) => Number(b.blockNumber) - Number(a.blockNumber));

const eventNow = repriceEvents[0];
const apyBase = calcRate(eventNow, repriceEvents[1]);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

const { closestBefore, closestAfter, timestamp7DaysAgo } = await getClosestTo7dAgo(repriceEvents);
if (!closestBefore || !closestAfter) return { apyBase };

const interpolatedRate = await interpolate7d(closestBefore, closestAfter, timestamp7DaysAgo);
const endTime = await sdk.api.util.getTimestamp(eventNow.blockNumber, 'ethereum');
const currentRate = BigNumber.from(eventNow.backedRate);
const sevenDayRateChange = currentRate.mul(WAD.toString()).div(interpolatedRate).sub(WAD.toString());
const timeElapsed = BigNumber.from(endTime - timestamp7DaysAgo);
const apyBase7d = sevenDayRateChange.mul(365 * 86400).div(timeElapsed).mul(100).toString() / 1e18;

return { apyBase, apyBase7d };
}

async function getTvl(token) {
const [totalSupply, rate] = await Promise.all(
['totalSupply', 'getRate'].map(async (name) =>
(await sdk.api.abi.call({ target: token, abi: `uint256:${name}` })).output / 1e18
)
);
const ethPrice = (await getPriceApiData('/prices/current/coingecko:ethereum')).coins['coingecko:ethereum'].price;
return { tvlUsd: totalSupply * rate * ethPrice, rate };
}

module.exports = { getRepriceApy, getTvl };
38 changes: 6 additions & 32 deletions src/adaptors/swell-liquid-staking/index.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,12 @@
const axios = require('axios');
const sdk = require('@defillama/sdk');
const { getRepriceApy, getTvl } = require('../swell-liquid-restaking/reprice');

const abi = require('./abi.json');
const { getPriceApiData } = require('../utils');

const apr = 'https://v3.svc.swellnetwork.io/swell.v3.StatsService/All';
const apr7d = 'https://v3-public.svc.swellnetwork.io/api/tokens/sweth/apr';
const swETH = '0xf951E335afb289353dc249e82926178EaC7DEd78';

const apy = async () => {
const totalSupply =
(
await sdk.api.abi.call({
target: swETH,
abi: abi.find((m) => m.name === 'totalSupply'),
})
).output / 1e18;

const rate =
(
await sdk.api.abi.call({
target: swETH,
abi: abi.find((m) => m.name === 'getRate'),
})
).output / 1e18;

const priceKey = `ethereum:${swETH}`;
const ethPrice = (await getPriceApiData(`/prices/current/${priceKey}`)).coins[priceKey].price;

const tvlUsd = totalSupply * rate * ethPrice;

// const apyBase = (await axios.post(apr, {})).data.stakingAprPercent;
const apyBase7d = (await axios.get(apr7d)).data;
const [apyFields, { tvlUsd, rate }] = await Promise.all([
getRepriceApy(swETH, 'newSwETHToETHRate'),
getTvl(swETH),
]);

return [
{
Expand All @@ -40,8 +15,7 @@ const apy = async () => {
chain: 'Ethereum',
symbol: 'swETH',
tvlUsd,
apyBase: apyBase7d,
apyBase7d,
...apyFields,
...(rate > 0 && { pricePerShare: rate }),
underlyingTokens: ['0x0000000000000000000000000000000000000000'],
isIntrinsicSource: true,
Expand Down
Loading