forked from saltyfacu/yearn-mini
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.html
132 lines (127 loc) · 13.9 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
<html>
<head>
<title>Yearn-mini</title>
<link rel="stylesheet" href="styles.css"></link>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<script src="./web3.min.js"></script>
<script src="./bignumber.min.js"></script>
</head>
<body>
<script>
const alchemyKey = ''; // Your Alchemy Key
// OR
const infuraProjectId = ''; // Your Infura Project ID - DO NOT USE, WON'T WORK
const alchemyUrl = alchemyKey != ''?('https://eth-mainnet.alchemyapi.io/v2/' + alchemyKey):null;
const infuraUrl = infuraProjectId != ''?('https://mainnet.infura.io/v3/' + infuraProjectId):null;
const web3 = new Web3(alchemyUrl || infuraUrl || Web3.givenProvider);
const registryAdapterAbi = [{"inputs":[{"internalType":"address[]","name":"_assetsAddresses","type":"address[]"}],"name":"assetsStatic","outputs":[{"components":[{"internalType":"address","name":"id","type":"address"},{"internalType":"string","name":"typeId","type":"string"},{"internalType":"address","name":"tokenId","type":"address"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint8","name":"decimals","type":"uint8"}],"internalType":"struct RegisteryAdapterV2Vaults.AssetStatic[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"assetsAddresses","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"assetsTokensAddresses","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"_assetsAddresses","type":"address[]"}],"name":"assetsDynamic","outputs":[{"components":[{"internalType":"address","name":"id","type":"address"},{"internalType":"string","name":"typeId","type":"string"},{"internalType":"address","name":"tokenId","type":"address"},{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"amountUsdc","type":"uint256"}],"internalType":"struct RegisteryAdapterV2Vaults.TokenAmount","name":"underlyingTokenBalance","type":"tuple"},{"components":[{"internalType":"uint256","name":"pricePerShare","type":"uint256"},{"internalType":"bool","name":"migrationAvailable","type":"bool"},{"internalType":"address","name":"latestVaultAddress","type":"address"},{"internalType":"uint256","name":"depositLimit","type":"uint256"},{"internalType":"bool","name":"emergencyShutdown","type":"bool"}],"internalType":"struct RegisteryAdapterV2Vaults.AssetMetadata","name":"metadata","type":"tuple"}],"internalType":"struct RegisteryAdapterV2Vaults.AssetDynamic[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"accountAddress","type":"address"},{"internalType":"address[]","name":"_assetsAddresses","type":"address[]"}],"name":"assetsPositionsOf","outputs":[{"components":[{"internalType":"address","name":"assetId","type":"address"},{"internalType":"address","name":"tokenId","type":"address"},{"internalType":"string","name":"typeId","type":"string"},{"internalType":"uint256","name":"balance","type":"uint256"},{"components":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"amountUsdc","type":"uint256"}],"internalType":"struct RegisteryAdapterV2Vaults.TokenAmount","name":"underlyingTokenBalance","type":"tuple"},{"components":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct RegisteryAdapterV2Vaults.Allowance[]","name":"tokenAllowances","type":"tuple[]"},{"components":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct RegisteryAdapterV2Vaults.Allowance[]","name":"assetAllowances","type":"tuple[]"}],"internalType":"struct RegisteryAdapterV2Vaults.Position[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"}];
const balancesHelperAbi = [{"inputs":[{"internalType":"address","name":"accountAddress","type":"address"},{"internalType":"address[]","name":"tokensAddresses","type":"address[]"}],"name":"tokensBalances","outputs":[{"components":[{"internalType":"address","name":"tokenId","type":"address"},{"internalType":"uint256","name":"priceUsdc","type":"uint256"},{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"balanceUsdc","type":"uint256"}],"internalType":"struct BalancesHelper.TokenBalance[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"tokensAddresses","type":"address[]"}],"name":"tokensMetadata","outputs":[{"components":[{"internalType":"address","name":"id","type":"address"},{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"uint8","name":"decimals","type":"uint8"}],"internalType":"struct TokenMetadata[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"}];
const vaultAbi = [{"constant":true,"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"deposit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"_shares","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}];
const registryAdapterAddress = '0x240315db938d44bb124ae619f5Fd0269A02d1271';
const balancesHelperAddress = '0x855ffe28019106d089bC018Df18838f8d241c402';
let account, registryAdapterContract, balancesHelperContract, assets, assetsStatic, assetsDynamic, assestsPositionsOf, tokensMetadata, tokensBalances, tokens;
const compareValue = ( a, b ) => {
if ( a.valueRaw > b.valueRaw ){
return -1;
}
if ( a.valueRaw < b.valueRaw ){
return 1;
}
return 0;
}
const findAssetByUnderlyingTokenId = tokenId => assets.find(asset => asset.tokenId === tokenId);
const findToken = id => tokens.find(token => token.id === id);
const loadChunkedData = async (contract, addresses, chunkSize, method, args) => {
const chunkArray = (input, size) => input.reduce((arr, item, idx) => idx % size === 0 ? [...arr, [item]]: [...arr.slice(0, -1), [...arr.slice(-1)[0], item]], []);
return (await Promise.all(chunkArray(addresses, chunkSize).map(vaultAddressesChunk => args ? contract.methods[method](args, vaultAddressesChunk).call() : contract.methods[method](vaultAddressesChunk).call()))).reduceRight((previousArray, currentArray) => previousArray.concat(currentArray));
}
const formatCurrency = (val) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 2}).format(val);
const formatTokenAmount = (amount) => new Intl.NumberFormat("en-US", { minimumFractionDigits: 4 }).format(amount);
const loadData = async () => {
assetsAddressesPromise = registryAdapterContract.methods.assetsAddresses().call();
assetsTokensAddressesPromise = registryAdapterContract.methods.assetsTokensAddresses().call();
const [assetsAddresses, assetsTokensAddresses] = await Promise.all([assetsAddressesPromise, assetsTokensAddressesPromise]);
[assetsStatic, assetsDynamic, assestsPositionsOf, tokensBalances, tokensMetadata] = await Promise.all([
loadChunkedData(registryAdapterContract, assetsAddresses, 40, 'assetsStatic'),
loadChunkedData(registryAdapterContract, assetsAddresses, 4, 'assetsDynamic'),
loadChunkedData(registryAdapterContract, assetsAddresses, 3, 'assetsPositionsOf', account),
loadChunkedData(balancesHelperContract, assetsTokensAddresses, 4, 'tokensBalances', account),
balancesHelperContract.methods.tokensMetadata(assetsTokensAddresses).call()
]);
tokens = tokensMetadata.map((tokenMetadata) => Object.assign({}, tokenMetadata, tokensBalances.find(tokenBalance => tokenBalance.tokenId === tokenMetadata.id)));
assets = assetsStatic.map((assetStatic) => Object.assign({}, assetStatic, assetsDynamic.find(assetDynamic => assetDynamic.id === assetStatic.id))).filter(asset => !asset.metadata.migrationAvailable);
const walletEls = tokens.reduce((acc, token) => {
if (token.balanceUsdc > 0) acc += `<tr><td>${token.symbol}</td><td>${formatTokenAmount(token.balance / 10**token.decimals)}</td><td>${formatCurrency(token.balanceUsdc / 10**6)}</td><td><button onClick="deposit('${token.id}')">deposit</button></td></tr>`
return acc;
}, []);
const positionsRows = assestsPositionsOf.reduce((acc, position) => acc += `<tr><td>${findToken(position.tokenId).symbol}</td><td>${formatTokenAmount(position.underlyingTokenBalance.amount / 10**findToken(position.tokenId).decimals)}</td><td>${formatCurrency(position.underlyingTokenBalance.amountUsdc / 10**6)}</td><td><button onClick="withdraw('${position.assetId}')">withdraw</button></td></tr>`, '');
const positionsEls = `<h2>Your Deposits</h2><table><thead><th>Name</th><th>Tokens</th><th>Value</th><th></th></thead><tbody>${positionsRows}</tbody></table>`;
if (positionsRows) document.getElementById('deposits').setAttribute('style', 'display: inherit');
const assetsElsData = assets.reduce((acc, asset) => acc.concat({
name: findToken(asset.tokenId).symbol,
tokens: formatTokenAmount(asset.underlyingTokenBalance.amount / 10**findToken(asset.tokenId).decimals),
value: formatCurrency(asset.underlyingTokenBalance.amountUsdc / 10**6),
valueRaw: asset.underlyingTokenBalance.amountUsdc / 10**6,
}), []);
assetsElsData.sort(compareValue);
const assetsEls = assetsElsData.map((asset) => `<tr><td>${asset.name}</td><td>${asset.tokens}</td><td>${asset.value}</td></tr>`).join('');
document.getElementById('walletRows').innerHTML = walletEls;
document.getElementById('deposits').innerHTML = positionsEls;
document.getElementById('assetsRows').innerHTML = assetsEls;
}
const deposit = async (tokenId) => {
const assetId = findAssetByUnderlyingTokenId(tokenId).id;
const vaultContract = new web3.eth.Contract(vaultAbi, assetId);
const tokenContract = new web3.eth.Contract(vaultAbi, tokenId);
const allowance = await tokenContract.methods.allowance(account, assetId).call();
const token = findToken(tokenId);
if (allowance === "0") {
if(!confirm(`Allow "${findAssetByUnderlyingTokenId(tokenId).symbol}" to spend your "${token.symbol}" token?`)) return;
const maxUint256 = new BigNumber(2).pow(256).minus(1).toFixed(0);
const gasLimit = await vaultContract.methods.approve(assetId, maxUint256).estimateGas({ from: account });
await tokenContract.methods.approve(assetId, maxUint256).send({from: account,gas: gasLimit});
}
const amountStr = prompt(`Enter amount of "${token.symbol}" to deposit or type "all" to deposit all\r\n\r\nWallet (max): ${token.balance / 10**token.decimals}`)
const amount = amountStr === "all" ? new BigNumber(token.balance).toFixed(0) : new BigNumber(amountStr).times(10**token.decimals).toFixed(0);
const gasLimit = await vaultContract.methods.deposit(amount).estimateGas({ from: account });
await vaultContract.methods.deposit(amount).send({from: account,gas: gasLimit});
loadData();
}
const withdraw = async (assetId) => {
const position = assestsPositionsOf.find(assetPosition => assetPosition.assetId === assetId);
const vaultContract = new web3.eth.Contract(vaultAbi, assetId);
const amountStr = prompt(`Enter enter the percentage of your shares you wish to withdraw (0-100)`);
if (!amountStr) return;
const amount = new BigNumber(position.balance).times(amountStr / 100).toFixed(0);
const gasLimit = await vaultContract.methods.withdraw(amount).estimateGas({ from: account });
await vaultContract.methods.withdraw(amount).send({from: account,gas: gasLimit});
loadData();
}
window.onload = async () => {
if (window.ethereum) {
try {
window.ethereum.request({ method: 'eth_requestAccounts' }).then(
function(wallets) {
account = wallets[0];
registryAdapterContract = new web3.eth.Contract(registryAdapterAbi, registryAdapterAddress);
balancesHelperContract = new web3.eth.Contract(balancesHelperAbi, balancesHelperAddress);
loadData();
}
);
} catch (error) {
if (error.code === 4001) {
alert('rejected');// User rejected request
}
}
} else alert('No web3 support :(\r\n\r\nTry running this file using: python -m http.server 8080');
};
</script>
<div class="header">yearn-mini</div>
<h2>Your Wallet</h2>
<div class="wallet"><table><thead><th>Name</th><th>Tokens</th><th>Value</th><th></th></thead><tbody id="walletRows"></tbody></table></div>
<div id="deposits"></div>
<h2>All Vaults</h2>
<table><thead><th>Name</th><th>Tokens</th><th>Value</th><th></th></thead><tbody id="assetsRows"></tbody></table>
</div>
</body>
</html>