Skip to content

Commit 56afe1c

Browse files
committed
First work to integrate staking (Idle) in own wallet
1 parent 0b09b30 commit 56afe1c

File tree

9 files changed

+159
-24
lines changed

9 files changed

+159
-24
lines changed

web/src/components/SliderPanel.vue

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
>
3636
</td>
3737
<td :title="formatPriceLong(token)" class="text-end">{{ formatPrice(token) }}</td>
38-
<td class="text-end">{{ formatOwned(token) }}</td>
38+
<td class="text-end" :title="tokenTitle(token)">{{ formatOwned(token) }}</td>
3939
<td class="text-end">{{ formatValue(token) }}</td>
4040
<td class="text-end">
4141
<div class="d-flex flex-column align-items-end">
@@ -59,6 +59,7 @@ import { asyncComputed, debouncedWatch } from '@vueuse/core';
5959
import { computed, defineComponent, PropType, reactive, Ref, ref, watch } from 'vue';
6060
import Dropdown from 'primevue/dropdown';
6161
import { numberMixin } from '@/util/numbers';
62+
import { StakedToken } from '@/util/stakedTokens';
6263
6364
function adjustRatios(
6465
before: Record<string, number>,
@@ -219,6 +220,9 @@ export default defineComponent({
219220
formatValue(token: TokenData): string {
220221
return this.formatDollars(token.ownedAmount.toUnsafeFloat() * token.value);
221222
},
223+
tokenTitle(token: TokenData): string {
224+
return 'description' in token ? (token as StakedToken).description : '';
225+
},
222226
},
223227
mixins: [numberMixin],
224228
});

web/src/util/numbers.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ export function reduceDecimals(n: FixedNumber, decimals: number): FixedNumber {
3838
export function fixedToBigNumber(n: FixedNumber, decimals: number): BigNumber {
3939
return utils.parseUnits(n.toString(), decimals);
4040
}
41+
export function bigNumberToFixed(n: BigNumber, decimals: number): FixedNumber {
42+
return FixedNumber.from(utils.formatUnits(n, decimals), decimals);
43+
}
4144

4245
export function formatMaxDigits(n: number, digits = 2): string {
4346
if (n === undefined) {

web/src/util/stakedTokens.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { TokenData } from './tokens';
2+
import { FixedNumber } from 'ethers';
3+
4+
export interface StakedToken extends TokenData {
5+
stakedUnderlyingValue: FixedNumber;
6+
description: string;
7+
}
8+
9+
export function reduceTokens(allTokens: TokenData[]): StakedToken[] {
10+
// Find all tokens with the same symbol
11+
const tokensBySymbol: Record<string, TokenData[]> = {};
12+
for (const token of allTokens) {
13+
const tokenArray = tokensBySymbol[token.symbol];
14+
if (!tokenArray) {
15+
tokensBySymbol[token.symbol] = [token];
16+
} else {
17+
tokenArray.push(token);
18+
}
19+
}
20+
console.log(allTokens, tokensBySymbol);
21+
22+
// Reduce all symbols to one item
23+
const result: StakedToken[] = [];
24+
for (const [symbol, tokenArray] of Object.entries(tokensBySymbol)) {
25+
if (tokenArray.length === 0) {
26+
console.log('Empty array for ' + symbol);
27+
continue;
28+
}
29+
// Find the non-staked token version
30+
const nonStakedIndex = tokenArray.findIndex((token) => !('stakedUnderlyingValue' in token));
31+
if (nonStakedIndex === -1) {
32+
throw new Error(`${symbol} only has staked tokens? ${tokenArray}`);
33+
}
34+
const nonStakedItem: TokenData = tokenArray.splice(nonStakedIndex, 1)[0];
35+
const newToken: StakedToken = {
36+
...nonStakedItem,
37+
stakedUnderlyingValue: FixedNumber.from(0.0),
38+
description: `${nonStakedItem.ownedAmount} ${symbol}`,
39+
};
40+
// Add up the stakedTokens
41+
for (const _stakedToken of tokenArray) {
42+
if (!('stakedUnderlyingValue' in _stakedToken)) {
43+
throw new Error(`${symbol} has multiple unstaked tokens in list?`);
44+
}
45+
const stakedToken = _stakedToken as StakedToken;
46+
newToken.ownedAmount = newToken.ownedAmount.addUnsafe(stakedToken.stakedUnderlyingValue);
47+
newToken.stakedUnderlyingValue = newToken.stakedUnderlyingValue.addUnsafe(
48+
stakedToken.stakedUnderlyingValue
49+
);
50+
newToken.description += '\n' + stakedToken.description;
51+
}
52+
result.push(newToken);
53+
}
54+
return result;
55+
}

web/src/util/tokens.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { BigNumber, Contract, FixedNumber } from 'ethers';
44
export interface TokenData {
55
id: string;
66
name: string;
7-
symbol?: string;
7+
symbol: string;
88
decimals: number;
99
ownedAmount: FixedNumber;
1010
value: number;

web/src/views/EnzymeSliders.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export default defineComponent({
7777
if (tokenList) {
7878
if (fund) {
7979
const assetMap = await trackAssets(fund.id, web3Service.getProvider());
80-
const daiValue = tokenList.find(token => token.symbol == "DAI")?.value ?? 1.0;
80+
const daiValue = tokenList.find((token) => token.symbol == 'DAI')?.value ?? 1.0;
8181
tokens.value = tokenList
8282
.map((token) => {
8383
let value = token.value / daiValue;

web/src/views/FarmingStrategy.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export default defineComponent({
7272
.map((asset) => ({
7373
id: asset.id.toLowerCase(),
7474
name: asset.name,
75+
symbol: asset.symbol,
7576
value: asset.price?.price ?? -1,
7677
ownedAmount: FixedNumber.from('0'),
7778
decimals: asset.decimals,

web/src/views/WalletAccount.vue

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,27 @@ import { calcSliderChangeResult } from '@/util/sliderUtil';
2525
import { fetchTokens } from '@/util/tokenlist';
2626
import { calcPercentageMap, getTokenBalance, TokenData } from '@/util/tokens';
2727
import { getTokenPrices } from '@/web3/uniswapService';
28+
import { idleService } from '@/web3/idleService';
2829
import { web3Service } from '@/web3/web3Service';
2930
import { asyncComputed } from '@vueuse/core';
3031
import { BigNumber, FixedNumber } from 'ethers';
3132
import { defineComponent, ref, Ref, watchEffect } from 'vue';
33+
import { reduceTokens } from '@/util/stakedTokens';
3234
3335
export default defineComponent({
3436
setup() {
3537
const tokenList = asyncComputed(() => fetchTokens(), []);
3638
const tokenData: Ref<TokenData[]> = ref([]);
3739
const distribution: Ref<Record<string, number>> = ref({});
3840
const startingDistribution: Ref<Record<string, number>> = ref({});
41+
const idleTokenList = asyncComputed(() => idleService.getTokenData());
3942
4043
// TODO: native ETH is not shown now (only WETH)
4144
4245
watchEffect(async () => {
4346
const account = web3Service.status().address;
4447
const tokens = tokenList.value;
48+
const idleTokens = idleTokenList.value;
4549
// Look up all balances and create TokenData
4650
const balances = tokens.map((token) => {
4751
if (account) {
@@ -56,27 +60,30 @@ export default defineComponent({
5660
const multiplier = 1.0 / (tokenPrices[usdAddress]?.derivedETH ?? 1.0);
5761
5862
let index = -1;
59-
tokenData.value = (
60-
await Promise.all(
61-
tokens.map(async (tokenInfo) => {
62-
index++;
63-
let value =
64-
parseFloat(tokenPrices[tokenInfo.address]?.derivedETH ?? '0.0001') * multiplier;
65-
if (value == 0.0) {
66-
value = 0.0001 * multiplier;
67-
}
68-
return {
69-
id: tokenInfo.address,
70-
name: tokenInfo.name,
71-
symbol: tokenInfo.symbol,
72-
decimals: tokenInfo.decimals,
73-
ownedAmount: FixedNumber.fromValue(await balances[index], tokenInfo.decimals),
74-
value: value,
75-
logoUri: tokenInfo.logoURI,
76-
} as TokenData;
77-
})
78-
)
79-
).sort(
63+
let basicTokens = await Promise.all(
64+
tokens.map(async (tokenInfo) => {
65+
index++;
66+
let value =
67+
parseFloat(tokenPrices[tokenInfo.address]?.derivedETH ?? '0.0001') * multiplier;
68+
if (value == 0.0) {
69+
value = 0.0001 * multiplier;
70+
}
71+
return {
72+
id: tokenInfo.address,
73+
name: tokenInfo.name,
74+
symbol: tokenInfo.symbol,
75+
decimals: tokenInfo.decimals,
76+
ownedAmount: FixedNumber.fromValue(await balances[index], tokenInfo.decimals),
77+
value: value,
78+
logoUri: tokenInfo.logoURI,
79+
} as TokenData;
80+
})
81+
);
82+
if (basicTokens.length > 0) {
83+
basicTokens.push(...idleTokens);
84+
basicTokens = reduceTokens(basicTokens);
85+
}
86+
tokenData.value = basicTokens.sort(
8087
(a, b) => b.value * b.ownedAmount.toUnsafeFloat() - a.value * a.ownedAmount.toUnsafeFloat()
8188
);
8289
distribution.value = calcPercentageMap(tokenData.value);

web/src/web3/idleService.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { bigNumberToFixed } from '@/util/numbers';
2+
import { StakedToken } from '@/util/stakedTokens';
3+
import { getTokenBalance } from '@/util/tokens';
4+
import { BigNumber, Contract, FixedNumber, utils } from 'ethers';
5+
import idleTokenAbi from './IdleToken.json';
6+
import { web3Service } from './web3Service';
7+
8+
const idleAddresses = {
9+
DAI: '0x3fE7940616e5Bc47b0775a0dccf6237893353bB4',
10+
USDC: '0x5274891bEC421B39D23760c04A6755eCB444797C',
11+
USDT: '0xF34842d05A1c888Ca02769A633DF37177415C2f8',
12+
WBTC: '0x8C81121B15197fA0eEaEE1DC75533419DcfD3151',
13+
WETH: '0xC8E6CA6E96a326dC448307A5fDE90a0b21fd7f80',
14+
};
15+
16+
// Staking through idle.finance
17+
class IdleService {
18+
public async getTokenData(): Promise<StakedToken[]> {
19+
const address = web3Service.status().address;
20+
if (!address) {
21+
return [];
22+
}
23+
24+
// Enable this to test what things look like with 1 WETH staked:
25+
const testContract = ''; // '0xC8E6CA6E96a326dC448307A5fDE90a0b21fd7f80'
26+
27+
const balanceMap = await Promise.all(
28+
Object.values(idleAddresses).map((contractAddress) => {
29+
if (contractAddress == testContract) return Promise.resolve(utils.parseEther('1'));
30+
else return getTokenBalance(contractAddress, address);
31+
})
32+
);
33+
let index = -1;
34+
return Promise.all(
35+
Object.entries(idleAddresses).map(async ([symbol, address]) => {
36+
const decimals = 18; // 4-Jul-2021: the 5 addresses we use all have 18 as decimals.
37+
// For future, better to look it up
38+
index++;
39+
const owned = bigNumberToFixed(balanceMap[index], decimals);
40+
let value = FixedNumber.from(0.0);
41+
if (balanceMap[index].gt(BigNumber.from(0))) {
42+
const contract = new Contract(address, idleTokenAbi, web3Service.getProvider());
43+
const tokenPrice: BigNumber = await contract.tokenPrice();
44+
value = bigNumberToFixed(tokenPrice, decimals);
45+
console.log(owned, value);
46+
}
47+
return {
48+
id: address,
49+
name: symbol,
50+
symbol: symbol,
51+
decimals: decimals,
52+
ownedAmount: owned,
53+
value: value.toUnsafeFloat(),
54+
stakedUnderlyingValue: owned.mulUnsafe(value),
55+
description: `${value} Staked ${symbol} on idle.finance`,
56+
};
57+
})
58+
);
59+
}
60+
}
61+
62+
const idleService = new IdleService();
63+
64+
export { idleService };

web/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"esModuleInterop": true,
1111
"allowSyntheticDefaultImports": true,
1212
"sourceMap": true,
13+
"resolveJsonModule": true,
1314
"baseUrl": ".",
1415
"types": [
1516
"webpack-env"

0 commit comments

Comments
 (0)