Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display executed proposal TX hashes with link to Mintscan #441

Merged
merged 7 commits into from
Mar 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/dapp/.env.mainnet
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ NEXT_PUBLIC_DAO_DAO_VERSION="0.3.0"
# The prefix added to keys for favorited DAOs in local storage.
NEXT_PUBLIC_FAVORITES_NAMESPACE=43
NEXT_PUBLIC_GAS_PRICE=0.0025ujuno
NEXT_PUBLIC_CHAIN_TXN_URL_PREFIX=https://mintscan.io/juno/txs/

# Contracts / Codes
NEXT_PUBLIC_CW20_CODE_ID=37
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,45 +17,59 @@ function concatAddress(address: string) {
return concatAddressImpl(address, takeN, takeN)
}

export function Address({ address }: { address: string }) {
interface CopyToClipboardProps {
value: string
success?: string
}

export function CopyToClipboard({
value,
success = 'Copied to clipboard!',
}: CopyToClipboardProps) {
return (
<button
className="btn btn-sm btn-outline normal-case border-base-300 shadow w-36 font-normal rounded-md px-1 font-mono text-xs"
onClick={() => {
navigator.clipboard.writeText(address)
toast.success('Copied address to clipboard!')
navigator.clipboard.writeText(value)
toast.success(success)
}}
>
{concatAddress(address)}
{concatAddress(value)}
</button>
)
}

export function AddressSmall({ address }: { address: string }) {
export function CopyToClipboardSmall({
value,
success = 'Copied to clipboard!',
}: CopyToClipboardProps) {
return (
<button
className="transition font-sm font-mono text-sm text-secondary hover:text-primary"
onClick={() => {
navigator.clipboard.writeText(address)
toast.success('Copied address to clipboard!')
navigator.clipboard.writeText(value)
toast.success(success)
}}
>
<PaperClipIcon className="w-4 h-4 inline mr-1" />
{concatAddressImpl(address, 12, 7)}
{concatAddressImpl(value, 12, 7)}
</button>
)
}

export function AddressAccent({ address }: { address: string }) {
export function CopyToClipboardAccent({
value,
success = 'Copied to clipboard!',
}: CopyToClipboardProps) {
return (
<button
className="transition text-sm text-accent hover:underline"
onClick={() => {
navigator.clipboard.writeText(address)
toast.success('Copied address to clipboard!')
navigator.clipboard.writeText(value)
toast.success(success)
}}
>
{concatAddressImpl(address, 12, 7)}
{concatAddressImpl(value, 12, 7)}
</button>
)
}
12 changes: 5 additions & 7 deletions apps/dapp/components/DaoContractInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { ReactNode } from 'react'

import { useRecoilValue, waitForAll } from 'recoil'
import { useRecoilValue } from 'recoil'

import { CashIcon, ChartPieIcon } from '@heroicons/react/outline'

Expand All @@ -15,8 +13,8 @@ import {
getThresholdAndQuorumDisplay,
} from 'util/conversion'

import { AddressAccent } from './Address'
import { GovInfoListItem, TreasuryBalances } from './ContractView'
import { CopyToClipboardAccent } from './CopyToClipboard'
import SvgVotes from './icons/Votes'

export function DaoContractInfo({ address }: { address: string }) {
Expand Down Expand Up @@ -76,13 +74,13 @@ export function DaoContractInfo({ address }: { address: string }) {
<h2 className="font-medium text-lg mb-6">Addresses</h2>
<ul className="list-none ml-2 mt-3 flex flex-col gap-2 text-secondary text-sm">
<li>
DAO <AddressAccent address={address} />
DAO <CopyToClipboardAccent value={address} />
</li>
<li>
Gov token <AddressAccent address={daoInfo.gov_token} />
Gov token <CopyToClipboardAccent value={daoInfo.gov_token} />
</li>
<li>
Staking <AddressAccent address={daoInfo.staking_contract} />
Staking <CopyToClipboardAccent value={daoInfo.staking_contract} />
</li>
</ul>
</div>
Expand Down
7 changes: 4 additions & 3 deletions apps/dapp/components/MultisigContractInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { ClockIcon } from '@heroicons/react/outline'
import { sigSelector } from 'selectors/multisigs'
import { humanReadableDuration, thresholdString } from 'util/conversion'

import { AddressAccent } from './Address'
import { GovInfoListItem, TreasuryBalances } from './ContractView'
import { CopyToClipboardAccent } from './CopyToClipboard'
import SvgVotes from './icons/Votes'

export function MultisigContractInfo({ address }: { address: string }) {
Expand All @@ -33,10 +33,11 @@ export function MultisigContractInfo({ address }: { address: string }) {
<h2 className="font-medium text-lg mb-6">Addresses</h2>
<ul className="list-none mt-3 flex flex-col gap-2 text-secondary text-sm">
<li>
Multisig address <AddressAccent address={address} />
Multisig address <CopyToClipboardAccent value={address} />
</li>
<li>
cw4-group address <AddressAccent address={sigInfo.group_address} />
cw4-group address{' '}
<CopyToClipboardAccent value={sigInfo.group_address} />
</li>
</ul>
</div>
Expand Down
53 changes: 47 additions & 6 deletions apps/dapp/components/ProposalDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@ import {
SetterOrUpdater,
useRecoilState,
useRecoilValue,
useRecoilValueLoadable,
useSetRecoilState,
} from 'recoil'

import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'
import { CheckIcon, SparklesIcon, XIcon } from '@heroicons/react/outline'
import {
CheckIcon,
ExternalLinkIcon,
SparklesIcon,
XIcon,
} from '@heroicons/react/outline'
import toast from 'react-hot-toast'

import { ProposalStatus } from '@components'
Expand All @@ -26,6 +32,7 @@ import {
} from 'selectors/cosm'
import { isMemberSelector } from 'selectors/daos'
import {
proposalExecutionTXHashSelector,
proposalSelector,
proposalStartBlockSelector,
proposalTallySelector,
Expand All @@ -34,19 +41,19 @@ import {
} from 'selectors/proposals'
import { walletTokenBalanceLoading } from 'selectors/treasury'
import { cleanChainError } from 'util/cleanChainError'
import { CHAIN_TXN_URL_PREFIX } from 'util/constants'
import {
contractConfigSelector,
ContractConfigWrapper,
} from 'util/contractConfigWrapper'
import {
convertMicroDenomToDenomWithDecimals,
getThresholdAndQuorumDisplay,
thresholdString,
} from 'util/conversion'
import { decodedMessagesString, decodeMessages } from 'util/messagehelpers'

import { treasuryTokenListUpdates } from '../atoms/treasury'
import { Address } from './Address'
import { CopyToClipboard } from './CopyToClipboard'
import { CosmosMessageDisplay } from './CosmosMessageDisplay'
import { getEnd } from './ProposalList'

Expand Down Expand Up @@ -108,7 +115,9 @@ function executeProposalExecute(
'auto'
)
.then((response) => {
toast.success(`Success. Transaction hash: (${response.transactionHash})`)
toast.success(
`Success. Transaction hash (${response.transactionHash}) can be found in the sidebar.`
)
})
.catch((err) => {
console.error(err)
Expand Down Expand Up @@ -308,6 +317,12 @@ export function ProposalDetailsSidebar({
const proposalTally = useRecoilValue(
proposalTallySelector({ contractAddress, proposalId })
)
const { state: proposalExecutionTXHashState, contents: txHashContents } =
useRecoilValueLoadable(
proposalExecutionTXHashSelector({ contractAddress, proposalId })
)
const proposalExecutionTXHash: string | null =
proposalExecutionTXHashState === 'hasValue' ? txHashContents : null

const sigConfig = useRecoilValue(
contractConfigSelector({ contractAddress, multisig: !!multisig })
Expand Down Expand Up @@ -370,15 +385,41 @@ export function ProposalDetailsSidebar({
<h2 className="font-medium text-sm font-mono mb-8 text-secondary">
Proposal {proposal.id}
</h2>
<div className="grid grid-cols-3">
<div className="grid grid-cols-3 gap-1 items-center">
<p className="text-secondary">Status</p>
<div className="col-span-2">
<ProposalStatus status={proposal.status} />
</div>
<p className="text-secondary">Proposer</p>
<p className="col-span-2">
<Address address={proposal.proposer} />
<CopyToClipboard value={proposal.proposer} />
</p>
{proposal.status === 'executed' &&
proposalExecutionTXHashState === 'loading' ? (
<>
<p className="text-secondary">TX</p>
<p className="col-span-2">Loading...</p>
</>
) : !!proposalExecutionTXHash ? (
<>
{CHAIN_TXN_URL_PREFIX ? (
<a
className="text-secondary flex flex-row items-center gap-1"
target="_blank"
rel="noopener noreferrer"
href={CHAIN_TXN_URL_PREFIX + proposalExecutionTXHash}
>
TX
<ExternalLinkIcon width={16} />
</a>
) : (
<p className="text-secondary">TX</p>
)}
<p className="col-span-2">
<CopyToClipboard value={proposalExecutionTXHash} />
</p>
</>
) : null}
{proposal.status === 'open' && (
<>
<p className="text-secondary">Expires</p>
Expand Down
7 changes: 3 additions & 4 deletions apps/dapp/components/ProposalVotes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import { useEffect, useState } from 'react'
import { useRecoilValue, useRecoilValueLoadable } from 'recoil'

import { VoteInfo } from '@dao-dao/types/contracts/cw3-dao'
import { DownloadIcon, UserIcon } from '@heroicons/react/outline'
import { DownloadIcon } from '@heroicons/react/outline'

import { Address } from 'components/Address'
import { walletAddress as selectWalletAddress } from 'selectors/cosm'
import { CopyToClipboard } from '@components/CopyToClipboard'
import {
proposalTallySelector,
proposalVotesSelector,
Expand Down Expand Up @@ -59,7 +58,7 @@ export function PaginatedProposalVotes({
{votes.map((vote, idx) => {
return (
<li className="grid grid-cols-3 items-center" key={idx}>
<Address address={vote.voter} />
<CopyToClipboard value={vote.voter} />
<span className="font-mono text-sm">{vote.weight}</span>
<span>{vote.vote}</span>
</li>
Expand Down
36 changes: 35 additions & 1 deletion apps/dapp/selectors/proposals.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { selectorFamily } from 'recoil'

import {
ProposalResponse,
ProposalTallyResponse,
Expand All @@ -14,7 +16,6 @@ import {
EmptyProposalTallyResponse,
EmptyThresholdResponse,
} from 'models/proposal/proposal'
import { selectorFamily } from 'recoil'
import {
ContractProposalMap,
ExtendedProposalResponse,
Expand All @@ -23,6 +24,7 @@ import {
ProposalMapItem,
ProposalMessageKey,
} from 'types/proposals'

import { cosmWasmClient } from './cosm'
import { daoSelector } from './daos'
import { sigSelector } from './multisigs'
Expand Down Expand Up @@ -174,6 +176,38 @@ export const walletVotedSelector = selectorFamily<
},
})

export const proposalExecutionTXHashSelector = selectorFamily<
string | null,
{ contractAddress: string; proposalId: number }
>({
key: 'proposalTXHashSelector',
get:
({ contractAddress, proposalId }) =>
async ({ get }) => {
// Refresh when new updates occur.
get(proposalUpdateCountAtom({ contractAddress, proposalId }))

const client = get(cosmWasmClient)
const proposal = get(proposalSelector({ contractAddress, proposalId }))
// No TX Hash if proposal not yet executed.
if (!client || proposal?.status !== 'executed') return null

const events = await client.searchTx({
tags: [
{ key: 'wasm._contract_address', value: contractAddress },
{ key: 'wasm.proposal_id', value: proposalId.toString() },
{ key: 'wasm.action', value: 'execute' },
NoahSaso marked this conversation as resolved.
Show resolved Hide resolved
],
})

if (events.length > 1) {
console.error('More than one execution', events)
}

return events.length > 0 ? events[0].hash : null
},
})

export const proposalVotesSelector = selectorFamily<
VoteInfo[],
{ contractAddress: string; proposalId: number; startAfter?: string }
Expand Down
1 change: 1 addition & 0 deletions apps/dapp/util/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,4 @@ export const STATUS_COLORS: { [key: string]: string } = {
}

export const CHAIN_ID = process.env.NEXT_PUBLIC_CHAIN_ID as string
export const CHAIN_TXN_URL_PREFIX = process.env.NEXT_PUBLIC_CHAIN_TXN_URL_PREFIX