diff --git a/apps/web/src/composables/auth.ts b/apps/web/src/composables/auth.ts index 77577436d..789cb1fe1 100644 --- a/apps/web/src/composables/auth.ts +++ b/apps/web/src/composables/auth.ts @@ -1,5 +1,5 @@ import useEnvironment from '@/composables/environment' -import { LoginCredentials } from '@casimir/types' +import { SignInWithEthereumCredentials } from '@casimir/types' const { domain, origin, usersUrl } = useEnvironment() @@ -45,17 +45,17 @@ export default function useAuth() { * Signs user up if they don't exist, otherwise * logs the user in with an address, message, and signed message * - * @param {LoginCredentials} loginCredentials - The user's address, provider, currency, message, and signed message + * @param {SignInWithEthereumCredentials} signInWithEthereumCredentials - The user's address, provider, currency, message, and signed message * @returns {Promise} - The response from the login request */ - async function signInWithEthereum(loginCredentials: LoginCredentials): Promise { + async function signInWithEthereum(signInWithEthereumCredentials: SignInWithEthereumCredentials): Promise { try { const requestOptions = { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(loginCredentials) + body: JSON.stringify(signInWithEthereumCredentials) } const response = await fetch(`${usersUrl}/auth/login`, requestOptions) const { error, message } = await response.json() diff --git a/apps/web/src/composables/contracts.ts b/apps/web/src/composables/contracts.ts index 446ba12b9..05b083262 100644 --- a/apps/web/src/composables/contracts.ts +++ b/apps/web/src/composables/contracts.ts @@ -22,21 +22,6 @@ interface RegisterOperatorWithCasimirParams { collateral: string } -const currentStaked = ref({ - usd: '$0.00', - eth: '0 ETH' -}) - -const stakingRewards = ref({ - usd: '$0.00', - eth: '0 ETH' -}) - -const totalWalletBalance = ref({ - usd: '$0.00', - eth: '0 ETH' -}) - const { ethereumUrl, managerAddress, registryAddress, ssvNetworkAddress, ssvNetworkViewsAddress, viewsAddress } = useEnvironment() const provider = new ethers.providers.JsonRpcProvider(ethereumUrl) const manager: CasimirManager & ethers.Contract = new ethers.Contract(managerAddress, ICasimirManagerAbi, provider) as CasimirManager @@ -44,8 +29,6 @@ const views: CasimirViews & ethers.Contract = new ethers.Contract(viewsAddress, const registry: CasimirRegistry & ethers.Contract = new ethers.Contract(registryAddress, ICasimirRegistryAbi, provider) as CasimirRegistry const operators = ref([]) -const registeredOperators = ref([]) -const nonregisteredOperators = ref([]) const isMounted = ref(false) const loadingRegisteredOperators = ref(false) @@ -58,10 +41,6 @@ export default function useContracts() { const { getEthersTrezorSigner } = useTrezor() const { user } = useUsers() const { getWalletConnectSignerV2, nonReactiveWalletConnectWeb3Provider } = useWalletConnectV2() - - const stakeDepositedListener = async () => await refreshBreakdown() - const stakeRebalancedListener = async () => await refreshBreakdown() - const withdrawalInitiatedListener = async () => await refreshBreakdown() async function deposit({ amount, walletProvider }: { amount: string, walletProvider: ProviderString }) { try { @@ -102,116 +81,6 @@ export default function useContracts() { } } - async function getAllTimeStakingRewards() : Promise { - try { - /* Get User's Current Stake */ - const addresses = (user.value as UserWithAccountsAndOperators).accounts.map((account: Account) => account.address) as string[] - const currentUserStakePromises = [] as Array> - addresses.forEach(address => currentUserStakePromises.push(manager.getUserStake(address))) - const settledCurrentUserStakePromises = await Promise.allSettled(currentUserStakePromises) as Array> - const currentUserStake = settledCurrentUserStakePromises.filter(result => result.status === 'fulfilled').map(result => result.value) - const currentUserStakeSum = currentUserStake.reduce((acc, curr) => acc.add(curr), ethers.BigNumber.from(0)) - const currentUserStakeETH = parseFloat(ethers.utils.formatEther(currentUserStakeSum)) - - /* Get User's All Time Deposits and Withdrawals */ - const userEventTotalsPromises = [] as Array> - addresses.forEach(address => {userEventTotalsPromises.push(getContractEventsTotalsByAddress(address))}) - const userEventTotals = await Promise.all(userEventTotalsPromises) as Array - const userEventTotalsSum = userEventTotals.reduce((acc, curr) => { - const { StakeDeposited, WithdrawalInitiated } = curr - return { - StakeDeposited: acc.StakeDeposited + (StakeDeposited || 0), - WithdrawalInitiated: acc.WithdrawalInitiated + (WithdrawalInitiated || 0), - } - }, { StakeDeposited: 0, WithdrawalInitiated: 0 } as { StakeDeposited: number; WithdrawalInitiated: number }) - - - const stakedDepositedETH = userEventTotalsSum.StakeDeposited - const withdrawalInitiatedETH = userEventTotalsSum.WithdrawalInitiated - - /* Get User's All Time Rewards by Subtracting (StakeDeposited + WithdrawalInitiated) from CurrentStake */ - const currentUserStakeMinusEvents = currentUserStakeETH - (stakedDepositedETH as number) - (withdrawalInitiatedETH as number) - return { - eth: `${formatNumber(currentUserStakeMinusEvents)} ETH`, - usd: `$${formatNumber(currentUserStakeMinusEvents * (await getCurrentPrice({ coin: 'ETH', currency: 'USD' })))}` - } - } catch (err) { - console.error(`There was an error in getAllTimeStakingRewards: ${err}`) - return { - eth: '0 ETH', - usd: '$ 0.00' - } - } - } - - async function getContractEventsTotalsByAddress(address: string) : Promise { - try { - const eventList = [ - 'StakeDeposited', - 'StakeRebalanced', - 'WithdrawalInitiated' - ] - const eventFilters = eventList.map(event => { - if (event === 'StakeRebalanced') return manager.filters[event]() - return manager.filters[event](address) - }) - - // const items = (await Promise.all(eventFilters.map(async eventFilter => await manager.queryFilter(eventFilter, 0, 'latest')))) - // Use Promise.allSettled to avoid errors when a filter returns no results - const items = (await Promise.allSettled(eventFilters.map(async eventFilter => await manager.queryFilter(eventFilter, 0, 'latest')))).map(result => result.status === 'fulfilled' ? result.value : []) - - const userEventTotals = eventList.reduce((acc, event) => { - acc[event] = 0 - return acc - }, {} as { [key: string]: number }) - - for (const item of items) { - for (const action of item) { - const { args, event } = action - const { amount } = args - const amountInEth = parseFloat(ethers.utils.formatEther(amount)) - userEventTotals[event as string] += amountInEth - } - } - - return userEventTotals - } catch (err) { - console.error(`There was an error in getContractEventsTotalsByAddress: ${err}`) - return { - StakeDeposited: 0, - StakeRebalanced: 0, - WithdrawalInitiated: 0 - } - } - } - - async function getCurrentStaked(): Promise { - const addresses = (user.value as UserWithAccountsAndOperators).accounts.map((account: Account) => account.address) as string[] - try { - const promises = addresses.map((address) => manager.getUserStake(address)) - const settledPromises = await Promise.allSettled(promises) as Array> - const currentStaked = settledPromises - .filter((result) => result.status === 'fulfilled') - .map((result) => result.value) - - const totalStaked = currentStaked.reduce((accumulator, currentValue) => accumulator.add(currentValue), ethers.BigNumber.from(0)) - const totalStakedUSD = parseFloat(ethers.utils.formatEther(totalStaked)) * (await getCurrentPrice({ coin: 'ETH', currency: 'USD' })) - const totalStakedETH = parseFloat(ethers.utils.formatEther(totalStaked)) - const formattedTotalStakedUSD = formatNumber(totalStakedUSD) - const formattedTotalStakedETH = formatNumber(totalStakedETH) - return { - eth: formattedTotalStakedETH + ' ETH', - usd: '$ ' + formattedTotalStakedUSD - } - } catch (error) { - console.log('Error occurred while fetching stake:', error) - return { - eth: '0ETH', - usd: '$0.00' - } - } - } - async function getDepositFees(): Promise { try { const fees = await manager.FEE_PERCENT() @@ -223,82 +92,6 @@ export default function useContracts() { } } - async function _getPools(operatorId: number): Promise { - const pools: Pool[] = [] - - const poolIds = [ - ...await manager.getPendingPoolIds(), - ...await manager.getStakedPoolIds() - ] - - for (const poolId of poolIds) { - const poolDetails = await views.getPoolDetails(poolId) - const pool = { - ...poolDetails, - operatorIds: poolDetails.operatorIds.map(id => id.toNumber()), - reshares: poolDetails.reshares.toNumber() - } - if (pool.operatorIds.includes(operatorId)) { - pools.push(pool) - } - } - return pools - } - - async function getUserOperators(): Promise { - const userAddresses = (user.value as UserWithAccountsAndOperators).accounts.map((account: Account) => account.address) as string[] - - const scanner = new Scanner({ - ethereumUrl, - ssvNetworkAddress, - ssvNetworkViewsAddress - }) - - const ssvOperators: Operator[] = [] - for (const address of userAddresses) { - const userOperators = await scanner.getOperators(address) - ssvOperators.push(...userOperators) - } - - const casimirOperators: RegisteredOperator[] = [] - for (const operator of ssvOperators) { - const { active, collateral, poolCount, resharing } = await registry.getOperator(operator.id) - const registered = active || collateral.gt(0) || poolCount.gt(0) || resharing - if (registered) { - const pools = await _getPools(operator.id) - // TODO: Replace these Public Nodes URLs once we have this working again - const operatorStore = { - '654': 'https://nodes.casimir.co/eth/goerli/dkg/1', - '655': 'https://nodes.casimir.co/eth/goerli/dkg/2', - '656': 'https://nodes.casimir.co/eth/goerli/dkg/3', - '657': 'https://nodes.casimir.co/eth/goerli/dkg/4', - '658': 'https://nodes.casimir.co/eth/goerli/dkg/5', - '659': 'https://nodes.casimir.co/eth/goerli/dkg/6', - '660': 'https://nodes.casimir.co/eth/goerli/dkg/7', - '661': 'https://nodes.casimir.co/eth/goerli/dkg/8' - } - const url = operatorStore[operator.id.toString() as keyof typeof operatorStore] - casimirOperators.push({ - ...operator, - active, - collateral: ethers.utils.formatEther(collateral), - poolCount: poolCount.toNumber(), - url, - resharing, - pools - }) - } - } - - const nonregOperators = ssvOperators.filter((operator: any) => { - const idRegistered = casimirOperators.find((registeredOperator: any) => registeredOperator.id === operator.id) - return !idRegistered - }) - - _setOperators(nonregOperators, 'nonregistered') - _setOperators(casimirOperators, 'registered') - } - // async function _getSSVOperators(): Promise { // const ownerAddresses = (user?.value as UserWithAccountsAndOperators).accounts.map((account: Account) => account.address) as string[] // // const ownerAddressesTest = ['0x9725Dc287005CB8F11CA628Bb769E4A4Fc8f0309'] @@ -328,20 +121,6 @@ export default function useContracts() { // } // } - async function getTotalWalletBalance() : Promise { - const promises = [] as Array> - const addresses = (user.value as UserWithAccountsAndOperators).accounts.map((account: Account) => account.address) as string[] - addresses.forEach((address) => { promises.push(getEthersBalance(address)) }) - const totalWalletBalance = (await Promise.all(promises)).reduce((acc, curr) => acc + curr, 0) - const totalWalletBalanceUSD = totalWalletBalance * (await getCurrentPrice({ coin: 'ETH', currency: 'USD' })) - const formattedTotalWalletBalance = formatNumber(totalWalletBalance) - const formattedTotalWalletBalanceUSD = formatNumber(totalWalletBalanceUSD) - return { - eth: formattedTotalWalletBalance + ' ETH', - usd: '$ ' + formattedTotalWalletBalanceUSD - } - } - async function getUserStake(address: string): Promise { try { const bigNumber = await manager.getUserStake(address) @@ -353,24 +132,9 @@ export default function useContracts() { } } - async function listenForContractEvents() { - try { - console.log('listening for contract events') - manager.on('StakeDeposited', stakeDepositedListener) - manager.on('StakeRebalanced', stakeRebalancedListener) - manager.on('WithdrawalInitiated', withdrawalInitiatedListener) - registry.on('OperatorRegistered', getUserOperators) - registry.on('OperatorDeregistered', getUserOperators) - registry.on('DeregistrationRequested', getUserOperators) - } catch (err) { - console.log(`There was an error in listenForContractEvents: ${err}`) - } - } - onMounted(() => { if (isMounted.value) return isMounted.value = true - listenForContractEvents() }) onUnmounted(() => { @@ -391,31 +155,6 @@ export default function useContracts() { } } - async function refreshBreakdown() { - try { - if (!user.value?.id) { - // Reset currentStaked, totalWalletBalance, and stakingRewards - currentStaked.value = { - eth: '0 ETH', - usd: '$ 0.00' - } - totalWalletBalance.value = { - eth: '0 ETH', - usd: '$ 0.00' - } - stakingRewards.value = { - eth: '0 ETH', - usd: '$ 0.00' - } - } - setBreakdownValue({ name: 'currentStaked', ...await getCurrentStaked() }) - setBreakdownValue({ name: 'totalWalletBalance', ...await getTotalWalletBalance() }) - setBreakdownValue({ name: 'stakingRewardsEarned', ...await getAllTimeStakingRewards() }) - } catch (err) { - console.log(`There was an error in refreshBreakdown: ${err}`) - } - } - async function registerOperatorWithCasimir({ walletProvider, address, operatorId, collateral }: RegisterOperatorWithCasimirParams) { loadingRegisteredOperators.value = true try { @@ -442,46 +181,6 @@ export default function useContracts() { } } - function setBreakdownValue({ name, eth, usd }: { name: BreakdownString, eth: string, usd: string}) { - switch (name) { - case 'currentStaked': - currentStaked.value = { - eth, - usd - } - break - case 'totalWalletBalance': - totalWalletBalance.value = { - eth, - usd - } - break - case 'stakingRewardsEarned': - stakingRewards.value = { - eth, - usd - } - break - } - } - - function _setOperators(operatorsArray: Operator[], type: 'nonregistered' | 'registered') { - switch (type) { - case 'nonregistered': - nonregisteredOperators.value = operatorsArray as Array - break - case 'registered': - registeredOperators.value = operatorsArray as Array - break - } - } - - function stopListeningForContractEvents() { - manager.removeListener('StakeDeposited', stakeDepositedListener) - manager.removeListener('StakeRebalanced', stakeRebalancedListener) - manager.removeListener('WithdrawalInitiated', withdrawalInitiatedListener) - } - async function withdraw({ amount, walletProvider }: { amount: string, walletProvider: ProviderString }) { const signerCreators = { 'Browser': getEthersBrowserSigner, @@ -504,23 +203,15 @@ export default function useContracts() { } return { - currentStaked, loadingRegisteredOperators, manager, + views, + registry, operators, - registeredOperators, - stakingRewards, - totalWalletBalance, - nonregisteredOperators, deposit, - getCurrentStaked, getDepositFees, - getUserOperators, getUserStake, - listenForContractEvents, - refreshBreakdown, registerOperatorWithCasimir, - stopListeningForContractEvents, withdraw } } \ No newline at end of file diff --git a/apps/web/src/composables/ethers.ts b/apps/web/src/composables/ethers.ts index d9b881f1d..b1ba2b07a 100644 --- a/apps/web/src/composables/ethers.ts +++ b/apps/web/src/composables/ethers.ts @@ -3,9 +3,7 @@ import { EthersProvider } from '@casimir/types' import { Account, TransactionRequest, UserWithAccountsAndOperators } from '@casimir/types' import { GasEstimate, LoginCredentials, MessageRequest, ProviderString } from '@casimir/types' import useAuth from '@/composables/auth' -import useContracts from '@/composables/contracts' import useEnvironment from '@/composables/environment' -import useUsers from '@/composables/users' interface ethereumWindow extends Window { ethereum: any; @@ -31,30 +29,6 @@ export default function useEthers() { } } - async function blockListener(blockNumber: number) { - const { manager, refreshBreakdown } = useContracts() - const { user } = useUsers() - - console.log('blockNumber :>> ', blockNumber) - const addresses = (user.value as UserWithAccountsAndOperators).accounts.map((account: Account) => account.address) as string[] - const block = await provider.getBlockWithTransactions(blockNumber) - - const txs = block.transactions.map(async (tx) => { - if (addresses.includes(tx.from.toLowerCase())) { - console.log('tx :>> ', tx) - try { - // const response = manager.interface.parseTransaction({ data: tx.data }) - // console.log('response :>> ', response) - await refreshBreakdown() - } catch (error) { - console.error('Error parsing transaction:', error) - } - } - }) - - await Promise.all(txs) -} - /** * Estimate gas fee using EIP 1559 methodology * @returns string in ETH @@ -175,13 +149,6 @@ export default function useEthers() { return maxAfterFees } - async function listenForTransactions() { - provider.on('block', blockListener as ethers.providers.Listener) - await new Promise(() => { - // Wait indefinitely using a Promise that never resolves - }) - } - async function loginWithEthers(loginCredentials: LoginCredentials): Promise{ const { provider, address, currency } = loginCredentials const browserProvider = getBrowserProvider(provider) @@ -190,7 +157,7 @@ export default function useEthers() { const message = await createSiweMessage(address, 'Sign in with Ethereum to the app.') const signer = web3Provider.getSigner() const signedMessage = await signer.signMessage(message) - await signInWithEthereum({ + await signInWithEthereum({ address, currency, message, @@ -240,10 +207,6 @@ export default function useEthers() { return signature } - function stopListeningForTransactions() { - provider.off('block', blockListener as ethers.providers.Listener) - } - async function switchEthersNetwork (providerString: ProviderString, chainId: string) { const provider = getBrowserProvider(providerString) const currentChainId = await provider.networkVersion @@ -282,12 +245,10 @@ export default function useEthers() { getEthersBrowserSigner, getGasPriceAndLimit, getMaxETHAfterFees, - listenForTransactions, loginWithEthers, requestEthersAccount, sendEthersTransaction, signEthersMessage, - stopListeningForTransactions, switchEthersNetwork } } diff --git a/apps/web/src/composables/format.ts b/apps/web/src/composables/format.ts index 5ac73cd9e..a795479c1 100644 --- a/apps/web/src/composables/format.ts +++ b/apps/web/src/composables/format.ts @@ -23,11 +23,16 @@ export default function useFormat() { const scale = Math.pow(10, tier * 3) const scaled = number / scale return scaled.toFixed(2) + suffix - } + } + + function trimAndLowercaseAddress(address: string) { + return address.trim().toLowerCase() + } return { convertString, formatDecimalString, - formatNumber + formatNumber, + trimAndLowercaseAddress } } \ No newline at end of file diff --git a/apps/web/src/composables/router.ts b/apps/web/src/composables/router.ts index e4f2a3268..5d8d70d39 100644 --- a/apps/web/src/composables/router.ts +++ b/apps/web/src/composables/router.ts @@ -1,5 +1,4 @@ import { createWebHistory, createRouter } from 'vue-router' -import useUsers from '@/composables/users' import Overview from '@/pages/overview/Overview.vue' import Operator from '@/pages/operators/Operator.vue' @@ -31,22 +30,4 @@ const router = createRouter({ routes }) -// TO DO: Add a routing beforeEach that -// dynamically fixes rerouting to auth page - -router.beforeEach(async (to, from, next) => { - const { checkUserSessionExists } = useUsers() - await checkUserSessionExists() - next() - // if (to.fullPath === '/auth' && !loggedIn) { - // next() - // } else if (to.fullPath === '/auth' && loggedIn) { - // next('/') - // } else if (!loggedIn) { - // next('/auth') - // } else { - // next() - // } -}) - export default router \ No newline at end of file diff --git a/apps/web/src/composables/testUser.ts b/apps/web/src/composables/testUser.ts new file mode 100644 index 000000000..7300edaeb --- /dev/null +++ b/apps/web/src/composables/testUser.ts @@ -0,0 +1,544 @@ +import { onMounted, onUnmounted, readonly, ref, watch } from 'vue' +import * as Session from 'supertokens-web-js/recipe/session' +import { ethers } from 'ethers' +import { Account, BreakdownAmount, BreakdownString, ContractEventsByAddress, Currency, LoginCredentials, Pool, ProviderString, RegisteredOperator, User, UserWithAccountsAndOperators } from '@casimir/types' +import useContracts from '@/composables/contracts' +import useEnvironment from '@/composables/environment' +import useEthers from '@/composables/ethers' +import useFormat from '@/composables/format' +import useLedger from '@/composables/ledger' +import usePrice from '@/composables/price' +import useTrezor from '@/composables/trezor' +import useWalletConnect from '@/composables/walletConnectV2' +import { Operator, Scanner } from '@casimir/ssv' + +const { manager, registry, views } = useContracts() +const { ethereumUrl, managerAddress, registryAddress, ssvNetworkAddress, ssvNetworkViewsAddress, usersUrl, viewsAddress } = useEnvironment() +const { ethersProviderList, loginWithEthers } = useEthers() +const { formatNumber } = useFormat() +const { loginWithLedger } = useLedger() +const { loginWithTrezor } = useTrezor() +const { getCurrentPrice } = usePrice() +const { loginWithWalletConnectV2 } = useWalletConnect() + +const initializeComposable = ref(false) +const user = ref(undefined) +const provider = new ethers.providers.JsonRpcProvider(ethereumUrl) +const registeredOperators = ref([]) +const nonregisteredOperators = ref([]) + +const currentStaked = ref({ + usd: '$0.00', + eth: '0 ETH' +}) + +const stakingRewards = ref({ + usd: '$0.00', + eth: '0 ETH' +}) + +const totalWalletBalance = ref({ + usd: '$0.00', + eth: '0 ETH' +}) + +export default function useTestUser() { + async function addAccountToUser({ provider, address, currency }: { provider: string, address: string, currency: string}) { + const userAccountExists = user.value?.accounts?.some((account: Account | any) => account?.address === address && account?.walletProvider === provider && account?.currency === currency) + if (userAccountExists) return 'Account already exists for this user' + const account = { + userId: user?.value?.id, + address: address.toLowerCase() as string, + currency: currency || 'ETH', + ownerAddress: user?.value?.address.toLowerCase() as string, + walletProvider: provider + } + + const requestOptions = { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ account, id: user?.value?.id }) + } + + try { + const response = await fetch(`${usersUrl}/user/add-sub-account`, requestOptions) + const { error, message, data: updatedUser } = await response.json() + if (error) throw new Error(message || 'There was an error adding the account') + user.value = updatedUser + await setUserAccountBalances() + return { error, message, data: updatedUser } + } catch (error: any) { + throw new Error(error.message || 'Error adding account') + } + } + + async function blockListener(blockNumber: number) { + console.log('blockNumber :>> ', blockNumber) + const addresses = (user.value as UserWithAccountsAndOperators).accounts.map((account: Account) => account.address) as string[] + const block = await provider.getBlockWithTransactions(blockNumber) + + const txs = block.transactions.map(async (tx: any) => { + if (addresses.includes(tx.from.toLowerCase())) { + console.log('tx :>> ', tx) + try { + // const response = manager.interface.parseTransaction({ data: tx.data }) + // console.log('response :>> ', response) + await refreshBreakdown() + } catch (error) { + console.error('Error parsing transaction:', error) + } + } + }) + + await Promise.all(txs) + } + + async function getAllTimeStakingRewards() : Promise { + try { + /* Get User's Current Stake */ + const addresses = (user.value as UserWithAccountsAndOperators).accounts.map((account: Account) => account.address) as string[] + const currentUserStakePromises = [] as Array> + addresses.forEach(address => currentUserStakePromises.push(manager.getUserStake(address))) + const settledCurrentUserStakePromises = await Promise.allSettled(currentUserStakePromises) as Array> + const currentUserStake = settledCurrentUserStakePromises.filter(result => result.status === 'fulfilled').map(result => result.value) + const currentUserStakeSum = currentUserStake.reduce((acc, curr) => acc.add(curr), ethers.BigNumber.from(0)) + const currentUserStakeETH = parseFloat(ethers.utils.formatEther(currentUserStakeSum)) + + /* Get User's All Time Deposits and Withdrawals */ + const userEventTotalsPromises = [] as Array> + addresses.forEach(address => {userEventTotalsPromises.push(getContractEventsTotalsByAddress(address))}) + const userEventTotals = await Promise.all(userEventTotalsPromises) as Array + const userEventTotalsSum = userEventTotals.reduce((acc, curr) => { + const { StakeDeposited, WithdrawalInitiated } = curr + return { + StakeDeposited: acc.StakeDeposited + (StakeDeposited || 0), + WithdrawalInitiated: acc.WithdrawalInitiated + (WithdrawalInitiated || 0), + } + }, { StakeDeposited: 0, WithdrawalInitiated: 0 } as { StakeDeposited: number; WithdrawalInitiated: number }) + + + const stakedDepositedETH = userEventTotalsSum.StakeDeposited + const withdrawalInitiatedETH = userEventTotalsSum.WithdrawalInitiated + + /* Get User's All Time Rewards by Subtracting (StakeDeposited + WithdrawalInitiated) from CurrentStake */ + const currentUserStakeMinusEvents = currentUserStakeETH - (stakedDepositedETH as number) - (withdrawalInitiatedETH as number) + return { + eth: `${formatNumber(currentUserStakeMinusEvents)} ETH`, + usd: `$${formatNumber(currentUserStakeMinusEvents * (await getCurrentPrice({ coin: 'ETH', currency: 'USD' })))}` + } + } catch (err) { + console.error(`There was an error in getAllTimeStakingRewards: ${err}`) + return { + eth: '0 ETH', + usd: '$ 0.00' + } + } + } + + async function getCurrentStaked(): Promise { + const addresses = (user.value as UserWithAccountsAndOperators).accounts.map((account: Account) => account.address) as string[] + try { + const promises = addresses.map((address) => manager.getUserStake(address)) + const settledPromises = await Promise.allSettled(promises) as Array> + const currentStaked = settledPromises + .filter((result) => result.status === 'fulfilled') + .map((result) => result.value) + + const totalStaked = currentStaked.reduce((accumulator, currentValue) => accumulator.add(currentValue), ethers.BigNumber.from(0)) + const totalStakedUSD = parseFloat(ethers.utils.formatEther(totalStaked)) * (await getCurrentPrice({ coin: 'ETH', currency: 'USD' })) + const totalStakedETH = parseFloat(ethers.utils.formatEther(totalStaked)) + const formattedTotalStakedUSD = formatNumber(totalStakedUSD) + const formattedTotalStakedETH = formatNumber(totalStakedETH) + return { + eth: formattedTotalStakedETH + ' ETH', + usd: '$ ' + formattedTotalStakedUSD + } + } catch (error) { + console.log('Error occurred while fetching stake:', error) + return { + eth: '0ETH', + usd: '$0.00' + } + } + } + + async function getContractEventsTotalsByAddress(address: string) : Promise { + try { + const eventList = [ + 'StakeDeposited', + 'StakeRebalanced', + 'WithdrawalInitiated' + ] + const eventFilters = eventList.map(event => { + if (event === 'StakeRebalanced') return manager.filters[event]() + return manager.filters[event](address) + }) + + // const items = (await Promise.all(eventFilters.map(async eventFilter => await manager.queryFilter(eventFilter, 0, 'latest')))) + // Use Promise.allSettled to avoid errors when a filter returns no results + const items = (await Promise.allSettled(eventFilters.map(async eventFilter => await manager.queryFilter(eventFilter, 0, 'latest')))).map(result => result.status === 'fulfilled' ? result.value : []) + + const userEventTotals = eventList.reduce((acc, event) => { + acc[event] = 0 + return acc + }, {} as { [key: string]: number }) + + for (const item of items) { + for (const action of item) { + const { args, event } = action + const { amount } = args + const amountInEth = parseFloat(ethers.utils.formatEther(amount)) + userEventTotals[event as string] += amountInEth + } + } + + return userEventTotals + } catch (err) { + console.error(`There was an error in getContractEventsTotalsByAddress: ${err}`) + return { + StakeDeposited: 0, + StakeRebalanced: 0, + WithdrawalInitiated: 0 + } + } + } + + async function getEthersBalance(address: string) : Promise { + const balance = await provider.getBalance(address) + return parseFloat(ethers.utils.formatEther(balance)) + } + + async function getTotalWalletBalance() : Promise { + const promises = [] as Array> + const addresses = (user.value as UserWithAccountsAndOperators).accounts.map((account: Account) => account.address) as string[] + addresses.forEach((address) => { promises.push(getEthersBalance(address)) }) + const totalWalletBalance = (await Promise.all(promises)).reduce((acc, curr) => acc + curr, 0) + const totalWalletBalanceUSD = totalWalletBalance * (await getCurrentPrice({ coin: 'ETH', currency: 'USD' })) + const formattedTotalWalletBalance = formatNumber(totalWalletBalance) + const formattedTotalWalletBalanceUSD = formatNumber(totalWalletBalanceUSD) + return { + eth: formattedTotalWalletBalance + ' ETH', + usd: '$ ' + formattedTotalWalletBalanceUSD + } + } + + async function getUser() { + try { + const requestOptions = { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + } + const response = await fetch(`${usersUrl}/user`, requestOptions) + const { user: retrievedUser, error } = await response.json() + if (error) throw new Error(error) + user.value = retrievedUser + } catch (error: any) { + throw new Error('Error getting user from API route') + } + } + + async function getUserOperators(): Promise { + const userAddresses = (user.value as UserWithAccountsAndOperators).accounts.map((account: Account) => account.address) as string[] + + const scanner = new Scanner({ + ethereumUrl, + ssvNetworkAddress, + ssvNetworkViewsAddress + }) + + const ssvOperators: Operator[] = [] + for (const address of userAddresses) { + const userOperators = await scanner.getOperators(address) + ssvOperators.push(...userOperators) + } + + const casimirOperators: RegisteredOperator[] = [] + for (const operator of ssvOperators) { + const { active, collateral, poolCount, resharing } = await registry.getOperator(operator.id) + const registered = active || collateral.gt(0) || poolCount.gt(0) || resharing + if (registered) { + const pools = await _getPools(operator.id) + // TODO: Replace these Public Nodes URLs once we have this working again + const operatorStore = { + '654': 'https://nodes.casimir.co/eth/goerli/dkg/1', + '655': 'https://nodes.casimir.co/eth/goerli/dkg/2', + '656': 'https://nodes.casimir.co/eth/goerli/dkg/3', + '657': 'https://nodes.casimir.co/eth/goerli/dkg/4', + '658': 'https://nodes.casimir.co/eth/goerli/dkg/5', + '659': 'https://nodes.casimir.co/eth/goerli/dkg/6', + '660': 'https://nodes.casimir.co/eth/goerli/dkg/7', + '661': 'https://nodes.casimir.co/eth/goerli/dkg/8' + } + const url = operatorStore[operator.id.toString() as keyof typeof operatorStore] + casimirOperators.push({ + ...operator, + active, + collateral: ethers.utils.formatEther(collateral), + poolCount: poolCount.toNumber(), + url, + resharing, + pools + }) + } + } + + const nonregOperators = ssvOperators.filter((operator: any) => { + const idRegistered = casimirOperators.find((registeredOperator: any) => registeredOperator.id === operator.id) + return !idRegistered + }) + + nonregisteredOperators.value = nonregOperators as Array + registeredOperators.value = casimirOperators as Array + } + + async function initializeUser() { + // Update user accounts, operators, analytics + listenForContractEvents() + listenForTransactions() + await refreshBreakdown() + } + + function listenForContractEvents() { + try { + console.log('listening for contract events') + manager.on('StakeDeposited', stakeDepositedListener) + manager.on('StakeRebalanced', stakeRebalancedListener) + manager.on('WithdrawalInitiated', withdrawalInitiatedListener) + registry.on('OperatorRegistered', getUserOperators) + registry.on('OperatorDeregistered', getUserOperators) + registry.on('DeregistrationRequested', getUserOperators) + } catch (err) { + console.log(`There was an error in listenForContractEvents: ${err}`) + } + } + + async function listenForTransactions() { + provider.on('block', blockListener as ethers.providers.Listener) + await new Promise(() => { + // Wait indefinitely using a Promise that never resolves + }) + } + + /** + * Uses appropriate provider composable to login or sign up + * @param provider + * @param address + * @param currency + * @returns + */ + async function login(loginCredentials: LoginCredentials, pathIndex?: number) { + const { provider, address, currency } = loginCredentials + try { + if (ethersProviderList.includes(provider)) { + return await loginWithEthers(loginCredentials) + } else if (provider === 'Ledger') { + return await loginWithLedger(loginCredentials, JSON.stringify(pathIndex)) + } else if (provider === 'Trezor') { + return await loginWithTrezor(loginCredentials, JSON.stringify(pathIndex)) + } else if (provider === 'WalletConnect'){ + return await loginWithWalletConnectV2(loginCredentials) + } else { + // TODO: Implement this for other providers + console.log('Sign up not yet supported for this wallet provider') + } + await getUser() + + } catch (error: any) { + throw new Error(error.message || 'There was an error logging in') + } + } + + async function logout() { + await Session.signOut() + uninitializeUser() + // await disconnectWalletConnect() + // TODO: Fix bug that doesn't allow you to log in without refreshing page after a user logs out + window.location.reload() + console.log('user.value :>> ', user.value) + } + + onMounted(async () => { + if (!initializeComposable.value) { + const session = await Session.doesSessionExist() + if (session) await getUser() + + watch(user, async () => { + if (user.value) { + // Update user accounts, operators, analytics + console.log('User Updated', user.value) + await initializeUser() + } else { + uninitializeUser() + } + }) + initializeComposable.value = true + } + }) + + onUnmounted(() =>{ + initializeComposable.value = false + }) + + function stopListeningForContractEvents() { + manager.removeListener('StakeDeposited', stakeDepositedListener) + manager.removeListener('StakeRebalanced', stakeRebalancedListener) + manager.removeListener('WithdrawalInitiated', withdrawalInitiatedListener) + } + + async function refreshBreakdown() { + try { + setBreakdownValue({ name: 'currentStaked', ...await getCurrentStaked() }) + setBreakdownValue({ name: 'totalWalletBalance', ...await getTotalWalletBalance() }) + setBreakdownValue({ name: 'stakingRewardsEarned', ...await getAllTimeStakingRewards() }) + } catch (err) { + console.log(`There was an error in refreshBreakdown: ${err}`) + } + } + + // TODO: Re-enable once we have a way to remove accounts in UI + // async function removeConnectedAccount() { + // if (!user?.value?.address) { + // alert('Please login first') + // } + // if (selectedAddress.value === primaryAddress.value) { + // return alert('Cannot remove primary account') + // } else if (ethersProviderList.includes(selectedProvider.value)) { + // const opts = { + // address: selectedAddress.value, + // currency: selectedCurrency.value, + // ownerAddress: primaryAddress.value, + // walletProvider: selectedProvider.value + // } + // const removeAccountResult = await removeAccount(opts) + // if (!removeAccountResult.error) { + // setSelectedAddress(removeAccountResult.data.address) + // removeAccountResult.data.accounts.forEach((account: Account) => { + // if (account.address === selectedAddress.value) { + // setSelectedProvider(account.walletProvider as ProviderString) + // setSelectedCurrency(account.currency as Currency) + // } + // }) + // } + // } + // } + + function setBreakdownValue({ name, eth, usd }: { name: BreakdownString, eth: string, usd: string}) { + switch (name) { + case 'currentStaked': + currentStaked.value = { + eth, + usd + } + break + case 'totalWalletBalance': + totalWalletBalance.value = { + eth, + usd + } + break + case 'stakingRewardsEarned': + stakingRewards.value = { + eth, + usd + } + break + } + } + + async function setUserAccountBalances() { + try { + if (user?.value?.accounts) { + const { accounts } = user.value + const accountsWithBalances = await Promise.all(accounts.map(async (account: Account) => { + const { address } = account + const balance = await getEthersBalance(address) + return { + ...account, + balance + } + })) + user.value.accounts = accountsWithBalances + } + } catch (error) { + throw new Error('Error setting user account balances') + } + } + + function stopListeningForTransactions() { + provider.off('block', blockListener as ethers.providers.Listener) + } + + function uninitializeUser() { + // Update user accounts, operators, analytics + user.value = undefined + // Reset currentStaked, totalWalletBalance, and stakingRewards + currentStaked.value = { + eth: '0 ETH', + usd: '$ 0.00' + } + totalWalletBalance.value = { + eth: '0 ETH', + usd: '$ 0.00' + } + stakingRewards.value = { + eth: '0 ETH', + usd: '$ 0.00' + } + stopListeningForContractEvents() + stopListeningForTransactions() + } + + async function updatePrimaryAddress(updatedAddress: string) { + const userId = user?.value?.id + const requestOptions = { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ userId, updatedAddress }) + } + return await fetch(`${usersUrl}/user/update-primary-account`, requestOptions) + } + + const stakeDepositedListener = async () => await refreshBreakdown() + const stakeRebalancedListener = async () => await refreshBreakdown() + const withdrawalInitiatedListener = async () => await refreshBreakdown() + + return { + currentStaked: readonly(currentStaked), + stakingRewards: readonly(stakingRewards), + totalWalletBalance: readonly(totalWalletBalance), + user: readonly(user), + nonregisteredOperators: readonly(nonregisteredOperators), + registeredOperators: readonly(registeredOperators), + addAccountToUser, + login, + logout + } +} + +async function _getPools(operatorId: number): Promise { + const pools: Pool[] = [] + + const poolIds = [ + ...await manager.getPendingPoolIds(), + ...await manager.getStakedPoolIds() + ] + + for (const poolId of poolIds) { + const poolDetails = await views.getPoolDetails(poolId) + const pool = { + ...poolDetails, + operatorIds: poolDetails.operatorIds.map(id => id.toNumber()), + reshares: poolDetails.reshares.toNumber() + } + if (pool.operatorIds.includes(operatorId)) { + pools.push(pool) + } + } + return pools +} \ No newline at end of file diff --git a/apps/web/src/composables/wallet.ts b/apps/web/src/composables/wallet.ts index d05fef72a..9b3fe8370 100644 --- a/apps/web/src/composables/wallet.ts +++ b/apps/web/src/composables/wallet.ts @@ -1,13 +1,9 @@ import { ref } from 'vue' import useEthers from '@/composables/ethers' -import useContracts from '@/composables/contracts' import useLedger from '@/composables/ledger' // import useSolana from '@/composables/solana' import useTrezor from '@/composables/trezor' -import useUsers from '@/composables/users' -import useWalletConnectV2 from './walletConnectV2' -import { Account, CryptoAddress, Currency, LoginCredentials, MessageRequest, ProviderString, TransactionRequest } from '@casimir/types' -import * as Session from 'supertokens-web-js/recipe/session' +import { CryptoAddress, Currency, MessageRequest, ProviderString, TransactionRequest } from '@casimir/types' // Test ethereum send from address : 0xd557a5745d4560B24D36A68b52351ffF9c86A212 // Test ethereum send to address : 0xD4e5faa8aD7d499Aa03BDDE2a3116E66bc8F8203 @@ -15,35 +11,22 @@ import * as Session from 'supertokens-web-js/recipe/session' // Test iotex send to address: acc://06da5e904240736b1e21ca6dbbd5f619860803af04ff3d54/acme // Test bitcoin send to address : 2N3Petr4LMH9tRneZCYME9mu33gR5hExvds -const activeWallets = ref([ - 'MetaMask', - 'CoinbaseWallet', - 'WalletConnect', - 'Trezor', - 'Ledger', - 'TrustWallet', - // 'IoPay', -] as ProviderString[]) + const amount = ref('1') const amountToStake = ref('1.2') const walletProviderAddresses = ref([]) const loadingUserWallets = ref(false) -const openWalletsModal = ref(false) const primaryAddress = ref('') const selectedProvider = ref('') const selectedAddress = ref('') -const selectedPathIndex = ref('') const selectedCurrency = ref('') const toAddress = ref('0x728474D29c2F81eb17a669a7582A2C17f1042b57') export default function useWallet() { - const { refreshBreakdown, stopListeningForContractEvents } = useContracts() - const { estimateEIP1559GasFee, ethersProviderList, getEthersAddressWithBalance, getEthersBalance, sendEthersTransaction, signEthersMessage, listenForTransactions, loginWithEthers, getEthersBrowserProviderSelectedCurrency, stopListeningForTransactions, switchEthersNetwork } = useEthers() - const { getLedgerAddress, loginWithLedger, sendLedgerTransaction, signLedgerMessage } = useLedger() + const { ethersProviderList, sendEthersTransaction, signEthersMessage, getEthersBrowserProviderSelectedCurrency, switchEthersNetwork } = useEthers() + const { getLedgerAddress, sendLedgerTransaction, signLedgerMessage } = useLedger() // const { solanaProviderList, sendSolanaTransaction, signSolanaMessage } = useSolana() - const { getTrezorAddress, loginWithTrezor, sendTrezorTransaction, signTrezorMessage } = useTrezor() - const { addAccount, getUser, checkIfSecondaryAddress, checkIfPrimaryUserExists, removeAccount, setUser, setUserAnalytics, setUserAccountBalances, updatePrimaryAddress, user } = useUsers() - const { /* disconnectWalletConnect, getWalletConnectAddressAndBalance, */ walletConnectAddresses, connectWalletConnectV2, loginWithWalletConnectV2 } = useWalletConnectV2() + const { getTrezorAddress, sendTrezorTransaction, signTrezorMessage } = useTrezor() function getColdStorageAddress(provider: ProviderString, currency: Currency = 'ETH') { if (provider === 'Ledger') { @@ -61,57 +44,6 @@ export default function useWallet() { } } - /** - * Runs the login method for the selected provider - * Checks if the user is logged in, if not, it will sign up or login - * @param provider - * @param currency - * @returns - */ - async function connectWallet(): Promise { - try { // Sign Up or Login - if (!user?.value?.address) { - await login() - const { error, message, data: retrievedUser} = await getUser() - if (error) throw new Error(message || 'There was an error getting the user') - setUser(retrievedUser) - setPrimaryAddress(user?.value?.address as string) - listenForTransactions() - loadingUserWallets.value = false - } else { // Add account if it doesn't already exist - const userAccountExists = user.value?.accounts?.some((account: Account | any) => account?.address === selectedAddress.value && account?.walletProvider === selectedProvider.value && account?.currency === selectedCurrency.value) - if (userAccountExists) { - throw new Error('Account already exists on user.') - } else { - console.log('adding sub account') - const account = { - userId: user?.value?.id, - address: selectedAddress.value.toLowerCase() as string, - currency: selectedCurrency.value || 'ETH', - ownerAddress: user?.value?.address.toLowerCase() as string, - walletProvider: selectedProvider.value - } - - const { error: addAccountError, message: addAccountMessage } = await addAccount(account) - if (addAccountError) throw new Error(addAccountMessage || 'There was an error adding the account') - - const { error: getUserError, message: getUserMessage, data: getUserData } = await getUser() - if (getUserError) throw new Error(getUserMessage || 'There was an error getting the user') - - setUser(getUserData) - setPrimaryAddress(user?.value?.address as string) - } - } - // TODO: Implement setting user table analytics here - await setUserAccountBalances() - console.log('user.value after connecting wallet :>> ', user.value) - await refreshBreakdown() - } catch (error: any) { - loadingUserWallets.value = false - throw new Error(error.message || 'There was an error connecting the wallet') - } - } - /** * Detects the currency of the connected wallet provider and account * @param provider @@ -129,255 +61,6 @@ export default function useWallet() { } } - // Do we need balance of active address only? - // Or do we need balance of all addresses in accounts associated with user? - // Is this calculated on front end or back end or both? - async function getUserBalance() { - if (ethersProviderList.includes(selectedProvider.value)){ - const walletBalance = await getEthersBalance(selectedAddress.value) - console.log('walletBalance in wei in wallet.ts :>> ', walletBalance) - return walletBalance - } else { - alert('Please select account') - } - } - - /** - * Uses appropriate provider composable to login or sign up - * @param provider - * @param address - * @param currency - * @returns - */ - async function login() { - try { - const loginCredentials = { - provider: selectedProvider.value, - address: selectedAddress.value, - currency: selectedCurrency.value || 'ETH' - } as LoginCredentials - if (ethersProviderList.includes(selectedProvider.value)) { - return await loginWithEthers(loginCredentials) - } else if (selectedProvider.value === 'Ledger') { - return await loginWithLedger(loginCredentials, selectedPathIndex.value) - } else if (selectedProvider.value === 'Trezor') { - return await loginWithTrezor(loginCredentials, selectedPathIndex.value) - } else if (selectedProvider.value === 'WalletConnect'){ - return await loginWithWalletConnectV2(loginCredentials) - } else { - // TODO: Implement this for other providers - console.log('Sign up not yet supported for this wallet provider') - } - } catch (error: any) { - throw new Error(error.message || 'There was an error logging in') - } - } - - async function logout() { - loadingUserWallets.value = true - await Session.signOut() - stopListeningForContractEvents() - stopListeningForTransactions() - setUser(undefined) - setSelectedAddress('') - setSelectedProvider('') - setSelectedCurrency('') - setPrimaryAddress('') - setUserAnalytics() - // await disconnectWalletConnect() - await refreshBreakdown() - // TODO: Fix bug that doesn't allow you to log in without refreshing page after a user logs out - window.location.reload() - console.log('user.value :>> ', user.value) - } - - async function removeConnectedAccount() { - if (!user?.value?.address) { - alert('Please login first') - } - if (selectedAddress.value === primaryAddress.value) { - return alert('Cannot remove primary account') - } else if (ethersProviderList.includes(selectedProvider.value)) { - const opts = { - address: selectedAddress.value, - currency: selectedCurrency.value, - ownerAddress: primaryAddress.value, - walletProvider: selectedProvider.value - } - const removeAccountResult = await removeAccount(opts) - if (!removeAccountResult.error) { - setSelectedAddress(removeAccountResult.data.address) - removeAccountResult.data.accounts.forEach((account: Account) => { - if (account.address === selectedAddress.value) { - setSelectedProvider(account.walletProvider as ProviderString) - setSelectedCurrency(account.currency as Currency) - } - }) - } - } - } - - async function sendTransaction() { - const txRequest: TransactionRequest = { - from: selectedAddress.value, - to: toAddress.value, - value: amount.value, - providerString: selectedProvider.value, - currency: selectedCurrency.value || '' - } - - try { - if (txRequest.providerString === 'WalletConnect') { - // await sendWalletConnectTransaction(txRequest) - } else if (ethersProviderList.includes(txRequest.providerString)) { - await sendEthersTransaction(txRequest) - }/* else if (solanaProviderList.includes(txRequest.providerString)) { - await sendSolanaTransaction(txRequest) - }*/ else if (selectedProvider.value === 'IoPay') { - // await sendIoPayTransaction(txRequest) - } else if (selectedProvider.value === 'Ledger') { - await sendLedgerTransaction(txRequest) - } else if (selectedProvider.value === 'Trezor') { - await sendTrezorTransaction(txRequest) - } else { - throw new Error('Provider selected not yet supported') - } - } catch (error) { - console.error('sendTransaction error: ', error) - } - } - - /** - * Sets the selected address and returns the address - * @param provider - * @param currency - */ - async function selectAddress(address: any, pathIndex?: string) : Promise { - walletProviderAddresses.value = [] - loadingUserWallets.value = true - try { - address = trimAndLowercaseAddress(address) - setSelectedAddress(address) - setSelectedCurrency('ETH') // TODO: Implement this for other currencies when supported. - - if (pathIndex) setSelectedPathIndex(pathIndex) - const { data: { sameAddress, sameProvider } } = await checkIfPrimaryUserExists(selectedProvider.value, selectedAddress.value) - if (sameAddress && sameProvider ) { - await connectWallet() // login - return true - } else if (sameAddress && !sameProvider) { - // TODO: Handle this on front-end: do you want to change your primary provider? - throw new Error('Address already exists as a primary address using another provider') - } - - const { data: accountsIfSecondaryAddress } = await checkIfSecondaryAddress(selectedAddress.value) - if (accountsIfSecondaryAddress.length) throw new Error(`${selectedAddress.value} already exists as a secondary address on this/these account(s): ${JSON.stringify(accountsIfSecondaryAddress)}`) - await connectWallet() // sign up or add account - loadingUserWallets.value = false - return true - } catch (error: any) { - loadingUserWallets.value = false - return false - } - } - - /** - * Sets the selected provider and returns the set of addresses available for the selected provider - * @param provider - * @param currency - */ - async function selectProvider(provider: ProviderString, currency: Currency = 'ETH'): Promise { - console.clear() - try { - if (provider === 'WalletConnect') { - console.log('selected WalletConnect!!') - setSelectedProvider(provider) - await connectWalletConnectV2('eip155:5') - setWalletProviderAddresses(walletConnectAddresses.value as CryptoAddress[]) - } else if (ethersProviderList.includes(provider)) { - setSelectedProvider(provider) - const ethersAddresses = await getEthersAddressWithBalance(provider) as CryptoAddress[] - setWalletProviderAddresses(ethersAddresses) - } else if (provider === 'Ledger') { - setSelectedProvider(provider) - const ledgerAddresses = await getLedgerAddress[currency]() as CryptoAddress[] - setWalletProviderAddresses(ledgerAddresses) - } else if (provider === 'Trezor') { - setSelectedProvider(provider) - const trezorAddresses = await getTrezorAddress[currency]() as CryptoAddress[] - setWalletProviderAddresses(trezorAddresses) - } - } catch (error: any) { - throw new Error(`Error selecting provider: ${error.message}`) - } - } - - // TODO: Implement this for other providers - async function setPrimaryWalletAccount() { - if (!user?.value?.address) { - alert('Please login first') - } else if (ethersProviderList.includes(selectedProvider.value)) { - const result = await updatePrimaryAddress(selectedAddress.value) - const { data } = await result.json() - if (data) { - setPrimaryAddress(data.address) - } - } else { - return alert('Not yet implemented for this wallet provider') - } - } - - function setPrimaryAddress(address: string) { - primaryAddress.value = address - } - - function setSelectedAddress (address: string) { - selectedAddress.value = address - } - - function setSelectedCurrency (currency: Currency) { - selectedCurrency.value = currency - } - - function setSelectedPathIndex (pathIndex: string) { - selectedPathIndex.value = pathIndex - } - - function setSelectedProvider (provider: ProviderString) { - selectedProvider.value = provider - } - - function setWalletProviderAddresses(addresses: CryptoAddress[]) { - walletProviderAddresses.value = addresses - } - - async function signMessage(message: string) { - const messageRequest: MessageRequest = { - message, - providerString: selectedProvider.value, - currency: selectedCurrency.value || '' - } - try { - if (messageRequest.providerString === 'WalletConnect') { - // await signWalletConnectMessage(messageRequest) - } else if (ethersProviderList.includes(messageRequest.providerString)) { - await signEthersMessage(messageRequest) - }/* else if (solanaProviderList.includes(messageRequest.providerString)) { - await signSolanaMessage(messageRequest) - }*/ else if (messageRequest.providerString === 'IoPay') { - // await signIoPayMessage(messageRequest) - } else if (messageRequest.providerString === 'Ledger') { - await signLedgerMessage(messageRequest) - } else if (messageRequest.providerString === 'Trezor') { - await signTrezorMessage(messageRequest) - } else { - console.log('signMessage not yet supported for this wallet provider') - } - } catch (error) { - console.error(error) - } - } - async function switchNetwork(chainId: string): Promise { try { if (selectedProvider.value === 'MetaMask') { @@ -393,30 +76,15 @@ export default function useWallet() { } } - function trimAndLowercaseAddress(address: string) : string { - return address.trim().toLowerCase() - } - return { - activeWallets, amount, amountToStake, - connectWallet, detectCurrencyInProvider, - getUserBalance, loadingUserWallets, - logout, - openWalletsModal, primaryAddress, - removeConnectedAccount, - selectAddress, - selectProvider, selectedAddress, selectedCurrency, selectedProvider, - sendTransaction, - setPrimaryWalletAccount, - signMessage, switchNetwork, toAddress, walletProviderAddresses diff --git a/apps/web/src/composables/walletConnectV2.ts b/apps/web/src/composables/walletConnectV2.ts index bb36c06b3..28be207e5 100644 --- a/apps/web/src/composables/walletConnectV2.ts +++ b/apps/web/src/composables/walletConnectV2.ts @@ -92,6 +92,7 @@ export default function useWalletConnectV2() { chain.value = caipChainId web3Modal.value?.closeModal() + return walletConnectAddresses.value } catch (e) { console.error('Failed to connect to WalletConnect.') console.error(e) diff --git a/apps/web/src/layouts/default-layout.vue b/apps/web/src/layouts/default-layout.vue index 3f0db2b55..009dcd0fe 100644 --- a/apps/web/src/layouts/default-layout.vue +++ b/apps/web/src/layouts/default-layout.vue @@ -6,31 +6,87 @@ import router from '@/composables/router' import VueFeather from 'vue-feather' import useFormat from '@/composables/format' import useScreenDimensions from '@/composables/screenDimensions' -import useUser from '@/composables/users' -import useWallet from '@/composables/wallet' +import useTestUser from '@/composables/testUser' +import { CryptoAddress, Currency, ProviderString } from '@casimir/types' +import useEthers from '@/composables/ethers' +import useLedger from '@/composables/ledger' +import useTrezor from '@/composables/trezor' +import useWalletConnect from '@/composables/walletConnectV2' -const authFlowCardNumber = ref(1) -const selectedProvider = ref(null as null | string) -const termsCheckbox = ref(true) +const { ethersProviderList, getEthersAddressWithBalance } = useEthers() +const { convertString, trimAndLowercaseAddress } = useFormat() +const { getLedgerAddress } = useLedger() +const { getTrezorAddress } = useTrezor() +const { screenWidth } = useScreenDimensions() +const { connectWalletConnectV2 } = useWalletConnect() +const activeWallets = [ + 'MetaMask', + 'CoinbaseWallet', + 'WalletConnect', + 'Trezor', + 'Ledger', + 'TrustWallet', + // 'IoPay', +] as ProviderString[] +const authFlowCardNumber = ref(1) +const selectedProvider = ref(null as ProviderString | null) const openRouterMenu = ref(false) - -const { convertString } = useFormat() +const openWalletsModal = ref(false) +const walletProviderAddresses = ref([] as CryptoAddress[]) const { - screenWidth -} = useScreenDimensions() - -const { - activeWallets, - openWalletsModal, - walletProviderAddresses, + user, + addAccountToUser, + login, logout, - selectAddress, - selectProvider, -} = useWallet() +} = useTestUser() -const { user } = useUser() +function checkIfAddressIsUsed (account: CryptoAddress): boolean { + const { address } = account + if (user.value?.accounts) { + const accountAddresses = user.value.accounts.map((account: any) => account.address) + if (accountAddresses.includes(address)) return true + } + return false +} + + /** + * Checks if user is adding an account or logging in + * @param address + */ + async function selectAddress(address: string) { + address = trimAndLowercaseAddress(address) + if (user.value) { + // Add account + await addAccountToUser({provider: selectedProvider.value as ProviderString, address, currency: 'ETH'}) + } else { + // Login + await login({provider: selectedProvider.value as ProviderString, address, currency: 'ETH'}) + } + } + +/** + * Sets the selected provider and returns the set of addresses available for the selected provider + * @param provider + * @param currency +*/ +async function selectProvider(provider: ProviderString, currency: Currency = 'ETH'): Promise { + console.clear() + try { + if (provider === 'WalletConnect') { + walletProviderAddresses.value = await connectWalletConnectV2('eip155:5') as CryptoAddress[] + } else if (ethersProviderList.includes(provider)) { + walletProviderAddresses.value = await getEthersAddressWithBalance(provider) as CryptoAddress[] + } else if (provider === 'Ledger') { + walletProviderAddresses.value = await getLedgerAddress[currency]() as CryptoAddress[] + } else if (provider === 'Trezor') { + walletProviderAddresses.value = await getTrezorAddress[currency]() as CryptoAddress[] + } + } catch (error: any) { + throw new Error(`Error selecting provider: ${error.message}`) + } +} const show_setting_modal = ref(false) @@ -176,6 +232,7 @@ onUnmounted(() =>{
- -
diff --git a/apps/web/src/pages/operators/Operator.vue b/apps/web/src/pages/operators/Operator.vue index 2358326d3..afc10d97e 100644 --- a/apps/web/src/pages/operators/Operator.vue +++ b/apps/web/src/pages/operators/Operator.vue @@ -6,13 +6,13 @@ import useContracts from '@/composables/contracts' import useFiles from '@/composables/files' import useFormat from '@/composables/format' import useUsers from '@/composables/users' -import useWallets from '@/composables/wallet' +import useTestUser from '@/composables/testUser' -const { getUserOperators, registerOperatorWithCasimir, loadingRegisteredOperators, nonregisteredOperators, registeredOperators } = useContracts() +const { registerOperatorWithCasimir, loadingRegisteredOperators } = useContracts() const { exportFile } = useFiles() const { convertString } = useFormat() -const { openWalletsModal } = useWallets() -const { user, addOperator } = useUsers() +const { addOperator } = useUsers() +const { user, nonregisteredOperators, registeredOperators } = useTestUser() // Form inputs const selectedWallet = ref({address: '', wallet_provider: ''}) @@ -87,7 +87,7 @@ const submitButtonTxt = ref('Submit') onMounted(async () => { if (user.value) { - await getUserOperators() + // await getUserOperators() const primaryAccount = user.value.accounts.find(item => { return item.address === user.value?.address}) selectedWallet.value = {address: primaryAccount?.address as string, wallet_provider: primaryAccount?.walletProvider as string} @@ -109,7 +109,7 @@ onMounted(async () => { watch(user, async () => { if (user.value) { - await getUserOperators() + // await getUserOperators() if (selectedWallet.value.address === ''){ const primaryAccount = user.value.accounts.find(item => { item.address === user.value?.address}) selectedWallet.value = {address: primaryAccount?.address as string, wallet_provider: primaryAccount?.walletProvider as string} @@ -123,7 +123,7 @@ watch(selectedWallet, async () =>{ selectedPublicNodeURL.value = '' selectedCollateral.value = '' - await getUserOperators() + // await getUserOperators() if (selectedWallet.value.address === '') { availableOperatorIDs.value = [] @@ -149,6 +149,13 @@ watch([searchInput, selectedHeader, selectedOrientation, currentPage], ()=>{ filterData() }) +const openWalletsModal = () => { + const el = document.getElementById('connect_wallet_button') + if (el) { + el.click() + } +} + const handleInputChangeCollateral = (event: any) => { const value = event.target.value.replace(/[^\d.]/g, '') const parts = value.split('.') @@ -266,7 +273,7 @@ async function submitRegisterOperatorForm() {
to view and register operators... diff --git a/apps/web/src/pages/overview/Overview.vue b/apps/web/src/pages/overview/Overview.vue index 0abad77c6..797a90359 100644 --- a/apps/web/src/pages/overview/Overview.vue +++ b/apps/web/src/pages/overview/Overview.vue @@ -17,7 +17,7 @@ import BreakdownTable from './components/BreakdownTable.vue'
-
+
- + --> -
+
- + --> diff --git a/apps/web/src/pages/overview/components/BreakdownChart.vue b/apps/web/src/pages/overview/components/BreakdownChart.vue index 050393759..414cacb7b 100644 --- a/apps/web/src/pages/overview/components/BreakdownChart.vue +++ b/apps/web/src/pages/overview/components/BreakdownChart.vue @@ -4,11 +4,11 @@ import { onMounted, ref, watch} from 'vue' import useContracts from '@/composables/contracts' import useUsers from '@/composables/users' import useEthers from '@/composables/ethers' +import useTestUser from '@/composables/testUser' import useScreenDimensions from '@/composables/screenDimensions' import { AnalyticsData, ProviderString } from '@casimir/types' -const { currentStaked, refreshBreakdown, stakingRewards, totalWalletBalance } = useContracts() -const { listenForTransactions } = useEthers() +const { currentStaked, stakingRewards, totalWalletBalance } = useTestUser() const { screenWidth } = useScreenDimensions() const { user, getUserAnalytics, userAnalytics } = useUsers() @@ -100,26 +100,26 @@ const setChartData = () => { } onMounted(async () => { - if (user.value?.id) { - await getUserAnalytics() + // if (user.value?.id) { + // await getUserAnalytics() + // setChartData() + // await refreshBreakdown() + // // TODO: Potentially find a better place to initialize these listeners + // // Doing this here because currently we're currently initializing listeners on connectWallet + // // which isn't used if user is already signed in + // listenForTransactions() + // } else { setChartData() - await refreshBreakdown() - // TODO: Potentially find a better place to initialize these listeners - // Doing this here because currently we're currently initializing listeners on connectWallet - // which isn't used if user is already signed in - listenForTransactions() - } else { - setChartData() - } + // } }) watch(user, async () => { - if (user.value?.id) { - await getUserAnalytics() - setChartData() - } else { + // if (user.value?.id) { + // await getUserAnalytics() + // setChartData() + // } else { setChartData() - } + // } }) watch(selectedTimeframe, () => { diff --git a/common/types/src/index.ts b/common/types/src/index.ts index d3b058692..540712d44 100644 --- a/common/types/src/index.ts +++ b/common/types/src/index.ts @@ -25,6 +25,7 @@ import { OperatorAddedSuccess } from './interfaces/OperatorAddedSuccess' import { Pool } from './interfaces/Pool' import { PoolStatus } from './interfaces/PoolStatus' import { ProviderString } from './interfaces/ProviderString' +import { SignInWithEthereumCredentials } from './interfaces/SignInWithEthereumCredentials' import { TransactionRequest } from './interfaces/TransactionRequest' import { RegisteredOperator } from './interfaces/RegisteredOperator' import { RemoveAccountOptions } from './interfaces/RemoveAccountOptions' @@ -61,6 +62,7 @@ export type { OperatorAddedSuccess, Pool, ProviderString, + SignInWithEthereumCredentials, TransactionRequest, RegisteredOperator, RemoveAccountOptions, diff --git a/common/types/src/interfaces/LoginCredentials.ts b/common/types/src/interfaces/LoginCredentials.ts index 355b7c1ca..a9a9b9c11 100644 --- a/common/types/src/interfaces/LoginCredentials.ts +++ b/common/types/src/interfaces/LoginCredentials.ts @@ -5,6 +5,4 @@ export interface LoginCredentials { address: string provider: ProviderString currency: Currency - message: string - signedMessage: string } \ No newline at end of file diff --git a/common/types/src/interfaces/SignInWithEthereumCredentials.ts b/common/types/src/interfaces/SignInWithEthereumCredentials.ts new file mode 100644 index 000000000..f667c04ef --- /dev/null +++ b/common/types/src/interfaces/SignInWithEthereumCredentials.ts @@ -0,0 +1,6 @@ +import { LoginCredentials } from './LoginCredentials' + +export interface SignInWithEthereumCredentials extends LoginCredentials { + message: string + signedMessage: string +} \ No newline at end of file