From 43c56c518fe7a8209c4e923cceef7e9ade4e7276 Mon Sep 17 00:00:00 2001 From: lukitsbrian Date: Thu, 1 Apr 2021 15:36:31 -0700 Subject: [PATCH] Stargate upgrade --- .gitignore | 2 + apis/cosmos-reducers-0.39.js | 750 - apis/cosmos-reducers.js | 170 +- apis/cosmos-source-0.39.js | 627 - apis/cosmos-source.js | 454 +- .../images/currencies/{muon.png => atom.png} | Bin common/b64.js | 5 + common/network.js | 4 +- common/proposal-status.js | 14 +- .../ActionModal.vue => Modal/Action.vue} | 46 +- .../ClaimModal.vue => Modal/Claim.vue} | 14 +- .../DepositModal.vue => Modal/Deposit.vue} | 14 +- .../SendModal.vue => Modal/Send.vue} | 30 +- .../StakeModal.vue => Modal/Stake.vue} | 29 +- .../{ActionModals => Modal}/TableInvoice.vue | 8 +- .../UnstakeModal.vue => Modal/Unstake.vue} | 31 +- .../VoteModal.vue => Modal/Vote.vue} | 18 +- components/address/AccountList.vue | 4 +- components/address/FieldSeed.vue | 2 +- components/address/ImportNameStep.vue | 16 +- components/address/ImportSeedStep.vue | 18 +- components/address/LiSession.vue | 2 +- components/address/NameStep.vue | 22 +- components/address/NewSeedStep.vue | 6 +- components/address/PasswordStep.vue | 14 +- components/address/Seed.vue | 4 +- components/common/AppHeader.vue | 2 +- components/common/AppMenu.vue | 8 +- components/common/Bar.vue | 2 +- components/common/ConnectedNetwork.vue | 2 +- components/common/FormGroup.vue | 2 +- components/common/Notifications.vue | 4 +- components/common/Steps.vue | 2 +- components/common/TableContainer.vue | 6 +- components/common/UserMenu.vue | 6 +- components/common/UserMenuAddress.vue | 2 +- components/{address => common}/Warning.vue | 0 components/governance/ParticipantList.vue | 4 +- components/governance/ProposalHeader.vue | 16 +- components/governance/ProposalRow.vue | 2 +- components/portfolio/Balances.vue | 12 +- components/portfolio/Delegations.vue | 4 +- components/portfolio/Undelegations.vue | 6 +- components/staking/TableValidators.vue | 8 +- components/staking/ValidatorRow.vue | 4 +- components/transactions/EventList.vue | 2 +- components/transactions/TransactionItem.vue | 4 +- layouts/default.vue | 4 +- network.js | 25 +- package.json | 2 +- pages/create/index.vue | 2 +- pages/explore/index.vue | 18 +- pages/extension/index.vue | 4 +- pages/index.vue | 6 +- pages/keplr/index.vue | 4 +- pages/ledger/index.vue | 6 +- pages/proposals/_id.vue | 16 +- pages/proposals/index.vue | 10 +- pages/recover/index.vue | 2 +- pages/signin/index.vue | 14 +- pages/transactions/index.vue | 6 +- pages/validators/_address.vue | 18 +- pages/validators/index.vue | 12 +- pages/welcome/index.vue | 22 +- static/currencies/{muon.png => atom.png} | Bin yarn.lock | 19288 ++++++++-------- 66 files changed, 10366 insertions(+), 11495 deletions(-) delete mode 100644 apis/cosmos-reducers-0.39.js delete mode 100644 apis/cosmos-source-0.39.js rename assets/images/currencies/{muon.png => atom.png} (100%) create mode 100644 common/b64.js rename components/{ActionModals/ActionModal.vue => Modal/Action.vue} (95%) rename components/{ActionModals/ClaimModal.vue => Modal/Claim.vue} (96%) rename components/{ActionModals/DepositModal.vue => Modal/Deposit.vue} (96%) rename components/{ActionModals/SendModal.vue => Modal/Send.vue} (96%) rename components/{ActionModals/StakeModal.vue => Modal/Stake.vue} (94%) rename components/{ActionModals => Modal}/TableInvoice.vue (97%) rename components/{ActionModals/UnstakeModal.vue => Modal/Unstake.vue} (91%) rename components/{ActionModals/VoteModal.vue => Modal/Vote.vue} (93%) rename components/{address => common}/Warning.vue (100%) rename static/currencies/{muon.png => atom.png} (100%) diff --git a/.gitignore b/.gitignore index e8f682ba..86c25e27 100644 --- a/.gitignore +++ b/.gitignore @@ -88,3 +88,5 @@ sw.* # Vim swap files *.swp + +package-lock.json \ No newline at end of file diff --git a/apis/cosmos-reducers-0.39.js b/apis/cosmos-reducers-0.39.js deleted file mode 100644 index 6a1f3999..00000000 --- a/apis/cosmos-reducers-0.39.js +++ /dev/null @@ -1,750 +0,0 @@ -import BigNumber from 'bignumber.js' -import { reverse, sortBy, uniq, uniqWith } from 'lodash' -import { encodeB32, decodeB32 } from '~/common/address' -import { getProposalSummary } from '~/common/common-reducers' -import { lunieMessageTypes } from '~/common/lunie-message-types' -import network from '~/common/network' - -function proposalBeginTime(proposal) { - const status = getProposalStatus(proposal) - switch (status) { - case 'DepositPeriod': - return proposal.submit_time - case 'VotingPeriod': - return proposal.voting_start_time - case 'Passed': - case 'Rejected': - return proposal.voting_end_time - } -} - -function proposalEndTime(proposal) { - const status = getProposalStatus(proposal) - switch (status) { - case 'DepositPeriod': - return proposal.deposit_end_time - case 'VotingPeriod': - // the end time lives in the past already if the proposal is finalized - // eslint-disable-next-line no-fallthrough - case 'Passed': - case 'Rejected': - return proposal.voting_end_time - } -} - -function proposalFinalized(proposal) { - return ['Passed', 'Rejected'].includes(getProposalStatus(proposal)) -} - -export function getStakingCoinViewAmount(chainStakeAmount) { - const coinLookup = network.getCoinLookup(network.stakingDenom, 'viewDenom') - - return coinReducer({ - amount: chainStakeAmount, - denom: coinLookup.chainDenom, - }).amount -} - -export function coinReducer(chainCoin, ibcInfo) { - const chainDenom = ibcInfo ? ibcInfo.denom : chainCoin.denom - const coinLookup = network.getCoinLookup(chainDenom) - const sourceChain = ibcInfo ? ibcInfo.chainTrace[0] : undefined - - if (!coinLookup) { - return { - supported: false, - amount: chainCoin.amount, - denom: chainDenom, - sourceChain, - } - } - - const precision = coinLookup.chainToViewConversionFactor - .toString() - .split('.')[1].length - - return { - supported: true, - amount: BigNumber(chainCoin.amount) - .times(coinLookup.chainToViewConversionFactor) - .toFixed(precision), - denom: coinLookup.viewDenom, - sourceChain, - } -} - -/* if you don't get this, write fabian@lunie.io */ -// expected rewards if delegator stakes x tokens -export const expectedRewardsPerToken = ( - validator, - commission, - annualProvision -) => { - if (validator.status === 'INACTIVE' || validator.jailed === true) { - return 0 - } - - // share of all provisioned block rewards all delegators of this validator get - const totalAnnualValidatorRewards = BigNumber(validator.votingPower).times( - annualProvision - ) - // the validator takes a cut in amount of the commission - const totalAnnualDelegatorRewards = totalAnnualValidatorRewards.times( - BigNumber(1).minus(commission) - ) - - // validator.tokens is the amount of all tokens delegated to that validator - // one token delegated would receive x percentage of all delegator rewards - const delegatorSharePerToken = BigNumber(1).div(validator.tokens) - const annualDelegatorRewardsPerToken = totalAnnualDelegatorRewards.times( - delegatorSharePerToken - ) - return annualDelegatorRewardsPerToken -} - -// reduce deposits to one number -// ATTENTION doesn't consider multi denom deposits -function getDeposit(proposal) { - const sum = proposal.total_deposit - .filter(({ denom }) => denom === network.stakingDenom) - .reduce((sum, cur) => sum.plus(cur.amount), BigNumber(0)) - return getStakingCoinViewAmount(sum) -} - -function getTotalVotePercentage(proposal, totalBondedTokens, totalVoted) { - // for passed proposals we can't calculate the total voted percentage, as we don't know the totalBondedTokens in the past - if (proposalFinalized(proposal)) return -1 - if (BigNumber(totalVoted).eq(0)) return 0 - if (!totalBondedTokens) return -1 - return Number( - BigNumber(totalVoted) - .div(getStakingCoinViewAmount(totalBondedTokens)) - .toFixed(4) // output is 0.1234 = 12.34% - ) -} - -export function tallyReducer(proposal, tally, totalBondedTokens) { - // if the proposal is out of voting, use the final result for the tally - if (proposalFinalized(proposal)) { - tally = proposal.final_tally_result - } - - const totalVoted = getStakingCoinViewAmount( - BigNumber(tally.yes) - .plus(tally.no) - .plus(tally.abstain) - .plus(tally.no_with_veto) - ) - - return { - yes: getStakingCoinViewAmount(tally.yes), - no: getStakingCoinViewAmount(tally.no), - abstain: getStakingCoinViewAmount(tally.abstain), - veto: getStakingCoinViewAmount(tally.no_with_veto), - total: totalVoted, - totalVotedPercentage: getTotalVotePercentage( - proposal, - totalBondedTokens, - totalVoted - ), - } -} - -export function depositReducer(deposit, validators) { - return { - id: deposit.depositor, - amount: deposit.amount.map(coinReducer), - depositer: networkAccountReducer(deposit.depositor, validators), - } -} - -export function voteReducer(vote, validators) { - return { - id: String(vote.proposal_id.concat(`_${vote.voter}`)), - voter: networkAccountReducer(vote.voter, validators), - option: vote.option, - } -} - -function networkAccountReducer(address, validators) { - const proposerValAddress = address - ? encodeB32(decodeB32(address), network.validatorAddressPrefix, `hex`) - : '' - const validator = - validators && proposerValAddress.length > 0 - ? validators[proposerValAddress] - : undefined - return { - name: validator ? validator.name : undefined, - address: validator ? proposerValAddress : address || '', - picture: validator ? validator.picture : '', - validator, - } -} - -export function topVoterReducer(topVoter) { - return { - name: topVoter.name, - address: topVoter.operatorAddress, - votingPower: topVoter.votingPower, - picture: topVoter.picture, - validator: topVoter, - } -} - -function getValidatorStatus(validator) { - if (validator.status === 2) { - return { - status: 'ACTIVE', - status_detailed: 'active', - } - } - if ( - validator.signing_info && - new Date(validator.signing_info.jailed_until) > new Date(9000, 1, 1) - ) { - return { - status: 'INACTIVE', - status_detailed: 'banned', - } - } - - return { - status: 'INACTIVE', - status_detailed: 'inactive', - } -} - -export function blockReducer(block) { - return { - id: block.block_meta.block_id.hash, - height: block.block_meta.header.height, - chainId: block.block_meta.header.chain_id, - hash: block.block_meta.block_id.hash, - time: block.block_meta.header.time, - proposer_address: block.block_meta.header.proposer_address, - } -} - -// delegations rewards in Tendermint are located in events as strings with this form: -// amount: {"15000umuon"}, or in multidenom networks they look like this: -// amount: {"15000ungm,100000uchf,110000ueur,2000000ujpy"} -// That is why we need this separate function to extract those amounts in this format -export function rewardCoinReducer(reward) { - const multiDenomRewardsArray = reward.split(`,`) - const mappedMultiDenomRewardsArray = multiDenomRewardsArray.map((reward) => { - const rewardDenom = reward.match(/[a-z]+/gi)[0] - const rewardAmount = reward.match(/[0-9]+/gi) - return coinReducer({ - amount: rewardAmount, - denom: rewardDenom, - }) - }) - return mappedMultiDenomRewardsArray -} - -export function balanceReducer(lunieCoin, delegations, undelegations) { - const isStakingDenom = lunieCoin.denom === network.stakingDenom - const delegatedStake = delegations.reduce( - (sum, { amount }) => BigNumber(sum).plus(amount), - 0 - ) - const undelegatingStake = undelegations.reduce( - (sum, { amount }) => BigNumber(sum).plus(amount), - 0 - ) - const total = isStakingDenom - ? BigNumber(lunieCoin.amount).plus(delegatedStake).plus(undelegatingStake) - : lunieCoin.amount - return { - id: lunieCoin.denom, - type: isStakingDenom ? 'STAKE' : 'CURRENCY', - total, - denom: lunieCoin.denom, - available: lunieCoin.amount, - staked: delegatedStake.amount || 0, - sourceChain: lunieCoin.sourceChain, - } -} - -export function undelegationReducer(undelegation, validator) { - return { - id: `${validator.operatorAddress}_${undelegation.creation_height}`, - delegatorAddress: undelegation.delegator_address, - validator, - amount: getStakingCoinViewAmount(undelegation.balance), - startHeight: undelegation.creation_height, - endTime: undelegation.completion_time, - } -} - -export function reduceFormattedRewards(reward, validator) { - return reward.map((denomReward) => { - const lunieCoin = coinReducer(denomReward) - if (Number(lunieCoin.amount) < 0.000001) return null - - return { - id: `${validator.operatorAddress}_${lunieCoin.denom}`, - denom: lunieCoin.denom, - amount: lunieCoin.amount, - validator, - } - }) -} - -export async function rewardReducer(rewards, validatorsDictionary) { - const formattedRewards = rewards.map((reward) => ({ - reward: reward.reward, - validator: validatorsDictionary[reward.validator_address], - })) - const multiDenomRewardsArray = await Promise.all( - formattedRewards.map(({ reward, validator }) => - reduceFormattedRewards(reward, validator) - ) - ) - return multiDenomRewardsArray.flat().filter((reward) => reward) -} - -const proposalTypeEnumDictionary = { - TextProposal: 'TEXT', - CommunityPoolSpendProposal: 'TREASURY', - ParameterChangeProposal: 'PARAMETER_CHANGE', -} - -// map Cosmos SDK message types to Lunie message types -export function getMessageType(type) { - // different networks use different prefixes for the transaction types like cosmos/MsgSend vs core/MsgSend in Terra - const transactionTypeSuffix = type.split('/')[1] - switch (transactionTypeSuffix) { - case 'MsgSend': - return lunieMessageTypes.SEND - case 'MsgDelegate': - return lunieMessageTypes.STAKE - case 'MsgBeginRedelegate': - return lunieMessageTypes.RESTAKE - case 'MsgUndelegate': - return lunieMessageTypes.UNSTAKE - case 'MsgWithdrawDelegationReward': - return lunieMessageTypes.CLAIM_REWARDS - case 'MsgSubmitProposal': - return lunieMessageTypes.SUBMIT_PROPOSAL - case 'MsgVote': - return lunieMessageTypes.VOTE - case 'MsgDeposit': - return lunieMessageTypes.DEPOSIT - default: - return lunieMessageTypes.UNKNOWN - } -} - -export function setTransactionSuccess(transaction, index) { - // TODO identify logs per message - if (transaction.code) { - return false - } - return true -} - -export function sendDetailsReducer(message) { - return { - from: [message.from_address], - to: [message.to_address], - amount: message.amount.map(coinReducer), - } -} - -export function stakeDetailsReducer(message) { - return { - to: [message.validator_address], - amount: coinReducer(message.amount), - } -} - -export function restakeDetailsReducer(message) { - return { - from: [message.validator_src_address], - to: [message.validator_dst_address], - amount: coinReducer(message.amount), - } -} - -export function unstakeDetailsReducer(message) { - return { - from: [message.validator_address], - amount: coinReducer(message.amount), - } -} - -export function claimRewardsDetailsReducer(message, transaction) { - return { - from: message.validators, - amounts: claimRewardsAmountReducer(transaction), - } -} - -export function claimRewardsAmountReducer(transaction) { - const transactionClaimEvents = - transaction.events && - transaction.events.filter((event) => event.type === `transfer`) - if (!transactionClaimEvents) { - return [{ denom: '', amount: 0 }] - } - // filter out unsuccessful messages - if (transaction.logs) { - transaction.logs.forEach((log, index) => { - if (log.success !== true) { - transactionClaimEvents.splice(index, 1) - } - }) - } - // if transactionClaimEvents is empty after the successful transaction check, we default it - if (transactionClaimEvents.length === 0) { - return [{ denom: '', amount: 0 }] - } - const amountAttributes = transactionClaimEvents - .map((tx) => tx.attributes) - .find((attributes) => attributes.length > 0) - .filter((attribute) => attribute.key === `amount`) - const allClaimedRewards = amountAttributes - .map((amount) => amount.value) - .map((rewardValue) => rewardCoinReducer(rewardValue)) - const aggregatedClaimRewardsObject = allClaimedRewards.reduce( - (all, rewards) => { - rewards.forEach((reward) => { - all = { - ...all, - [reward.denom]: BigNumber(reward.amount).plus(all[reward.denom] || 0), - } - }) - return all - }, - {} - ) - const claimedRewardsDenomArray = Object.entries(aggregatedClaimRewardsObject) - return claimedRewardsDenomArray.map(([denom, amount]) => ({ denom, amount })) -} - -export function submitProposalDetailsReducer(message) { - return { - proposalType: message.content.type, - proposalTitle: message.content.value.title, - proposalDescription: message.content.value.description, - initialDeposit: coinReducer(message.initial_deposit[0]), - } -} - -export function voteProposalDetailsReducer(message) { - return { - proposalId: message.proposal_id, - voteOption: message.option, - } -} - -export function depositDetailsReducer(message) { - return { - proposalId: message.proposal_id, - amount: coinReducer(message.amount[0]), - } -} - -// function to map cosmos messages to our details format -export function transactionDetailsReducer(type, message, transaction) { - let details - switch (type) { - case lunieMessageTypes.SEND: - details = sendDetailsReducer(message) - break - case lunieMessageTypes.STAKE: - details = stakeDetailsReducer(message) - break - case lunieMessageTypes.RESTAKE: - details = restakeDetailsReducer(message) - break - case lunieMessageTypes.UNSTAKE: - details = unstakeDetailsReducer(message) - break - case lunieMessageTypes.CLAIM_REWARDS: - details = claimRewardsDetailsReducer(message, transaction) - break - case lunieMessageTypes.SUBMIT_PROPOSAL: - details = submitProposalDetailsReducer(message) - break - case lunieMessageTypes.VOTE: - details = voteProposalDetailsReducer(message) - break - case lunieMessageTypes.DEPOSIT: - details = depositDetailsReducer(message) - break - default: - details = {} - } - - return { - type, - ...details, - } -} - -export function claimRewardsMessagesAggregator(claimMessages) { - // reduce all withdraw messages to one one collecting the validators from all the messages - const onlyValidatorsAddressesArray = claimMessages.map( - (msg) => msg.value.validator_address - ) - return { - type: `xxx/MsgWithdrawDelegationReward`, // prefix omited as not important - value: { - validators: onlyValidatorsAddressesArray, - }, - } -} - -function getProposalStatus(proposal) { - return proposal.proposal_status -} - -export function proposalReducer( - proposal, - tally, - proposer, - totalBondedTokens, - detailedVotes, - validators -) { - return { - id: Number(proposal.id), - proposalId: String(proposal.id), - type: proposalTypeEnumDictionary[proposal.content.type.split('/')[1]], - title: proposal.content.value.title, - description: proposal.content.value.changes - ? `Parameter: ${JSON.stringify( - proposal.content.value.changes, - null, - 4 - )}\nDescription: ` - : `` + proposal.content.value.description, - creationTime: proposal.submit_time, - status: getProposalStatus(proposal), - statusBeginTime: proposalBeginTime(proposal), - statusEndTime: proposalEndTime(proposal), - tally: tallyReducer(proposal, tally, totalBondedTokens), - deposit: getDeposit(proposal), - proposer: proposer - ? networkAccountReducer(proposer.proposer, validators) - : undefined, - summary: getProposalSummary( - proposalTypeEnumDictionary[proposal.content.type.split('/')[1]] - ), - detailedVotes, - } -} - -export function getTransactionLogs(transaction, index) { - if (!transaction.logs || !transaction.logs[index]) { - return JSON.parse(JSON.stringify(transaction.raw_log)).message - } - const log = transaction.logs[index] - return log.success === false ? transaction.logs[0].log : log.log // failing txs show the first logs -} - -export function transactionReducer(transaction) { - try { - // TODO check if this is anywhere not an array - let fees - if ( - transaction.tx.value && - Array.isArray(transaction.tx.value.fee.amount) - ) { - fees = transaction.tx.value.fee.amount.map((coin) => { - const coinLookup = network.getCoinLookup(network, coin.denom) - return coinReducer(coin, coinLookup) - }) - } else { - fees = transaction.tx.auth_info.fee.amount.map((fee) => { - const coinLookup = network.getCoinLookup(network, fee.denom) - return coinReducer(fee, coinLookup) - }) - } - const { claimMessages, otherMessages } = transaction.tx.value.msg.reduce( - ({ claimMessages, otherMessages }, message) => { - // we need to aggregate all withdraws as we display them together in one transaction - if (getMessageType(message.type) === lunieMessageTypes.CLAIM_REWARDS) { - claimMessages.push(message) - } else { - otherMessages.push(message) - } - return { claimMessages, otherMessages } - }, - { claimMessages: [], otherMessages: [] } - ) - - // we need to aggregate claim rewards messages in one single one to avoid transaction repetition - const claimMessage = - claimMessages.length > 0 - ? claimRewardsMessagesAggregator(claimMessages) - : undefined - const allMessages = claimMessage - ? otherMessages.concat(claimMessage) // add aggregated claim message - : otherMessages - const returnedMessages = allMessages.map( - ({ value: message, type }, messageIndex) => ({ - id: transaction.txhash, - type: getMessageType(type), - hash: transaction.txhash, - networkId: network.id, - key: `${transaction.txhash}_${messageIndex}`, - height: transaction.height, - details: transactionDetailsReducer( - getMessageType(type), - message, - transaction - ), - timestamp: transaction.timestamp, - memo: transaction.tx.value.memo, - fees, - success: setTransactionSuccess(transaction, messageIndex), - log: getTransactionLogs(transaction, messageIndex), - involvedAddresses: extractInvolvedAddresses( - transaction.logs.find( - ({ msg_index: msgIndex }) => msgIndex === messageIndex - ).events - ), - rawMessage: { - type, - message, - }, - }) - ) - return returnedMessages - } catch (error) { - console.error(error) - return [] // must return something differ from undefined - } -} -export function transactionsReducer(txs) { - const duplicateFreeTxs = uniqWith(txs, (a, b) => a.txhash === b.txhash) - const sortedTxs = sortBy(duplicateFreeTxs, ['timestamp']) - const reversedTxs = reverse(sortedTxs) - // here we filter out all transactions related to validators - return reversedTxs.reduce((collection, transaction) => { - return collection.concat(transactionReducer(transaction)) - }, []) -} - -export function delegationReducer(delegation, validator, active) { - const { denom } = coinReducer({ - amount: delegation.balance, - denom: network.stakingDenom, - }) - return { - id: delegation.validator_address.concat(`-${denom}`), - validatorAddress: delegation.validator_address, - delegatorAddress: delegation.delegator_address, - validator, - amount: getStakingCoinViewAmount(delegation.balance), - active, - } -} - -export function getValidatorUptimePercentage(validator, signedBlocksWindow) { - if ( - validator.signing_info && - validator.signing_info.missed_blocks_counter && - signedBlocksWindow - ) { - return ( - 1 - - Number(validator.signing_info.missed_blocks_counter) / - Number(signedBlocksWindow) - ) - } else { - return 1 - } -} - -export function validatorReducer( - signedBlocksWindow, - validator, - annualProvision -) { - const statusInfo = getValidatorStatus(validator) - let websiteURL = validator.description.website - if (!websiteURL || websiteURL === '[do-not-modify]') { - websiteURL = '' - } else if (!websiteURL.match(/http[s]?/)) { - websiteURL = `https://` + websiteURL - } - - return { - id: validator.operator_address, - operatorAddress: validator.operator_address, - consensusPubkey: validator.consensus_pubkey, - jailed: validator.jailed, - details: validator.description.details, - website: websiteURL, - identity: validator.description.identity, - name: validator.description.moniker, - votingPower: validator.votingPower.toFixed(6), - startHeight: validator.signing_info - ? validator.signing_info.start_height - : undefined, - uptimePercentage: getValidatorUptimePercentage( - validator, - signedBlocksWindow - ), - tokens: getStakingCoinViewAmount(validator.tokens), - commissionUpdateTime: validator.commission.update_time, - commission: Number(validator.commission.commission_rates.rate).toFixed(6), - maxCommission: validator.commission.commission_rates.max_rate, - maxChangeCommission: validator.commission.commission_rates.max_change_rate, - status: statusInfo.status, - statusDetailed: statusInfo.status_detailed, - expectedReturns: expectedRewardsPerToken( - validator, - validator.commission.commission_rates.rate, - annualProvision - ).toFixed(6), - } -} - -export function extractInvolvedAddresses(messageEvents) { - // If the transaction has failed, it doesn't get tagged - if (!Array.isArray(messageEvents)) return [] - - // extract all addresses from events that are either sender or recipient - const involvedAddresses = messageEvents.reduce((involvedAddresses, event) => { - const senderAttributes = event.attributes - .filter(({ key }) => key === 'sender') - .map((sender) => sender.value) - if (senderAttributes.length) { - involvedAddresses = [...involvedAddresses, ...senderAttributes] - } - - const recipientAttribute = event.attributes.find( - ({ key }) => key === 'recipient' - ) - if (recipientAttribute) { - involvedAddresses.push(recipientAttribute.value) - } - - return involvedAddresses - }, []) - return uniq(involvedAddresses) -} - -export function undelegationEndTimeReducer(transaction) { - const events = transaction.logs.reduce( - (events, log) => (log.events ? events.concat(log.events) : events), - [] - ) - - let completionTimeAttribute - events.find(({ attributes }) => { - if (attributes) { - completionTimeAttribute = attributes.find( - (tx) => tx.key === `completion_time` - ) - } - return !!completionTimeAttribute - }) - return completionTimeAttribute ? completionTimeAttribute.value : undefined -} diff --git a/apis/cosmos-reducers.js b/apis/cosmos-reducers.js index c54f9829..a91371e5 100644 --- a/apis/cosmos-reducers.js +++ b/apis/cosmos-reducers.js @@ -1,3 +1,5 @@ +/* eslint-disable */ + import BigNumber from 'bignumber.js' import { reverse, sortBy, uniq, uniqWith } from 'lodash' import { encodeB32, decodeB32 } from '~/common/address' @@ -5,40 +7,8 @@ import { getProposalSummary } from '~/common/common-reducers' import { lunieMessageTypes } from '~/common/lunie-message-types' import network from '~/common/network' -function proposalBeginTime(proposal) { - const status = getProposalStatus(proposal) - switch (status) { - case 'DepositPeriod': - return proposal.submit_time - case 'VotingPeriod': - return proposal.voting_start_time - case 'Passed': - case 'Rejected': - return proposal.voting_end_time - } -} - -function proposalEndTime(proposal) { - const status = getProposalStatus(proposal) - switch (status) { - case 'DepositPeriod': - return proposal.deposit_end_time - case 'VotingPeriod': - // the end time lives in the past already if the proposal is finalized - // eslint-disable-next-line no-fallthrough - case 'Passed': - case 'Rejected': - return proposal.voting_end_time - } -} - -function proposalFinalized(proposal) { - return ['Passed', 'Rejected'].includes(getProposalStatus(proposal)) -} - export function getStakingCoinViewAmount(chainStakeAmount) { const coinLookup = network.getCoinLookup(network.stakingDenom, 'viewDenom') - return coinReducer({ amount: chainStakeAmount, denom: coinLookup.chainDenom, @@ -73,42 +43,14 @@ export function coinReducer(chainCoin, ibcInfo) { } } -/* if you don't get this, write fabian@lunie.io */ -// expected rewards if delegator stakes x tokens -export const expectedRewardsPerToken = ( - validator, - commission, - annualProvision -) => { - if (validator.status === 'INACTIVE' || validator.jailed === true) { - return 0 - } - - // share of all provisioned block rewards all delegators of this validator get - const totalAnnualValidatorRewards = BigNumber(validator.votingPower).times( - annualProvision - ) - // the validator takes a cut in amount of the commission - const totalAnnualDelegatorRewards = totalAnnualValidatorRewards.times( - BigNumber(1).minus(commission) - ) - - // validator.tokens is the amount of all tokens delegated to that validator - // one token delegated would receive x percentage of all delegator rewards - const delegatorSharePerToken = BigNumber(1).div(validator.tokens) - const annualDelegatorRewardsPerToken = totalAnnualDelegatorRewards.times( - delegatorSharePerToken - ) - return annualDelegatorRewardsPerToken -} - // reduce deposits to one number // ATTENTION doesn't consider multi denom deposits function getDeposit(proposal) { + const sum = proposal.total_deposit .filter(({ denom }) => denom === network.stakingDenom) - .reduce((sum, cur) => sum.plus(cur.amount), BigNumber(0)) - return getStakingCoinViewAmount(sum) + let s = sum.reduce((ss, cur) => { return ss.plus(cur.amount) }, BigNumber(0)) + return getStakingCoinViewAmount(s) } function getTotalVotePercentage(proposal, totalBondedTokens, totalVoted) { @@ -498,47 +440,57 @@ export function claimRewardsMessagesAggregator(claimMessages) { } } -function getProposalStatus(proposal) { - return { - 1: 'DepositPeriod', - 2: 'VotingPeriod', - 3: 'Passed', - 4: 'Rejected', - 5: 'Failed', - }[proposal.status] +function proposalBeginTime(proposal) { + switch (proposal.status) { + case 'PROPOSAL_STATUS_DEPOSIT_PERIOD': + return proposal.submit_time + case 'PROPOSAL_STATUS_VOTING_PERIOD': + return proposal.voting_start_time + case 'PROPOSAL_STATUS_PASSED': + case 'PROPOSAL_STATUS_REJECTED': + return proposal.voting_end_time + } +} + +function proposalEndTime(proposal) { + switch (proposal.status) { + case 'PROPOSAL_STATUS_DEPOSIT_PERIOD': + return proposal.deposit_end_time + case 'PROPOSAL_STATUS_VOTING_PERIOD': + // the end time lives in the past already if the proposal is finalized + // eslint-disable-next-line no-fallthrough + case 'PROPOSAL_STATUS_PASSED': + case 'PROPOSAL_STATUS_REJECTED': + return proposal.voting_end_time + } +} + +function proposalFinalized(proposal) { + return ['PROPOSAL_STATUS_PASSED', 'PROPOSAL_STATUS_REJECTED'].includes( + proposal.status + ) } export function proposalReducer( proposal, - tally, - proposer, totalBondedTokens, - detailedVotes, - validators + detailedVotes ) { + return { - id: Number(proposal.id), - proposalId: String(proposal.id), - type: proposalTypeEnumDictionary[proposal.content.type.split('/')[1]], - title: proposal.content.value.title, - description: proposal.content.value.changes - ? `Parameter: ${JSON.stringify( - proposal.content.value.changes, - null, - 4 - )}\nDescription: ` - : `` + proposal.content.value.description, + id: Number(proposal.proposal_id), + proposalId: String(proposal.proposal_id), + type: proposalTypeEnumDictionary[proposal.content["@type"].split('/')[1]], + title: proposal.content.title, + description: proposal.content.description, creationTime: proposal.submit_time, - status: getProposalStatus(proposal), + status: proposal.status, statusBeginTime: proposalBeginTime(proposal), statusEndTime: proposalEndTime(proposal), - tally: tallyReducer(proposal, tally, totalBondedTokens), + tally: tallyReducer(proposal, detailedVotes.tally, totalBondedTokens), deposit: getDeposit(proposal), - proposer: proposer - ? networkAccountReducer(proposer.proposer, validators) - : undefined, summary: getProposalSummary( - proposalTypeEnumDictionary[proposal.content.type.split('/')[1]] + proposalTypeEnumDictionary[proposal.content["@type"].split('/')[1]] ), detailedVotes, } @@ -640,7 +592,7 @@ export function transactionsReducer(txs) { } export function delegationReducer(delegation, validator, active) { - const coinLookup = network.getCoinLookup(network, delegation.balance.denom) + const coinLookup = network.getCoinLookup(network.stakingDenom, delegation.balance.denom) const { amount, denom } = coinReducer(delegation.balance, coinLookup) return { @@ -653,7 +605,7 @@ export function delegationReducer(delegation, validator, active) { } } -export function getValidatorUptimePercentage(validator, signedBlocksWindow) { +export function getValidatorUptimePercentage(validator, signedBlocksWindow) {// temp if ( validator.signing_info && validator.signing_info.missed_blocks_counter && @@ -662,18 +614,14 @@ export function getValidatorUptimePercentage(validator, signedBlocksWindow) { return ( 1 - Number(validator.signing_info.missed_blocks_counter) / - Number(signedBlocksWindow) + Number(signedBlocksWindow) ) } else { return 1 } } -export function validatorReducer( - signedBlocksWindow, - validator, - annualProvision -) { +export function validatorReducer(validator, annualProvision, supply, pool) { const statusInfo = getValidatorStatus(validator) let websiteURL = validator.description.website if (!websiteURL || websiteURL === '[do-not-modify]') { @@ -682,6 +630,11 @@ export function validatorReducer( websiteURL = `https://` + websiteURL } + const pctCommission = new BigNumber(1 - validator.commission.commission_rates.rate) + const provision = new BigNumber(annualProvision) + const bonded = new BigNumber(pool.pool.bonded_tokens) + const expectedRewards = pctCommission.times(provision.div(bonded)) + return { id: validator.operator_address, operatorAddress: validator.operator_address, @@ -691,14 +644,15 @@ export function validatorReducer( website: websiteURL, identity: validator.description.identity, name: validator.description.moniker, - votingPower: validator.votingPower.toFixed(6), + votingPower: (validator.tokens / supply).toFixed(6), startHeight: validator.signing_info ? validator.signing_info.start_height : undefined, - uptimePercentage: getValidatorUptimePercentage( - validator, - signedBlocksWindow - ), + uptimePercentage: 1, + // getValidatorUptimePercentage( + // validator, + // signedBlocksWindow + // ), tokens: getStakingCoinViewAmount(validator.tokens), commissionUpdateTime: validator.commission.update_time, commission: Number(validator.commission.commission_rates.rate).toFixed(6), @@ -707,11 +661,7 @@ export function validatorReducer( status: statusInfo.status, statusDetailed: statusInfo.status_detailed, expectedReturns: annualProvision - ? expectedRewardsPerToken( - validator, - validator.commission.commission_rates.rate, - annualProvision - ).toFixed(6) + ? expectedRewards : undefined, } } diff --git a/apis/cosmos-source-0.39.js b/apis/cosmos-source-0.39.js deleted file mode 100644 index 2e064500..00000000 --- a/apis/cosmos-source-0.39.js +++ /dev/null @@ -1,627 +0,0 @@ -import BigNumber from 'bignumber.js' -import { keyBy, orderBy, take, reverse, sortBy, chunk } from 'lodash' -import * as reducers from './cosmos-reducers-0.39' -import { encodeB32, decodeB32, pubkeyToAddress } from '~/common/address' -import { setDecimalLength } from '~/common/numbers' -import network from '~/common/network' - -const delegationEnum = { ACTIVE: 'ACTIVE', INACTIVE: 'INACTIVE' } -const PAGE_RECORDS_COUNT = 20 -const GOLANG_NULL_TIME = `0001-01-01T00:00:00Z` // time that gets serialized from null in golang - -export default class CosmosAPI { - constructor(axios) { - this.axios = axios // passed in here to use Nuxt $axios instance - this.network = network - this.reducers = reducers - - // system to stop queries to proceed if store data is not yet available - this.dataReady = new Promise((resolve) => { - this.resolveReady = resolve - }) - - this.loadValidators().then((validators) => { - this.validators = keyBy(validators, 'operatorAddress') - this.resolveReady() - }) - } - - async get(url) { - return await this.axios( - network.apiURL + (url.startsWith('/') ? url : '/' + url) - ).then((res) => res.data) - } - - // querying data from the cosmos REST API - // some endpoints /blocks and /txs have a different response format so they use this.get directly - async query(url, resultSelector = 'result') { - try { - const response = await this.get(url) - return response[resultSelector] - } catch (error) { - console.error( - `Error for query ${url} in network ${this.network.name} (tried 3 times)` - ) - throw error - } - } - - async getAccountInfo(address) { - const result = await this.query(`/auth/accounts/${address}`) - let accountInfo - if (result.type === 'cosmos-sdk/DelayedVestingAccount') { - const vestingAccountType = Object.keys(result.value)[0] - accountInfo = result.value[vestingAccountType].BaseAccount - } else { - accountInfo = result.value - } - return { - accountNumber: accountInfo.account_number, - sequence: accountInfo.sequence || '0', - } - } - - async getSignedBlockWindow() { - const slashingParams = await this.query('/slashing/parameters') - return slashingParams.signed_blocks_window - } - - async getTransactions(address, pageNumber = 0) { - // getting page count - const [senderPage, recipientPage] = await Promise.all([ - this.getPageCount(`/txs?message.sender=${address}`), - this.getPageCount(`/txs?transfer.recipient=${address}`), - ]) - - const requests = [ - this.loadPaginatedTxs( - `/txs?message.sender=${address}`, - senderPage - pageNumber - ), - this.loadPaginatedTxs( - `/txs?transfer.recipient=${address}`, - recipientPage - pageNumber - ), - ] - /* - if it's a first requests we need to load two pages, instead of one, - cause last page could contain less records than any other (even 1) - To do this asynchronously we need to do it with Promise.all - and not wait until last page is loaded - */ - if (!pageNumber) { - if (senderPage - pageNumber > 1) { - requests.push( - this.loadPaginatedTxs( - `/txs?message.sender=${address}`, - senderPage - pageNumber - 1 - ) - ) - } - if (recipientPage - pageNumber > 1) { - requests.push( - this.loadPaginatedTxs( - `/txs?transfer.recipient=${address}`, - recipientPage - pageNumber - 1 - ) - ) - } - } - - const txs = await Promise.all(requests).then(([...results]) => - [].concat(...results) - ) - - return this.reducers.transactionsReducer(txs) - } - - async getValidatorSigningInfos() { - const signingInfos = await this.query(`slashing/signing_infos`) - return signingInfos - } - - async getValidatorSet(height = 'latest') { - const response = await this.query(`validatorsets/${height}`) - return response - } - - async getSelfStake(validator) { - const hexDelegatorAddressFromOperator = decodeB32(validator.operatorAddress) - const delegatorAddressFromOperator = encodeB32( - hexDelegatorAddressFromOperator, - this.network.addressPrefix - ) - - let selfDelegation - try { - selfDelegation = await this.query( - `staking/delegators/${delegatorAddressFromOperator}/delegations/${validator.operatorAddress}` - ) - } catch (error) { - // in some rare cases the validator has no self delegation so this query fails - - if (error.response.status === 500) { - const parsedErrorLog = JSON.parse(error.response.body.error) - if (parsedErrorLog.message.startsWith('no delegation for this')) { - return 0 - } - } - - // still throw in every other unknown case - throw error - } - - return this.reducers.delegationReducer( - selfDelegation, - validator, - delegationEnum.ACTIVE - ).amount - } - - async getValidator(address) { - await this.dataReady - return this.validators[address] - } - - async getValidators() { - await this.dataReady - return Object.values(this.validators) - } - - async loadValidators(height) { - const [ - validators, - annualProvision, - validatorSet, - signedBlocksWindow, - ] = await Promise.all([ - Promise.all([ - this.query(`staking/validators?status=unbonding`), - this.query(`staking/validators?status=bonded`), - this.query(`staking/validators?status=unbonded`), - ]).then((validatorGroups) => [].concat(...validatorGroups)), - this.getAnnualProvision(), - this.getValidatorSet(height), - this.getSignedBlockWindow(), - ]) - - // create a dictionary to reduce array lookups - const consensusValidators = keyBy(validatorSet.validators, 'address') - const totalVotingPower = validatorSet.validators.reduce( - (sum, { voting_power: votingPower }) => sum.plus(votingPower), - BigNumber(0) - ) - - // query for signing info - const signingInfos = keyBy( - await this.getValidatorSigningInfos(validators), - 'address' - ) - - validators.forEach((validator) => { - const consensusAddress = pubkeyToAddress( - validator.consensus_pubkey, - network.validatorConsensusaddressPrefix - ) - validator.votingPower = consensusValidators[consensusAddress] - ? BigNumber(consensusValidators[consensusAddress].voting_power) - .div(totalVotingPower) - .toNumber() - : 0 - validator.signing_info = signingInfos[consensusAddress] - }) - - return validators.map((validator) => - this.reducers.validatorReducer( - signedBlocksWindow, - validator, - annualProvision - ) - ) - } - - async getDetailedVotes(proposal) { - await this.dataReady - const [ - votes, - deposits, - tally, - tallyingParameters, - depositParameters, - ] = await Promise.all([ - this.query(`/gov/proposals/${proposal.id}/votes`), - this.query(`/gov/proposals/${proposal.id}/deposits`), - this.query(`/gov/proposals/${proposal.id}/tally`), - this.query(`/gov/parameters/tallying`), - this.query(`/gov/parameters/deposit`), - ]) - const totalVotingParticipation = BigNumber(tally.yes) - .plus(tally.abstain) - .plus(tally.no) - .plus(tally.no_with_veto) - const formattedDeposits = deposits - ? deposits.map((deposit) => - this.reducers.depositReducer(deposit, this.validators) - ) - : undefined - const depositsSum = formattedDeposits - ? formattedDeposits.reduce((depositAmountAggregator, deposit) => { - return (depositAmountAggregator += Number(deposit.amount[0].amount)) - }, 0) - : undefined - return { - deposits: formattedDeposits, - depositsSum: deposits ? Number(depositsSum).toFixed(6) : undefined, - percentageDepositsNeeded: deposits - ? percentage( - depositsSum, - BigNumber(depositParameters.min_deposit[0].amount) - ) - : undefined, - votes: votes - ? votes.map((vote) => this.reducers.voteReducer(vote, this.validators)) - : undefined, - votesSum: votes ? votes.length : undefined, - votingThresholdYes: Number(tallyingParameters.threshold).toFixed(2), - votingThresholdNo: (1 - tallyingParameters.threshold).toFixed(2), - votingPercentageYes: percentage(tally.yes, totalVotingParticipation), - votingPercentageNo: percentage( - BigNumber(tally.no).plus(tally.no_with_veto), - totalVotingParticipation - ), - timeline: [ - proposal.submit_time - ? { title: `Created`, time: proposal.submit_time } - : undefined, - proposal.deposit_end_time - ? { - title: `Deposit Period Ends`, - // the deposit period can end before the time as the limit is reached already - time: - proposal.voting_start_time !== GOLANG_NULL_TIME && - new Date(proposal.voting_start_time) < - new Date(proposal.deposit_end_time) - ? proposal.voting_start_time - : proposal.deposit_end_time, - } - : undefined, - proposal.voting_start_time - ? { - title: `Voting Period Starts`, - time: - proposal.voting_start_time !== GOLANG_NULL_TIME - ? proposal.voting_start_time - : undefined, - } - : undefined, - proposal.voting_end_time - ? { - title: `Voting Period Ends`, - time: - proposal.voting_end_time !== GOLANG_NULL_TIME - ? proposal.voting_end_time - : undefined, - } - : undefined, - ].filter((x) => !!x), - } - } - - // we can't query the proposer of blocks from past chains - async getProposer(proposal, firstBlock) { - let proposer = { proposer: undefined } - const proposalIsFromPastChain = - proposal.voting_end_time !== GOLANG_NULL_TIME && - new Date(firstBlock.time) > new Date(proposal.voting_end_time) - if (!proposalIsFromPastChain) { - proposer = await this.query(`gov/proposals/${proposal.id}/proposer`) - } - return proposer - } - - async getProposalMetaData(proposal, firstBlock) { - const [tally, detailedVotes, proposer] = await Promise.all([ - this.query(`gov/proposals/${proposal.id}/tally`), - this.getDetailedVotes(proposal), - this.getProposer(proposal, firstBlock), - ]) - return [tally, detailedVotes, proposer] - } - - async getProposals() { - await this.dataReady - const [ - proposalsResponse, - firstBlock, - { bonded_tokens: totalBondedTokens }, - ] = await Promise.all([ - this.query('gov/proposals'), - this.getBlock(1), - this.query('/staking/pool'), - ]) - if (!Array.isArray(proposalsResponse)) return [] - const proposals = await Promise.all( - proposalsResponse.map(async (proposal) => { - const [tally, detailedVotes, proposer] = await this.getProposalMetaData( - proposal, - firstBlock - ) - return this.reducers.proposalReducer( - proposal, - tally, - proposer, - totalBondedTokens, - detailedVotes, - this.validators - ) - }) - ) - - return orderBy(proposals, 'id', 'desc') - } - - async getProposal(proposalId) { - await this.dataReady - const [ - proposal, - { bonded_tokens: totalBondedTokens }, - firstBlock, - ] = await Promise.all([ - this.query(`gov/proposals/${proposalId}`).catch(() => { - throw new Error( - `There is no proposal in the network with ID '${proposalId}'` - ) - }), - this.query(`/staking/pool`), - this.getBlock(1), - ]) - const [tally, detailedVotes, proposer] = await this.getProposalMetaData( - proposal, - firstBlock - ) - return this.reducers.proposalReducer( - proposal, - tally, - proposer, - totalBondedTokens, - detailedVotes, - this.validators - ) - } - - async getTopVoters() { - await this.dataReady - // for now defaulting to pick the 10 largest voting powers - return take( - reverse( - sortBy(this.validators, [ - (validator) => { - return validator.votingPower - }, - ]) - ), - 10 - ) - } - - async getGovernanceOverview() { - const { bonded_tokens: totalBondedTokens } = await this.query( - '/staking/pool' - ) - const [communityPoolArray, topVoters] = await Promise.all([ - this.query('/distribution/community_pool'), - this.getTopVoters(), - ]) - const stakingChainDenom = this.network.getCoinLookup( - this.network.stakingDenom, - 'viewDenom' - ).chainDenom - const communityPool = communityPoolArray.find( - ({ denom }) => denom === stakingChainDenom - ).amount - return { - totalStakedAssets: setDecimalLength( - reducers.getStakingCoinViewAmount(totalBondedTokens), - 2 - ), - totalVoters: undefined, - treasurySize: setDecimalLength( - reducers.getStakingCoinViewAmount(communityPool), - 2 - ), - topVoters: topVoters.map((topVoter) => - this.reducers.topVoterReducer(topVoter) - ), - } - } - - async getDelegatorVote({ proposalId, address }) { - const response = await this.query(`gov/proposals/${proposalId}/votes`) - const votes = response || [] - const vote = votes.find(({ voter }) => voter === address) || {} - return { - option: vote.option, - } - } - - async getBlock(blockHeight) { - let block - if (blockHeight) { - block = await this.get(`blocks/${blockHeight}`) - } else { - block = await this.get(`blocks/latest`) - } - return this.reducers.blockReducer(block) - } - - async getBalances(address) { - const [balancesResponse, delegations, undelegations] = await Promise.all([ - this.query(`bank/balances/${address}`), - this.getDelegationsForDelegator(address), - this.getUndelegationsForDelegator(address), - ]) - const balances = balancesResponse || [] - const coins = await Promise.all( - balances.map(async (balance) => { - let ibcInfo - if (balance.denom.startsWith('ibc/')) { - ibcInfo = await this.getIbcInfo(balance.denom) - } - return this.reducers.coinReducer(balance, ibcInfo) - }) - ) - // also check if there are any denoms as rewards the user has not as a balance - // we need to show those as well in the balance overview as we show the rewards there - const rewards = await this.getRewards(address) - const rewardsBalances = rewards.reduce((coinsAggregator, reward) => { - if ( - !coins.find((coin) => coin.denom === reward.denom) && - !coinsAggregator.find((coin) => coin.denom === reward.denom) - ) { - coinsAggregator.push({ - amount: 0, - denom: reward.denom, - }) - } - return coinsAggregator - }, []) - // join regular balances and rewards balances - coins.push(...rewardsBalances) - - // the user might not have liquid staking tokens but have staking tokens delegated - // if we don't add the staking denom, we would show a 0 total for the staking denom which is wrong - const hasStakingDenom = coins.find( - ({ denom }) => denom === this.network.stakingDenom - ) - if (!hasStakingDenom) { - coins.push({ - amount: BigNumber(0), - denom: this.network.stakingDenom, - }) - } - return coins.map((coin) => { - return this.reducers.balanceReducer(coin, delegations, undelegations) - }) - } - - async getIbcInfo(traceId) { - if (traceId.startsWith('ibc/')) { - traceId = traceId.split(`/`)[1] - } - const result = await this.get( - `/ibc_transfer/v1beta1/denom_traces/${traceId}` - ) - const trace = result.denom_trace - const chainTrace = await Promise.all( - chunk(trace.path.split('/'), 2).map(async ([port, channel]) => { - const result = await this.get( - `/ibc/channel/v1beta1/channels/${channel}/ports/${port}/client_state` - ) - return result.identified_client_state.client_state.chain_id - }) - ) - return { - denom: trace.base_denom, - chainTrace, - } - } - - async getDelegationsForDelegator(address) { - await this.dataReady - const delegations = - (await this.query(`staking/delegators/${address}/delegations`)) || [] - return delegations - .map((delegation) => - this.reducers.delegationReducer( - delegation, - this.validators[delegation.validator_address], - delegationEnum.ACTIVE - ) - ) - .filter((delegation) => BigNumber(delegation.amount).gt(0)) - } - - async getUndelegationsForDelegator(address) { - await this.dataReady - const undelegations = - (await this.query( - `staking/delegators/${address}/unbonding_delegations` - )) || [] - - // undelegations come in a nested format { validator_address, delegator_address, entries } - // we flatten the format to be able to easier iterate over the list - const flattenedUndelegations = undelegations.reduce( - (list, undelegation) => - list.concat( - undelegation.entries.map((entry) => ({ - validator_address: undelegation.validator_address, - delegator_address: undelegation.delegator_address, - balance: entry.balance, - completion_time: entry.completion_time, - creation_height: entry.creation_height, - initial_balance: entry.initial_balance, - })) - ), - [] - ) - return flattenedUndelegations.map((undelegation) => - this.reducers.undelegationReducer( - undelegation, - this.validators[undelegation.validator_address] - ) - ) - } - - async getValidatorDelegations(validator) { - const delegations = await this.query( - `staking/validators/${validator.operatorAddress}/delegations` - ).catch(() => { - return [] - }) - - return delegations.map((delegation) => - this.reducers.delegationReducer( - delegation, - validator, - delegationEnum.ACTIVE - ) - ) - } - - async getAnnualProvision() { - const response = await this.query(`minting/annual-provisions`) - return response - } - - async getRewards(delegatorAddress) { - await this.dataReady - const result = await this.query( - `distribution/delegators/${delegatorAddress}/rewards` - ) - const rewards = (result.rewards || []).filter( - ({ reward }) => reward && reward.length > 0 - ) - return await this.reducers.rewardReducer(rewards, this.validators) - } - - async loadPaginatedTxs(url, page = 1) { - if (page < 1) { - return [] - } - const pagination = `&limit=${PAGE_RECORDS_COUNT}&page=${page}` - const { txs } = await this.get(`${url}${pagination}`) - return txs || [] - } - - async getPageCount(url) { - const page = await this.get(url + `&limit=${PAGE_RECORDS_COUNT}`) - return page.page_total - } -} - -function percentage(x, total) { - // percentage output should always be a number between 0 and 1 - return total.toNumber() > 0 - ? BigNumber(x).div(total).toNumber().toFixed(4) - : 0 -} diff --git a/apis/cosmos-source.js b/apis/cosmos-source.js index 2c676f17..1e077260 100644 --- a/apis/cosmos-source.js +++ b/apis/cosmos-source.js @@ -1,7 +1,10 @@ +/* eslint-disable */ + import BigNumber from 'bignumber.js' import { keyBy, orderBy, take, reverse, sortBy, chunk } from 'lodash' import * as reducers from './cosmos-reducers' -import { encodeB32, decodeB32, pubkeyToAddress } from '~/common/address' +import { encodeB32, decodeB32 } from '~/common/address' +import { urlSafeEncode } from '~/common/b64' import { setDecimalLength } from '~/common/numbers' import network from '~/common/network' @@ -19,11 +22,22 @@ export default class CosmosAPI { this.dataReady = new Promise((resolve) => { this.resolveReady = resolve }) - + this.getBlock(network.minBlockHeight).then(block => { + this.firstBlock = block + }) this.loadValidators().then((validators) => { this.validators = keyBy(validators, 'operatorAddress') this.resolveReady() }) + + } + + getChainStartTime() { + return new Date(this.firstBlock.time) + } + + dataExistsInThisChain(timestamp) { + return new Date(timestamp) > this.getChainStartTime() } async get(url) { @@ -31,13 +45,12 @@ export default class CosmosAPI { network.apiURL + (url.startsWith('/') ? url : '/' + url) ).then((res) => res.data) } - // querying data from the cosmos REST API // some endpoints /blocks and /txs have a different response format so they use this.get directly - async query(url, resultSelector = 'result') { + async query(url) { try { const response = await this.get(url) - return response[resultSelector] + return response } catch (error) { console.error( `Error for query ${url} in network ${this.network.name} (tried 3 times)` @@ -46,75 +59,104 @@ export default class CosmosAPI { } } + async queryPaginate(url, key) { + return await this.query(url + `?pagination.key=${key}`) + } + + async queryAutoPaginate(url) { + var data = await this.query(url) + const keys = Object.keys(data) + const fieldIndex = keys.indexOf("pagination") ? 0 : 1 + const fieldName = keys[fieldIndex] + + var paginatedData = data[fieldName] + while (data.pagination != null && data.pagination.next_key != null) { + data = await this.queryPaginate(url, urlSafeEncode(data.pagination.next_key)) + paginatedData = paginatedData.concat(data[fieldName]) + } + return paginatedData + } + async getAccountInfo(address) { - const accountInfo = await this.query(`/auth/accounts/${address}`) + const accountInfo = await this.query( + `cosmos/auth/v1beta1/accounts/${address}` + ) return { - accountNumber: accountInfo.value.account_number, - sequence: accountInfo.value.sequence || '0', + accountNumber: accountInfo.account.account_number, + sequence: accountInfo.account.sequence || '0', } } + async getAccountTxs(account) { // to be replaced + return await axios.get(`https://api.cosmostation.io/v1/account/txs/${account}`) + } + async getSignedBlockWindow() { - const slashingParams = await this.query('/slashing/parameters') - return slashingParams.signed_blocks_window + const slashingParams = await this.query(`/cosmos/slashing/v1beta1/params`) + return slashingParams.params.signed_blocks_window } async getTransactions(address, pageNumber = 0) { - // getting page count - const [senderPage, recipientPage] = await Promise.all([ - this.getPageCount(`/txs?message.sender=${address}`), - this.getPageCount(`/txs?transfer.recipient=${address}`), - ]) - - const requests = [ - this.loadPaginatedTxs( - `/txs?message.sender=${address}`, - senderPage - pageNumber - ), - this.loadPaginatedTxs( - `/txs?transfer.recipient=${address}`, - recipientPage - pageNumber - ), - ] - /* - if it's a first requests we need to load two pages, instead of one, - cause last page could contain less records than any other (even 1) - To do this asynchronously we need to do it with Promise.all - and not wait until last page is loaded - */ - if (!pageNumber) { - if (senderPage - pageNumber > 1) { - requests.push( - this.loadPaginatedTxs( - `/txs?message.sender=${address}`, - senderPage - pageNumber - 1 - ) - ) - } - if (recipientPage - pageNumber > 1) { - requests.push( - this.loadPaginatedTxs( - `/txs?transfer.recipient=${address}`, - recipientPage - pageNumber - 1 - ) - ) - } - } - - const txs = await Promise.all(requests).then(([...results]) => - [].concat(...results) - ) + // // getting page count + // const [senderPage, recipientPage] = await Promise.all([ + // this.getPageCount(`/cosmos/tx/v1beta1/txs?message.sender=${address}`), + // this.getPageCount(`/cosmos/tx/v1beta1/txs?transfer.recipient=${address}`), + // ]) + + // const requests = [ + // this.loadPaginatedTxs( + // `/cosmos/tx/v1beta1/txs?message.sender=${address}`, + // senderPage - pageNumber + // ), + // this.loadPaginatedTxs( + // `/cosmos/tx/v1beta1/txs?transfer.recipient=${address}`, + // recipientPage - pageNumber + // ), + // ] + // /* + // if it's a first requests we need to load two pages, instead of one, + // cause last page could contain less records than any other (even 1) + // To do this asynchronously we need to do it with Promise.all + // and not wait until last page is loaded + // */ + // if (!pageNumber) { + // if (senderPage - pageNumber > 1) { + // requests.push( + // this.loadPaginatedTxs( + // `/cosmos/tx/v1beta1/txs?message.sender=${address}`, + // senderPage - pageNumber - 1 + // ) + // ) + // } + // if (recipientPage - pageNumber > 1) { + // requests.push( + // this.loadPaginatedTxs( + // `/cosmos/tx/v1beta1/txs?transfer.recipient=${address}`, + // recipientPage - pageNumber - 1 + // ) + // ) + // } + // } + + // const txs = await Promise.all(requests).then(([...results]) => + // [].concat(...results) + // ) + const txs = await this.axios.get(`https://api.cosmostation.io/v1/account/txs/${address}`) return this.reducers.transactionsReducer(txs) } async getValidatorSigningInfos() { - const signingInfos = await this.query(`slashing/signing_infos`) + const signingInfos = await this.queryAutoPaginate( + `cosmos/slashing/v1beta1/signing_infos` + ) return signingInfos } async getValidatorSet(height = 'latest') { - const response = await this.query(`validatorsets/${height}`) + const response = await this.queryAutoPaginate( + `staking/validators?status=BOND_STATUS_BONDED` + ) return response } @@ -128,7 +170,7 @@ export default class CosmosAPI { let selfDelegation try { selfDelegation = await this.query( - `staking/delegators/${delegatorAddressFromOperator}/delegations/${validator.operatorAddress}` + `cosmos/staking/v1beta1/validators/${validator.operatorAddress}/delegations/${delegatorAddressFromOperator}` ) } catch (error) { // in some rare cases the validator has no self delegation so this query fails @@ -160,192 +202,155 @@ export default class CosmosAPI { await this.dataReady return Object.values(this.validators) } - - async loadValidators(height) { + async getStakingSupply() { + const res = await this.query(`cosmos/bank/v1beta1/supply`) + return BigNumber(res.supply[0].amount) + } + async loadValidators() { const [ validators, annualProvision, - validatorSet, - signedBlocksWindow, + supply, + pool ] = await Promise.all([ - Promise.all([ - this.query(`staking/validators?status=unbonding`), - this.query(`staking/validators?status=bonded`), - this.query(`staking/validators?status=unbonded`), - ]).then((validatorGroups) => [].concat(...validatorGroups)), + this.query(`staking/validators?status=BOND_STATUS_BONDED`), this.getAnnualProvision().catch(() => undefined), - this.getValidatorSet(height), - this.getSignedBlockWindow(), + this.getStakingSupply(), + this.query(`cosmos/staking/v1beta1/pool`) ]) + return validators.result.map(validator => reducers.validatorReducer(validator, annualProvision, supply, pool)) - // create a dictionary to reduce array lookups - const consensusValidators = keyBy(validatorSet.validators, 'address') - const totalVotingPower = validatorSet.validators.reduce( - (sum, { voting_power: votingPower }) => sum.plus(votingPower), - BigNumber(0) - ) - - // query for signing info - const signingInfos = keyBy( - await this.getValidatorSigningInfos(validators), - 'address' - ) - - validators.forEach((validator) => { - const consensusAddress = pubkeyToAddress( - validator.consensus_pubkey, - network.validatorConsensusaddressPrefix - ) - validator.votingPower = consensusValidators[consensusAddress] - ? BigNumber(consensusValidators[consensusAddress].voting_power) - .div(totalVotingPower) - .toNumber() - : 0 - validator.signing_info = signingInfos[consensusAddress] - }) + } - return validators.map((validator) => - this.reducers.validatorReducer( - signedBlocksWindow, - validator, - annualProvision - ) - ) + async getInflation() { + const rate = await this.query(`cosmos/mint/v1beta1/inflation`) + const params = await this.query(`cosmos/mint/v1beta1/params`) + const annual_provisions = await this.query(`cosmos/mint/v1beta1/annual_provisions`) } - async getDetailedVotes(proposal) { + async getDetailedVotes(proposal, tallyParams, depositParams) { await this.dataReady - const [ - votes, - deposits, - tally, - tallyingParameters, - depositParameters, - ] = await Promise.all([ - this.query(`/gov/proposals/${proposal.id}/votes`), - this.query(`/gov/proposals/${proposal.id}/deposits`), - this.query(`/gov/proposals/${proposal.id}/tally`), - this.query(`/gov/parameters/tallying`), - this.query(`/gov/parameters/deposit`), - ]) + + const dataAvailable = this.dataExistsInThisChain(proposal.submit_time) + const votingComplete = ['PROPOSAL_STATUS_PASSED', 'PROPOSAL_STATUS_REJECTED'].includes(proposal.status) + + const votes = dataAvailable ? await this.queryAutoPaginate(`/cosmos/gov/v1beta1/proposals/${proposal.proposal_id}/votes`) : [] + const deposits = dataAvailable ? await this.queryAutoPaginate(`/cosmos/gov/v1beta1/proposals/${proposal.proposal_id}/deposits`) : [] + const tally = votingComplete ? proposal.final_tally_result : await this.query(`/cosmos/gov/v1beta1/proposals/${proposal.proposal_id}/tally`) + const totalVotingParticipation = BigNumber(tally.yes) .plus(tally.abstain) .plus(tally.no) .plus(tally.no_with_veto) - const formattedDeposits = deposits + const formattedDeposits = deposits.length ? deposits.map((deposit) => - this.reducers.depositReducer(deposit, this.validators) - ) - : undefined - const depositsSum = formattedDeposits + this.reducers.depositReducer(deposit, this.validators) + ) + : [] + + const depositsSum = deposits.length ? formattedDeposits.reduce((depositAmountAggregator, deposit) => { - return (depositAmountAggregator += Number(deposit.amount[0].amount)) - }, 0) - : undefined + return (depositAmountAggregator += deposit.amount.length ? Number(deposit.amount[0].amount) : 0) + }, 0) + : [] + return { deposits: formattedDeposits, - depositsSum: deposits ? Number(depositsSum).toFixed(6) : undefined, + depositsSum: deposits.length ? Number(depositsSum).toFixed(6) : [], percentageDepositsNeeded: deposits ? percentage( - depositsSum, - BigNumber(depositParameters.min_deposit[0].amount) - ) - : undefined, - votes: votes + depositsSum, + BigNumber(depositParams.deposit_params.min_deposit[0].amount) + ) + : [], + votes: votes.length ? votes.map((vote) => this.reducers.voteReducer(vote, this.validators)) - : undefined, - votesSum: votes ? votes.length : undefined, - votingThresholdYes: Number(tallyingParameters.threshold).toFixed(2), - votingThresholdNo: (1 - tallyingParameters.threshold).toFixed(2), + : [], + votesSum: votes ? votes.length : [], + votingThresholdYes: Number(tallyParams.threshold).toFixed(2), + votingThresholdNo: (1 - tallyParams.threshold).toFixed(2), votingPercentageYes: percentage(tally.yes, totalVotingParticipation), votingPercentageNo: percentage( BigNumber(tally.no).plus(tally.no_with_veto), totalVotingParticipation ), + tally: tally, timeline: [ proposal.submit_time ? { title: `Created`, time: proposal.submit_time } : undefined, proposal.deposit_end_time ? { - title: `Deposit Period Ends`, - // the deposit period can end before the time as the limit is reached already - time: - proposal.voting_start_time !== GOLANG_NULL_TIME && + title: `Deposit Period Ends`, + // the deposit period can end before the time as the limit is reached already + time: + proposal.voting_start_time !== GOLANG_NULL_TIME && new Date(proposal.voting_start_time) < - new Date(proposal.deposit_end_time) - ? proposal.voting_start_time - : proposal.deposit_end_time, - } - : undefined, + new Date(proposal.deposit_end_time) + ? proposal.voting_start_time + : proposal.deposit_end_time, + } + : [], proposal.voting_start_time ? { - title: `Voting Period Starts`, - time: - proposal.voting_start_time !== GOLANG_NULL_TIME - ? proposal.voting_start_time - : undefined, - } - : undefined, + title: `Voting Period Starts`, + time: + proposal.voting_start_time !== GOLANG_NULL_TIME + ? proposal.voting_start_time + : [], + } + : [], proposal.voting_end_time ? { - title: `Voting Period Ends`, - time: - proposal.voting_end_time !== GOLANG_NULL_TIME - ? proposal.voting_end_time - : undefined, - } - : undefined, + title: `Voting Period Ends`, + time: + proposal.voting_end_time !== GOLANG_NULL_TIME + ? proposal.voting_end_time + : [], + } + : [], ].filter((x) => !!x), } } // we can't query the proposer of blocks from past chains - async getProposer(proposal, firstBlock) { + async getProposer(proposal) { let proposer = { proposer: undefined } - const proposalIsFromPastChain = - proposal.voting_end_time !== GOLANG_NULL_TIME && - new Date(firstBlock.time) > new Date(proposal.voting_end_time) - if (!proposalIsFromPastChain) { - proposer = await this.query(`gov/proposals/${proposal.id}/proposer`) + const proposalExistsOnCurrentChain = this.firstBlock.chainId == this.network.chainId + if (!proposalExistsOnCurrentChain) { + proposer = await this.query( + `/cosmos/gov/v1beta1/proposals/${proposal.proposal_id}/proposer` + ) } return proposer } - async getProposalMetaData(proposal, firstBlock) { - const [tally, detailedVotes, proposer] = await Promise.all([ - this.query(`gov/proposals/${proposal.id}/tally`), - this.getDetailedVotes(proposal), - this.getProposer(proposal, firstBlock), - ]) - return [tally, detailedVotes, proposer] - } - async getProposals() { await this.dataReady const [ proposalsResponse, - firstBlock, - { bonded_tokens: totalBondedTokens }, + pool, + tallyParams, + depositParams, ] = await Promise.all([ - this.query('gov/proposals'), - this.getBlock(1), - this.query('/staking/pool'), + this.queryAutoPaginate('cosmos/gov/v1beta1/proposals'), + this.query('cosmos/staking/v1beta1/pool'), + this.query(`/cosmos/gov/v1beta1/params/tallying`), + this.query(`/cosmos/gov/v1beta1/params/deposit`) ]) if (!Array.isArray(proposalsResponse)) return [] + const proposals = await Promise.all( proposalsResponse.map(async (proposal) => { - const [tally, detailedVotes, proposer] = await this.getProposalMetaData( + const detailedVotes = await this.getDetailedVotes( proposal, - firstBlock + tallyParams, + depositParams, ) return this.reducers.proposalReducer( proposal, - tally, - proposer, - totalBondedTokens, + pool.bonded_tokens, detailedVotes, - this.validators ) }) ) @@ -353,35 +358,6 @@ export default class CosmosAPI { return orderBy(proposals, 'id', 'desc') } - async getProposal(proposalId) { - await this.dataReady - const [ - proposal, - { bonded_tokens: totalBondedTokens }, - firstBlock, - ] = await Promise.all([ - this.query(`gov/proposals/${proposalId}`).catch(() => { - throw new Error( - `There is no proposal in the network with ID '${proposalId}'` - ) - }), - this.query(`/staking/pool`), - this.getBlock(1), - ]) - const [tally, detailedVotes, proposer] = await this.getProposalMetaData( - proposal, - firstBlock - ) - return this.reducers.proposalReducer( - proposal, - tally, - proposer, - totalBondedTokens, - detailedVotes, - this.validators - ) - } - async getTopVoters() { await this.dataReady // for now defaulting to pick the 10 largest voting powers @@ -398,28 +374,31 @@ export default class CosmosAPI { } async getGovernanceOverview() { - const { bonded_tokens: totalBondedTokens } = await this.query( - '/staking/pool' - ) - const [communityPoolArray, topVoters] = await Promise.all([ - this.query('/distribution/community_pool'), + + const [pool, communityPoolArray, topVoters] = await Promise.all([ + this.query('cosmos/staking/v1beta1/pool'), + this.query('cosmos/distribution/v1beta1/community_pool'), this.getTopVoters(), ]) - const stakingChainDenom = this.network.getCoinLookup( + + const stakingCoin = this.network.getCoinLookup( this.network.stakingDenom, 'viewDenom' - ).chainDenom - const communityPool = communityPoolArray.find( + ) + + const stakingChainDenom = stakingCoin.chainDenom + + const communityPool = communityPoolArray.pool.find( ({ denom }) => denom === stakingChainDenom - ).amount + ) return { totalStakedAssets: setDecimalLength( - reducers.getStakingCoinViewAmount(totalBondedTokens), + reducers.getStakingCoinViewAmount(pool.pool.bonded_tokens), 2 ), totalVoters: undefined, treasurySize: setDecimalLength( - reducers.getStakingCoinViewAmount(communityPool), + reducers.getStakingCoinViewAmount(communityPool.amount), 2 ), topVoters: topVoters.map((topVoter) => @@ -429,7 +408,9 @@ export default class CosmosAPI { } async getDelegatorVote({ proposalId, address }) { - const response = await this.query(`gov/proposals/${proposalId}/votes`) + const response = await this.query( + `cosmos/gov/v1beta1/proposals/${proposalId}/votes` + ) const votes = response || [] const vote = votes.find(({ voter }) => voter === address) || {} return { @@ -449,7 +430,7 @@ export default class CosmosAPI { async getBalances(address) { const [balancesResponse, delegations, undelegations] = await Promise.all([ - this.query(`bank/balances/${address}`), + this.queryAutoPaginate(`cosmos/bank/v1beta1/balances/${address}`), this.getDelegationsForDelegator(address), this.getUndelegationsForDelegator(address), ]) @@ -486,6 +467,7 @@ export default class CosmosAPI { const hasStakingDenom = coins.find( ({ denom }) => denom === this.network.stakingDenom ) + if (!hasStakingDenom) { coins.push({ amount: BigNumber(0), @@ -521,9 +503,13 @@ export default class CosmosAPI { async getDelegationsForDelegator(address) { await this.dataReady - const delegations = - (await this.query(`staking/delegators/${address}/delegations`)) || [] - return delegations + + const delegations = await this.queryAutoPaginate( + `cosmos/staking/v1beta1/delegations/${address}` + ).catch(console.log) || [] + + console.log(delegations) + return delegations.length ? delegations .map((delegation) => this.reducers.delegationReducer( delegation, @@ -531,14 +517,14 @@ export default class CosmosAPI { delegationEnum.ACTIVE ) ) - .filter((delegation) => BigNumber(delegation.amount).gt(0)) + .filter((delegation) => BigNumber(delegation.amount).gt(0)) : [] } async getUndelegationsForDelegator(address) { await this.dataReady const undelegations = - (await this.query( - `staking/delegators/${address}/unbonding_delegations` + (await this.queryAutoPaginate( + `cosmos/staking/v1beta1/delegators/${address}/unbonding_delegations` )) || [] // undelegations come in a nested format { validator_address, delegator_address, entries } @@ -582,14 +568,18 @@ export default class CosmosAPI { } async getAnnualProvision() { - const response = await this.query(`minting/annual-provisions`) - return response + const response = await this.query(`cosmos/mint/v1beta1/annual_provisions`) + return response.annual_provisions || undefined + } + + async getPool() { + return await this.query(`cosmos/staking/v1beta1/pool`) } async getRewards(delegatorAddress) { await this.dataReady const result = await this.query( - `distribution/delegators/${delegatorAddress}/rewards` + `cosmos/distribution/v1beta1/delegators/${delegatorAddress}/rewards` ) const rewards = (result.rewards || []).filter( ({ reward }) => reward && reward.length > 0 diff --git a/assets/images/currencies/muon.png b/assets/images/currencies/atom.png similarity index 100% rename from assets/images/currencies/muon.png rename to assets/images/currencies/atom.png diff --git a/common/b64.js b/common/b64.js new file mode 100644 index 00000000..5a957de0 --- /dev/null +++ b/common/b64.js @@ -0,0 +1,5 @@ +module.exports = { + urlSafeEncode(value) { + return value.replaceAll('+', '_').replaceAll('/', '-') + }, +} diff --git a/common/network.js b/common/network.js index 2a5003e0..6bb81e72 100644 --- a/common/network.js +++ b/common/network.js @@ -6,6 +6,8 @@ export default { // utility functions // TODO put in a wrapper outside this file getCoinLookup(denom, coinLookupDenomType = `chainDenom`) { - return this.coinLookup.find((coin) => coin[coinLookupDenomType] === denom) + return network.coinLookup.find( + (coin) => coin[coinLookupDenomType] === denom + ) }, } diff --git a/common/proposal-status.js b/common/proposal-status.js index bee666c8..ab9e6121 100644 --- a/common/proposal-status.js +++ b/common/proposal-status.js @@ -1,34 +1,34 @@ export const governanceStatusEnum = { + DEPOSITING: 'DEPOSIT', + VOTING: 'VOTING', PASSED: 'PASSED', REJECTED: 'REJECTED', - DEPOSITING: 'DEPOSITING', - VOTING: 'VOTING', - UNKNOWN: 'UNKNOWN', + UNKNOWN: 'FAILED', } export const getProposalStatus = (proposal) => { switch (proposal.status) { - case `Passed`: + case `PROPOSAL_STATUS_PASSED`: return { title: `Passed`, value: governanceStatusEnum.PASSED, color: `green`, active: true, } - case `Rejected`: + case `PROPOSAL_STATUS_REJECTED`: return { title: `Rejected`, value: governanceStatusEnum.REJECTED, color: `red`, active: false, } - case `DepositPeriod`: + case `PROPOSAL_STATUS_DEPOSIT_PERIOD`: return { title: `Deposit Period`, value: governanceStatusEnum.DEPOSITING, color: `orange`, } - case `VotingPeriod`: + case `PROPOSAL_STATUS_VOTING_PERIOD`: return { title: `Voting Period`, value: governanceStatusEnum.VOTING, diff --git a/components/ActionModals/ActionModal.vue b/components/Modal/Action.vue similarity index 95% rename from components/ActionModals/ActionModal.vue rename to components/Modal/Action.vue index e734ce17..f31aebe1 100644 --- a/components/ActionModals/ActionModal.vue +++ b/components/Modal/Action.vue @@ -18,30 +18,30 @@ close {{ title }} -
- +
You're in explore mode
Sign in with a Ledger Nano or browser extension to proceed.
-
+
- - - - - + - {{ getExternalSessionMessage(session.sessionType) }}
- +
- +
Sent and confirming
Waiting for confirmation from {{ network.name }}.
-
+
- +
{{ notifyMessage.title }}
{{ notifyMessage.body }} @@ -103,28 +103,28 @@
See your transaction
-
+
-
{{ submissionErrorPrefix }}
{{ submissionError }}
-
+