Skip to content

Commit 73dceaa

Browse files
authored
Merge pull request #2237 from b-tarczynski/spark-savings
Add Spark savings vaults
2 parents c4548e0 + bcb58b3 commit 73dceaa

File tree

2 files changed

+174
-0
lines changed

2 files changed

+174
-0
lines changed

src/adaptors/spark-savings/abi.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
export const sparkSavingsAbi = {
2+
totalAssets: {
3+
inputs: [],
4+
name: 'totalAssets',
5+
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
6+
stateMutability: 'view',
7+
type: 'function',
8+
},
9+
totalSupply: {
10+
inputs: [],
11+
name: 'totalSupply',
12+
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
13+
stateMutability: 'view',
14+
type: 'function',
15+
},
16+
vsr: {
17+
inputs: [],
18+
name: 'vsr',
19+
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
20+
stateMutability: 'view',
21+
type: 'function',
22+
},
23+
24+
} as const
25+
26+
module.exports = {
27+
sparkSavingsAbi,
28+
};
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
const axios = require('axios')
2+
const sdk = require('@defillama/sdk')
3+
4+
import { Pool } from '../../types/Pool'
5+
import { sparkSavingsAbi } from './abi'
6+
import { BigNumber } from 'bignumber.js'
7+
8+
const sparkBaseUrl = 'https://app.spark.fi/savings'
9+
10+
type Chain = 'ethereum' | 'avax'
11+
interface VaultConfig {
12+
assetSymbol: string
13+
address: string
14+
decimals: number
15+
}
16+
17+
const sparkSavings: Record<Chain, VaultConfig[]> = {
18+
ethereum: [
19+
{
20+
assetSymbol: 'USDC',
21+
address: '0x28B3a8fb53B741A8Fd78c0fb9A6B2393d896a43d',
22+
decimals: 6,
23+
},
24+
{
25+
assetSymbol: 'USDT',
26+
address: '0xe2e7a17dFf93280dec073C995595155283e3C372',
27+
decimals: 6,
28+
},
29+
{
30+
assetSymbol: 'PYUSD',
31+
address: '0x80128DbB9f07b93DDE62A6daeadb69ED14a7D354',
32+
decimals: 6,
33+
},
34+
{
35+
assetSymbol: 'ETH',
36+
address: '0xfE6eb3b609a7C8352A241f7F3A21CEA4e9209B8f',
37+
decimals: 18,
38+
},
39+
],
40+
avax: [
41+
{
42+
assetSymbol: 'USDC',
43+
address: '0x28B3a8fb53B741A8Fd78c0fb9A6B2393d896a43d',
44+
decimals: 6,
45+
},
46+
],
47+
}
48+
49+
async function getPools(): Promise<Pool[]> {
50+
const pools: Pool[] = []
51+
const chains = Object.keys(sparkSavings) as Chain[]
52+
53+
for (const chain of chains) {
54+
const vaults = sparkSavings[chain]
55+
56+
const totalSupplies = toOutput<string>(
57+
await sdk.api.abi.multiCall({
58+
abi: sparkSavingsAbi.totalSupply,
59+
chain,
60+
calls: vaults.map((config) => ({
61+
target: config.address,
62+
})),
63+
}),
64+
)
65+
66+
const rates = toOutput<string>(
67+
await sdk.api.abi.multiCall({
68+
abi: sparkSavingsAbi.vsr,
69+
chain,
70+
calls: vaults.map((config) => ({
71+
target: config.address,
72+
})),
73+
}),
74+
)
75+
76+
const symbols = toOutput<string>(
77+
await sdk.api.abi.multiCall({
78+
abi: 'erc20:symbol',
79+
chain,
80+
calls: vaults.map((config) => ({
81+
target: config.address,
82+
})),
83+
}),
84+
)
85+
86+
const prices = await fetchPrices(chain, vaults)
87+
88+
pools.push(
89+
...vaults.map(
90+
(vaultConfig, i): Pool => ({
91+
pool: `${vaultConfig.address}-${chain}`,
92+
chain,
93+
apyBase: rateToApy(rates[i]),
94+
url: getVaultUrl(chain, symbols[i]),
95+
project: 'spark-savings',
96+
symbol: vaultConfig.assetSymbol,
97+
tvlUsd: new BigNumber(totalSupplies[i])
98+
.div(10 ** vaultConfig.decimals)
99+
.times(prices[`${chain}:${vaultConfig.address.toLowerCase()}`].price)
100+
.toNumber(),
101+
}),
102+
),
103+
)
104+
}
105+
106+
return pools
107+
}
108+
109+
function toOutput<T>(results: { output: { output: T }[] }): T[] {
110+
return results.output.map((result) => result.output)
111+
}
112+
113+
async function fetchPrices(
114+
chain: Chain,
115+
vaultConfigs: readonly VaultConfig[],
116+
): Promise<Record<string, { price: number }>> {
117+
const priceKeys = vaultConfigs.map((config) => `${chain}:${config.address.toLowerCase()}`).join(',')
118+
const response = await axios.get(`https://coins.llama.fi/prices/current/${priceKeys}`)
119+
return response.data.coins
120+
}
121+
122+
function getVaultUrl(chain: Chain, symbol: string): string {
123+
return `${sparkBaseUrl}/${chainToAppChain[chain]}/${symbol.toLowerCase()}`
124+
}
125+
126+
const chainToAppChain: Record<Chain, string> = {
127+
ethereum: 'mainnet',
128+
avax: 'avalanche',
129+
}
130+
131+
const yearInSeconds = 31536000
132+
133+
function rateToApy(rate: string): number {
134+
const normalizedRate = new BigNumber(rate).div(10 ** 27)
135+
return pow(normalizedRate, yearInSeconds).minus(1).times(100).toNumber()
136+
}
137+
138+
// high precision pow function for correct calculations
139+
function pow(a: BigNumber, b: number): BigNumber {
140+
return BigNumber.clone({ POW_PRECISION: 60 }).prototype.pow.apply(a, [b])
141+
}
142+
143+
module.exports = {
144+
timetravel: false,
145+
apy: getPools,
146+
}

0 commit comments

Comments
 (0)