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

Proposals and Votes POC #4

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
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
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ dist-ssr
*.local
node_modules/*
stats.html

.idea
### VisualStudioCode ###
.vscode/**/*
!.vscode/settings.suggested.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.idea

3 changes: 2 additions & 1 deletion makefile
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ local-keys:
local-init: local-clean local-keys
simd init $(MONIKER) --chain-id $(CHAIN_ID) --home $(CHAIN_HOME)
simd add-genesis-account alice 10000000000000000000000001stake --home $(CHAIN_HOME) --keyring-backend test
simd add-genesis-account machete 0stake --home $(CHAIN_HOME) --keyring-backend test
simd gentx alice 1000000000stake --chain-id $(CHAIN_ID) --home $(CHAIN_HOME) --keyring-backend test --keyring-dir $(CHAIN_HOME)
simd collect-gentxs --home $(CHAIN_HOME)
$(sed) "s/prometheus = false/prometheus = true/" $(CHAIN_HOME)/config/config.toml
Expand All @@ -61,7 +62,7 @@ local-init: local-clean local-keys
local-start:
#simd start --home $(CHAIN_HOME) --grpc-web.enable true --grpc-web.address 0.0.0.0:9091
# simd start --mode validator --home $(CHAIN_HOME)
simd start --home $(CHAIN_HOME)
simd start --home $(CHAIN_HOME) --log_level debug

.PHONY: query-balance
query-balance:
Expand Down
27 changes: 27 additions & 0 deletions src/api/bank.messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { MsgSend } from '@haveanicedavid/cosmos-groups-ts/types/codegen/cosmos/bank/v1beta1/tx'
import { Coin } from '@haveanicedavid/cosmos-groups-ts/types/codegen/cosmos/base/v1beta1/coin'
import Long from 'long'

import type { GroupWithPolicyFormValues, UIGroupMetadata } from 'types'
import { clearEmptyStr } from 'util/helpers'

import { BankSendType } from '../types/bank.types'

import { MsgBankWithTypeUrl, MsgWithTypeUrl } from './cosmosgroups'
import { encodeDecisionPolicy } from './policy.messages'

export function updateGroupMetadataMsg({
admin,
metadata,
groupId,
}: {
admin: string
groupId: string
metadata: UIGroupMetadata
}) {
return MsgWithTypeUrl.updateGroupMetadata({
admin,
group_id: Long.fromString(groupId),
metadata: JSON.stringify(metadata),
})
}
1 change: 1 addition & 0 deletions src/api/cosmosgroups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ import { cosmos } from '@haveanicedavid/cosmos-groups-ts'

export const v1 = cosmos.group.v1
export const MsgWithTypeUrl = cosmos.group.v1.MessageComposer.withTypeUrl
export const MsgBankWithTypeUrl = cosmos.bank.v1beta1.MessageComposer.withTypeUrl
8 changes: 7 additions & 1 deletion src/api/group.actions.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { MsgSend } from '@haveanicedavid/cosmos-groups-ts/types/codegen/cosmos/bank/v1beta1/tx'
import { Coin } from '@haveanicedavid/cosmos-groups-ts/types/codegen/cosmos/base/v1beta1/coin'
import Long from 'long'

import { type GroupWithPolicyFormValues, type UIGroup } from 'types'
import { throwError } from 'util/errors'

import { Group, signAndBroadcast } from 'store'
import { Group, signAndBroadcast, Wallet } from 'store'

import { BankSendType } from '../types/bank.types'

import { MsgBankWithTypeUrl } from './cosmosgroups'
import { createGroupWithPolicyMsg } from './group.messages'
import { addMembersToGroups, toUIGroup } from './group.utils'
import { fetchGroupPolicies } from './policy.actions'

export async function createGroupWithPolicy(values: GroupWithPolicyFormValues) {
try {
Expand Down
8 changes: 6 additions & 2 deletions src/api/group.messages.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { MsgSend } from '@haveanicedavid/cosmos-groups-ts/types/codegen/cosmos/bank/v1beta1/tx'
import { Coin } from '@haveanicedavid/cosmos-groups-ts/types/codegen/cosmos/base/v1beta1/coin'
import Long from 'long'

import type { GroupWithPolicyFormValues, UIGroupMetadata } from 'types'
import { clearEmptyStr } from 'util/helpers'

import { MsgWithTypeUrl } from './cosmosgroups'
import { MsgBankWithTypeUrl, MsgWithTypeUrl } from './cosmosgroups'
import { encodeDecisionPolicy } from './policy.messages'

export function createGroupWithPolicyMsg(values: GroupWithPolicyFormValues) {
Expand All @@ -19,7 +21,7 @@ export function createGroupWithPolicyMsg(values: GroupWithPolicyFormValues) {
threshold,
votingWindow,
} = values
return MsgWithTypeUrl.createGroupWithPolicy({
const groupPolicyResponse = MsgWithTypeUrl.createGroupWithPolicy({
admin,
group_policy_metadata: '',
group_policy_as_admin: policyAsAdmin === 'true',
Expand All @@ -41,6 +43,8 @@ export function createGroupWithPolicyMsg(values: GroupWithPolicyFormValues) {
metadata: JSON.stringify(m.metadata),
})),
})

return groupPolicyResponse
}

export function updateGroupMetadataMsg({
Expand Down
105 changes: 105 additions & 0 deletions src/api/proposal.actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { QueryProposalsByGroupPolicyResponse } from '@haveanicedavid/cosmos-groups-ts/types/codegen/cosmos/group/v1/query'
import {
Proposal,
Vote,
} from '@haveanicedavid/cosmos-groups-ts/types/codegen/cosmos/group/v1/types'
import Long from 'long'

import { throwError } from 'util/errors'

import { Group, signAndBroadcast } from 'store'

import { ProposalFormValues } from '@/organisms/proposal-form'
import { VoteFormValues } from '@/organisms/vote-form'

import { UIGroup } from '../types'
import { ProposalExecMsg } from '../types/proposal.types'

import { toUIGroup } from './group.utils'
import { createProposalMsg, execProposalMsg } from './proposal.messages'
import { createVoteMsg } from './vote.messages'

export async function createProposal(values: ProposalFormValues) {
try {
const msg = createProposalMsg(values)
const data = await signAndBroadcast([msg])
let proposalId
if (data.rawLog) {
const [raw] = JSON.parse(data.rawLog)
const idRaw = raw.events[0].attributes[0].value
proposalId = JSON.parse(idRaw)
}
return { ...data, proposalId }
} catch (error) {
throwError(error)
}
}

export async function voteProposal(values: VoteFormValues) {
try {
const msg = createVoteMsg(values)
const data = await signAndBroadcast([msg])
if (data.code !== 0) {
throwError(new Error(data.rawLog))
}
return data
} catch (error) {
throwError(error)
}
}

export async function execProposal(values: ProposalExecMsg) {
try {
const msg = execProposalMsg(values)
const data = await signAndBroadcast([msg])
if (data.code !== 0) {
throwError(new Error(data.rawLog))
}
return data
} catch (error) {
throwError(error)
}
}

export async function fetchProposalById(proposalId?: string | Long): Promise<Proposal> {
if (!Group.query) throwError('Wallet not initialized')
if (!proposalId) throwError('proposalId is required')
try {
const { proposal } = await Group.query.proposal({
proposal_id: proposalId instanceof Long ? proposalId : Long.fromString(proposalId),
})
return proposal
} catch (error) {
throwError(error)
}
}

export async function fetchProposalVotesById(
proposalId?: string | Long,
): Promise<Vote[]> {
if (!Group.query) throwError('Wallet not initialized')
if (!proposalId) throwError('proposalId is required')
try {
const { votes } = await Group.query.votesByProposal({
proposal_id: proposalId instanceof Long ? proposalId : Long.fromString(proposalId),
})
return votes
} catch (error) {
throwError(error)
}
}

export async function fetchProposalsByPolicyAddr(
policyAddress: string,
): Promise<QueryProposalsByGroupPolicyResponse> {
if (!Group.query) throwError('Wallet not initialized')
if (!policyAddress) throwError('policyAddress is required')
try {
const result = await Group.query.proposalsByGroupPolicy({
address: policyAddress,
})
return result
} catch (error) {
throwError(error)
}
}
49 changes: 49 additions & 0 deletions src/api/proposal.messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { cosmos } from '@haveanicedavid/cosmos-groups-ts'
import { MsgSend } from '@haveanicedavid/cosmos-groups-ts/types/codegen/cosmos/bank/v1beta1/tx'
import { Coin } from '@haveanicedavid/cosmos-groups-ts/types/codegen/cosmos/base/v1beta1/coin'
import {
MsgExec,
MsgSubmitProposal,
} from '@haveanicedavid/cosmos-groups-ts/types/codegen/cosmos/group/v1/tx'
import { Any } from '@haveanicedavid/cosmos-groups-ts/types/codegen/google/protobuf/any'

import { ProposalFormValues } from '@/organisms/proposal-form'

import { ProposalExecMsg } from '../types/proposal.types'

import { MsgWithTypeUrl, v1 } from './cosmosgroups'

export function createProposalMsg(values: ProposalFormValues) {
const { group_policy_address, metadata, proposers, Exec } = values
const coin: Coin = {
denom: 'stake',
amount: values.amount.toString(),
}
const msgSend: MsgSend = {
from_address: values.group_policy_address,
to_address: values.msgToAddr,
amount: [coin],
}

const message: MsgSubmitProposal = {
messages: [
{
value: cosmos.bank.v1beta1.MsgSend.encode(msgSend).finish(),
type_url: '/cosmos.bank.v1beta1.MsgSend',
},
], // For demo purposes, proposal have empty messages.
group_policy_address: group_policy_address,
metadata: metadata,
proposers: proposers.map((elm) => elm.address),
exec: Exec,
}
return MsgWithTypeUrl.submitProposal(message)
}

export function execProposalMsg(values: ProposalExecMsg) {
const message: MsgExec = {
proposal_id: values.proposal_id,
executor: values.executor,
}
return MsgWithTypeUrl.exec(message)
}
21 changes: 21 additions & 0 deletions src/api/vote.messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {
MsgSubmitProposal,
MsgVote,
} from '@haveanicedavid/cosmos-groups-ts/types/codegen/cosmos/group/v1/tx'

import { ProposalFormValues } from '@/organisms/proposal-form'
import { VoteFormValues } from '@/organisms/vote-form'

import { MsgWithTypeUrl } from './cosmosgroups'

export function createVoteMsg(values: VoteFormValues) {
const { proposal_id, metadata, option, exec, voter } = values
const message: MsgVote = {
voter: voter,
proposal_id: proposal_id,
metadata: metadata,
option: option,
exec: exec,
}
return MsgWithTypeUrl.vote(message)
}
55 changes: 55 additions & 0 deletions src/components/organisms/group-proposals-table.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useMemo } from 'react'
import { Proposal } from '@haveanicedavid/cosmos-groups-ts/types/codegen/cosmos/group/v1/types'

import { useBoolean, useBreakpointValue, useColorModeValue } from 'hooks/chakra'

import { Link, Table, TableContainer, Tbody, Td, Th, Thead, Tr } from '@/atoms'
import { TableTitlebar, Truncate } from '@/molecules'

export const GroupProposalsTable = ({ proposals }: { proposals: Proposal[] }) => {
const [isEdit, setEdit] = useBoolean(false)
const tableProposals: Proposal[] = useMemo(() => {
const proposalsVals = proposals.map((p) => p)
return [...proposalsVals]
}, [proposals])
const tailSize = useBreakpointValue({ base: 4, sm: 6, md: 25, lg: 35, xl: 100 })
return (
<TableContainer w="full" borderRadius="lg" borderWidth={2} shadow="md">
<TableTitlebar title="Proposals"></TableTitlebar>
<Table size="lg" variant={isEdit ? undefined : 'striped'}>
<Thead>
<Tr sx={{ '& > th': { fontWeight: 'bold' } }}>
<Th>Proposal</Th>
<Th>Status</Th>
<Th>Policy Address</Th>
</Tr>
</Thead>
<Tbody>
{tableProposals.map((proposal, i) => {
const key = proposal.id.toString()
return (
<Tr key={key}>
<Td>
<Link to={`/proposals/${proposal.id}/details`}>
<Truncate
tailLength={tailSize}
text={`Proposal ID: ${proposal.id}`}
/>
</Link>
</Td>
<Td>{proposal.status}</Td>
<Td>
{' '}
<Truncate
tailLength={tailSize}
text={proposal.group_policy_address}
/>{' '}
</Td>
</Tr>
)
})}
</Tbody>
</Table>
</TableContainer>
)
}
Loading