From aa78a70301a4213cdf7326616c6f760af285acba Mon Sep 17 00:00:00 2001 From: Jeff Date: Tue, 11 Jun 2024 19:15:22 +0300 Subject: [PATCH 1/5] community spend proposal #51 --- src/components/ProposalForm.tsx | 117 ++++++++++++------- src/config/agoric/agoric.tsx | 199 ++++++++++++++++++-------------- src/index.d.ts | 17 +-- src/lib/messageBuilder.ts | 85 ++++++++++---- 4 files changed, 260 insertions(+), 158 deletions(-) diff --git a/src/components/ProposalForm.tsx b/src/components/ProposalForm.tsx index bac09f6..254c3e7 100644 --- a/src/components/ProposalForm.tsx +++ b/src/components/ProposalForm.tsx @@ -5,16 +5,16 @@ import { forwardRef, FormEvent, ReactNode, -} from "react"; -import { CodeInputGroup } from "./CodeInputGroup"; -import { CoreEval } from "@agoric/cosmic-proto/swingset/swingset.js"; -import { Button } from "./Button"; -import { ParamChange } from "cosmjs-types/cosmos/params/v1beta1/params"; -import { ParameterChangeFormSection } from "./ParameterChangeForm"; -import { DepositSection } from "./DepositSection"; -import { paramOptions } from "../config/agoric/params"; -import type { ParameterChangeTypeOption } from "../types/form"; -import { TitleDescriptionInputs } from "./TitleDescriptionInputs"; +} from 'react'; +import { CodeInputGroup } from './CodeInputGroup'; +import { CoreEval } from '@agoric/cosmic-proto/swingset/swingset.js'; +import { Button } from './Button'; +import { ParamChange } from 'cosmjs-types/cosmos/params/v1beta1/params'; +import { ParameterChangeFormSection } from './ParameterChangeForm'; +import { DepositSection } from './DepositSection'; +import { paramOptions } from '../config/agoric/params'; +import type { ParameterChangeTypeOption } from '../types/form'; +import { TitleDescriptionInputs } from './TitleDescriptionInputs'; type BaseProposalArgs = { title: string; @@ -24,22 +24,27 @@ type BaseProposalArgs = { export type ProposalArgs = BaseProposalArgs & ProposalDetail; -export type QueryType = ReturnType<(typeof paramOptions)[number]["query"]>; +export type QueryType = ReturnType<(typeof paramOptions)[number]['query']>; export type SelectorReturnType = ReturnType< - (typeof paramOptions)[number]["selector"] + (typeof paramOptions)[number]['selector'] >; export type ProposalDetail = - | { msgType: "textProposal" } - | { msgType: "coreEvalProposal"; evals: CoreEval[] } - | { msgType: "parameterChangeProposal"; changes: ParamChange[] }; + | { msgType: 'textProposal' } + | { msgType: 'coreEvalProposal'; evals: CoreEval[] } + | { msgType: 'parameterChangeProposal'; changes: ParamChange[] } + | { + msgType: 'communityPoolSpendProposal'; + recipient: string; + amount: string; + }; interface ProposalFormProps { title: string; description: string | ReactNode; handleSubmit: (proposal: ProposalArgs) => void; titleDescOnly?: boolean; - msgType: QueryParams["msgType"]; + msgType: QueryParams['msgType']; governanceForumLink: string; } @@ -71,34 +76,38 @@ const ProposalForm = forwardRef( if (formRef?.current) { const formData = new FormData(formRef.current); if (formData) { - const title = (formData.get("title") as string) || ""; - const description = (formData.get("description") as string) || ""; - const depositBld = (formData.get("deposit") as string) || ""; + const title = (formData.get('title') as string) || ''; + const description = (formData.get('description') as string) || ''; + const depositBld = (formData.get('deposit') as string) || ''; const deposit = Number(depositBld) * 1_000_000; const args: BaseProposalArgs = { title, description, deposit }; - if (msgType === "coreEvalProposal" && evals.length) { + if (msgType === 'coreEvalProposal' && evals.length) { return handleSubmit({ ...args, msgType, evals }); - } else if (msgType == "textProposal") { + } else if (msgType == 'textProposal') { return handleSubmit({ ...args, msgType }); - } else if (msgType === "parameterChangeProposal") { + } else if (msgType === 'parameterChangeProposal') { const changes = paramChangeRef.current?.getChanges(); - if (!Array.isArray(changes)) throw new Error("No changes"); + if (!Array.isArray(changes)) throw new Error('No changes'); return handleSubmit({ ...args, msgType, changes }); + } else if (msgType === 'communityPoolSpendProposal') { + const recipient = (formData.get('recipient') as string) || ''; + const amount = (formData.get('amount') as string) || ''; + return handleSubmit({ ...args, msgType, recipient, amount }); } } } - throw new Error("Error reading form data."); + throw new Error('Error reading form data.'); }; return ( -
-
+ +
-

{title}

-

{description}

+

{title}

+

{description}

-
- {msgType === "parameterChangeProposal" ? ( +
+ {msgType === 'parameterChangeProposal' ? ( ref={paramChangeRef} options={ @@ -113,16 +122,44 @@ const ProposalForm = forwardRef( + {msgType === 'communityPoolSpendProposal' ? ( +
+ + - {msgType === "coreEvalProposal" ? ( -
+ +
+ ) : null} + {msgType === 'coreEvalProposal' ? ( +
+ -
+
(
-
+
diff --git a/src/config/agoric/agoric.tsx b/src/config/agoric/agoric.tsx index a892bcb..6562977 100644 --- a/src/config/agoric/agoric.tsx +++ b/src/config/agoric/agoric.tsx @@ -1,31 +1,32 @@ -import { useMemo, useRef, useState } from "react"; -import { toast } from "react-toastify"; -import { Code } from "../../components/inline"; -import { BundleForm, BundleFormArgs } from "../../components/BundleForm"; -import { ProposalForm, ProposalArgs } from "../../components/ProposalForm"; -import { Tabs } from "../../components/Tabs"; -import { useNetwork } from "../../hooks/useNetwork"; -import { useWallet } from "../../hooks/useWallet"; -import { compressBundle } from "../../lib/compression"; +import { useMemo, useRef, useState } from 'react'; +import { toast } from 'react-toastify'; +import { Code } from '../../components/inline'; +import { BundleForm, BundleFormArgs } from '../../components/BundleForm'; +import { ProposalForm, ProposalArgs } from '../../components/ProposalForm'; +import { Tabs } from '../../components/Tabs'; +import { useNetwork } from '../../hooks/useNetwork'; +import { useWallet } from '../../hooks/useWallet'; +import { compressBundle } from '../../lib/compression'; import { makeCoreEvalProposalMsg, makeTextProposalMsg, makeInstallBundleMsg, makeParamChangeProposalMsg, -} from "../../lib/messageBuilder"; -import { isValidBundle } from "../../utils/validate"; -import { makeSignAndBroadcast } from "../../lib/signAndBroadcast"; -import { useWatchBundle } from "../../hooks/useWatchBundle"; -import { coinsUnit, renderCoins } from "../../utils/coin.ts"; -import { useQueries, useQuery, UseQueryResult } from "@tanstack/react-query"; + makeCommunityPoolSpendProposalMsg, +} from '../../lib/messageBuilder'; +import { isValidBundle } from '../../utils/validate'; +import { makeSignAndBroadcast } from '../../lib/signAndBroadcast'; +import { useWatchBundle } from '../../hooks/useWatchBundle'; +import { coinsUnit, renderCoins } from '../../utils/coin.ts'; +import { useQueries, useQuery, UseQueryResult } from '@tanstack/react-query'; import { accountBalancesQuery, depositParamsQuery, votingParamsQuery, -} from "../../lib/queries.ts"; -import { selectBldCoins } from "../../lib/selectors.ts"; -import { DepositParams, VotingParams } from "../../types/gov.ts"; +} from '../../lib/queries.ts'; +import { selectBldCoins } from '../../lib/selectors.ts'; +import { DepositParams, VotingParams } from '../../types/gov.ts'; const Agoric = () => { const { netName, networkConfig } = useNetwork(); @@ -51,12 +52,12 @@ const Agoric = () => { async function handleBundle(vals: BundleFormArgs) { if (!walletAddress) { - toast.error("Wallet not connected.", { autoClose: 3000 }); - throw new Error("wallet not connected"); + toast.error('Wallet not connected.', { autoClose: 3000 }); + throw new Error('wallet not connected'); } if (!isValidBundle(vals.bundle)) { - toast.error("Invalid bundle format.", { autoClose: 3000 }); - throw new Error("Invalid bundle."); + toast.error('Invalid bundle format.', { autoClose: 3000 }); + throw new Error('Invalid bundle.'); } const { compressedBundle, uncompressedSize } = await compressBundle( JSON.parse(vals.bundle), @@ -67,7 +68,7 @@ const Agoric = () => { submitter: walletAddress, }); try { - const txResponse = await signAndBroadcast(proposalMsg, "bundle"); + const txResponse = await signAndBroadcast(proposalMsg, 'bundle'); if (txResponse) { const { endoZipBase64Sha512 } = JSON.parse(vals.bundle); await watchBundle(endoZipBase64Sha512, txResponse); @@ -78,37 +79,48 @@ const Agoric = () => { } } - function handleProposal(msgType: QueryParams["msgType"]) { + function handleProposal(msgType: QueryParams['msgType']) { return async (vals: ProposalArgs) => { if (!walletAddress) { - toast.error("Wallet not connected.", { autoClose: 3000 }); - throw new Error("wallet not connected"); + toast.error('Wallet not connected.', { autoClose: 3000 }); + throw new Error('wallet not connected'); } let proposalMsg; - if (msgType === "coreEvalProposal") { - if (!("evals" in vals)) throw new Error("Missing evals"); + if (msgType === 'coreEvalProposal') { + if (!('evals' in vals)) throw new Error('Missing evals'); proposalMsg = makeCoreEvalProposalMsg({ ...vals, proposer: walletAddress, }); } - if (msgType === "textProposal") { + if (msgType === 'textProposal') { proposalMsg = makeTextProposalMsg({ ...vals, proposer: walletAddress, }); } - if (msgType === "parameterChangeProposal") { - if (vals.msgType !== "parameterChangeProposal") return; + + if (msgType === 'communityPoolSpendProposal') { + if (!('recipient' in vals) || !('amount' in vals)) { + throw new Error('Missing recipient or amount'); + } + proposalMsg = makeCommunityPoolSpendProposalMsg({ + ...vals, + proposer: walletAddress, + }); + } + + if (msgType === 'parameterChangeProposal') { + if (vals.msgType !== 'parameterChangeProposal') return; proposalMsg = makeParamChangeProposalMsg({ ...vals, proposer: walletAddress, }); } - if (!proposalMsg) throw new Error("Error parsing query or inputs."); + if (!proposalMsg) throw new Error('Error parsing query or inputs.'); try { - await signAndBroadcast(proposalMsg, "proposal"); + await signAndBroadcast(proposalMsg, 'proposal'); proposalFormRef.current?.reset(); corEvalFormRef.current?.reset(); } catch (e) { @@ -141,39 +153,39 @@ const Agoric = () => { alertBox && (
-
+
-
- You need to have{" "} - +
+ You need to have{' '} + {renderCoins(minDeposit)} - {" "} + {' '} in your wallet to submit this action
-
+
setAlertBox(false)} > - +
@@ -184,16 +196,16 @@ const Agoric = () => { This is a governance proposal that can be used for signaling @@ -206,32 +218,32 @@ const Agoric = () => { ), }, { - title: "CoreEval Proposal", - msgType: "coreEvalProposal", + title: 'CoreEval Proposal', + msgType: 'coreEvalProposal', content: ( This is a governance proposal that executes code after a - passing vote. The JSON Permit grants{" "} + passing vote. The JSON Permit grants{' '} capabilities - {" "} + {' '} and the JS Script can start or update a contract. These - files can be generated with the agoric run{" "} - command. For more details, see the{" "} + files can be generated with the agoric run{' '} + command. For more details, see the{' '} official docs @@ -242,24 +254,24 @@ const Agoric = () => { ), }, { - title: "Install Bundle", - msgType: "installBundle", + title: 'Install Bundle', + msgType: 'installBundle', content: ( The install bundle message deploys and installs an external bundle generated during the agoric run process. - The resulting installation can be referenced in a{" "} + The resulting installation can be referenced in a{' '} CoreEval proposal - {" "} + {' '} that starts or updates a contract. } @@ -267,19 +279,32 @@ const Agoric = () => { ), }, { - title: "Parameter Change Proposal", - msgType: "parameterChangeProposal", + title: 'Parameter Change Proposal', + msgType: 'parameterChangeProposal', content: ( ), }, + { + title: 'Community Pool Spend Proposal', + msgType: 'communityPoolSpendProposal', + content: ( + + ), + }, ]} /> diff --git a/src/index.d.ts b/src/index.d.ts index b3329a8..6dd9843 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1,5 +1,5 @@ -import { NetName } from "./contexts/network"; -import { Keplr } from "@keplr-wallet/types"; +import { NetName } from './contexts/network'; +import { Keplr } from '@keplr-wallet/types'; declare global { interface Window { @@ -17,12 +17,13 @@ declare global { interface QueryParams { network: NetName; msgType: - | "coreEvalProposal" - | "textProposal" - | "installBundle" - | "parameterChangeProposal" - | "addPSM" - | "addVault"; + | 'coreEvalProposal' + | 'textProposal' + | 'installBundle' + | 'parameterChangeProposal' + | 'addPSM' + | 'addVault' + | 'communityPoolSpendProposal'; paramType: string; } diff --git a/src/lib/messageBuilder.ts b/src/lib/messageBuilder.ts index 9ec71d4..654dacd 100644 --- a/src/lib/messageBuilder.ts +++ b/src/lib/messageBuilder.ts @@ -1,19 +1,58 @@ -import { CoreEvalProposal } from "@agoric/cosmic-proto/swingset/swingset.js"; -import { MsgInstallBundle } from "@agoric/cosmic-proto/swingset/msgs.js"; -import { StdFee } from "@cosmjs/amino"; -import { fromBech32 } from "@cosmjs/encoding"; -import { coins, Registry } from "@cosmjs/proto-signing"; -import { defaultRegistryTypes } from "@cosmjs/stargate"; -import { TextProposal } from "cosmjs-types/cosmos/gov/v1beta1/gov"; -import { ParameterChangeProposal } from "cosmjs-types/cosmos/params/v1beta1/params"; -import { Any } from "cosmjs-types/google/protobuf/any"; -import type { ParamChange } from "cosmjs-types/cosmos/params/v1beta1/params"; +import { CoreEvalProposal } from '@agoric/cosmic-proto/swingset/swingset.js'; +import { MsgInstallBundle } from '@agoric/cosmic-proto/swingset/msgs.js'; +import { StdFee } from '@cosmjs/amino'; +import { fromBech32 } from '@cosmjs/encoding'; +import { coins, Registry } from '@cosmjs/proto-signing'; +import { defaultRegistryTypes } from '@cosmjs/stargate'; +import { TextProposal } from 'cosmjs-types/cosmos/gov/v1beta1/gov'; +import { ParameterChangeProposal } from 'cosmjs-types/cosmos/params/v1beta1/params'; +import { Any } from 'cosmjs-types/google/protobuf/any'; +import type { ParamChange } from 'cosmjs-types/cosmos/params/v1beta1/params'; +import { CommunityPoolSpendProposal } from 'cosmjs-types/cosmos/distribution/v1beta1/distribution'; export const registry = new Registry([ ...defaultRegistryTypes, - ["/agoric.swingset.MsgInstallBundle", MsgInstallBundle], + ['/agoric.swingset.MsgInstallBundle', MsgInstallBundle], ]); +export const makeCommunityPoolSpendProposalMsg = ({ + proposer, + recipient, + amount, + title, + description, + deposit, +}: { + proposer: string; + recipient: string; + amount: string; + title: string; + description: string; + deposit?: number | string; +}) => { + const communityPoolSpendProposal: CommunityPoolSpendProposal = { + title, + description, + recipient, + amount: coins(amount, 'ubld'), + }; + const msgSubmitProposal = { + typeUrl: '/cosmos.gov.v1beta1.MsgSubmitProposal', + value: { + content: { + typeUrl: '/cosmos.distribution.v1beta1.CommunityPoolSpendProposal', + value: CommunityPoolSpendProposal.encode( + communityPoolSpendProposal, + ).finish(), + }, + proposer: proposer, + ...(deposit && + Number(deposit) && { initialDeposit: coins(deposit, 'ubld') }), + }, + }; + return msgSubmitProposal; +}; + interface MakeTextProposalArgs { title: string; description: string; @@ -27,10 +66,10 @@ export const makeTextProposalMsg = ({ proposer, deposit, }: MakeTextProposalArgs) => ({ - typeUrl: "/cosmos.gov.v1beta1.MsgSubmitProposal", + typeUrl: '/cosmos.gov.v1beta1.MsgSubmitProposal', value: { content: Any.fromPartial({ - typeUrl: "/cosmos.gov.v1beta1.TextProposal", + typeUrl: '/cosmos.gov.v1beta1.TextProposal', value: Uint8Array.from( TextProposal.encode( TextProposal.fromPartial({ @@ -42,7 +81,7 @@ export const makeTextProposalMsg = ({ }), proposer, ...(deposit && - Number(deposit) && { initialDeposit: coins(deposit, "ubld") }), + Number(deposit) && { initialDeposit: coins(deposit, 'ubld') }), }, }); @@ -53,10 +92,10 @@ export const makeCoreEvalProposalMsg = ({ proposer, deposit, }: CoreEvalProposal & { proposer: string; deposit?: string | number }) => ({ - typeUrl: "/cosmos.gov.v1beta1.MsgSubmitProposal", + typeUrl: '/cosmos.gov.v1beta1.MsgSubmitProposal', value: { content: Any.fromPartial({ - typeUrl: "/agoric.swingset.CoreEvalProposal", + typeUrl: '/agoric.swingset.CoreEvalProposal', value: Uint8Array.from( CoreEvalProposal.encode( CoreEvalProposal.fromPartial({ @@ -69,7 +108,7 @@ export const makeCoreEvalProposalMsg = ({ }), proposer, ...(deposit && - Number(deposit) && { initialDeposit: coins(deposit, "ubld") }), + Number(deposit) && { initialDeposit: coins(deposit, 'ubld') }), }, }); @@ -88,10 +127,10 @@ export const makeParamChangeProposalMsg = ({ proposer, deposit = 1000000, }: ParamChangeArgs) => ({ - typeUrl: "/cosmos.gov.v1beta1.MsgSubmitProposal", + typeUrl: '/cosmos.gov.v1beta1.MsgSubmitProposal', value: { content: Any.fromPartial({ - typeUrl: "/cosmos.params.v1beta1.ParameterChangeProposal", + typeUrl: '/cosmos.params.v1beta1.ParameterChangeProposal', value: Uint8Array.from( ParameterChangeProposal.encode( ParameterChangeProposal.fromPartial({ @@ -104,7 +143,7 @@ export const makeParamChangeProposalMsg = ({ }), proposer, ...(deposit && - Number(deposit) && { initialDeposit: coins(deposit, "ubld") }), + Number(deposit) && { initialDeposit: coins(deposit, 'ubld') }), }, }); @@ -119,7 +158,7 @@ export const makeInstallBundleMsg = ({ uncompressedSize, submitter, }: MsgInstallArgs) => ({ - typeUrl: "/agoric.swingset.MsgInstallBundle", + typeUrl: '/agoric.swingset.MsgInstallBundle', value: { compressedBundle, uncompressedSize, @@ -135,6 +174,6 @@ interface MakeFeeObjectArgs { export const makeFeeObject = ({ denom, amount, gas }: MakeFeeObjectArgs) => ({ - amount: coins(amount || 0, denom || "uist"), - gas: gas ? String(gas) : "auto", + amount: coins(amount || 0, denom || 'uist'), + gas: gas ? String(gas) : 'auto', }) as StdFee; From 04790eb3772bd650e6b44581c870b3636386642e Mon Sep 17 00:00:00 2001 From: Jeff Date: Tue, 11 Jun 2024 20:18:10 +0300 Subject: [PATCH 2/5] form tests --- src/config/agoric/agoric.spec.tsx | 61 ++++++++++++++++++++++++++++++- src/config/agoric/agoric.tsx | 2 +- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/config/agoric/agoric.spec.tsx b/src/config/agoric/agoric.spec.tsx index 349b5bd..34d44e2 100644 --- a/src/config/agoric/agoric.spec.tsx +++ b/src/config/agoric/agoric.spec.tsx @@ -1,5 +1,5 @@ import "../../installSesLockdown"; -import { render, screen } from "@testing-library/react"; +import { fireEvent, render, screen } from "@testing-library/react"; import { Router } from "wouter"; import App from "../../App"; import { ContextProviders } from "../../contexts/providers"; @@ -26,6 +26,65 @@ describe("Agoric Config", () => { "CoreEval Proposal", "Install Bundle", "Parameter Change Proposal", + "Community Pool Spend", ]); }); + + vi.mock('../../hooks/useWallet', () => ({ + useWallet: vi.fn(() => ({ + walletAddress: 'agoric12se', + stargateClient: { + simulate: vi.fn(), + signAndBroadcast: vi.fn(), + }, + })), + })); + + vi.mock('../../lib/signAndBroadcast', () => ({ + makeSignAndBroadcast: vi.fn(), + })); + it(" renders comm spend proposal form", async () => { + + // const mockTxResult = { + // code: 0, + // events: [], + // }; + // makeSignAndBroadcast(vi.fn().mockResolvedValue(mockTxResult)); + + render( + + + + , + , + ); + const communityPoolSpendTab = await screen.findByRole("tab", { + name: "Community Pool Spend", + }); + fireEvent.click(communityPoolSpendTab); + + + const recipientField = await screen.findByLabelText("Recipient"); + expect(recipientField).toBeTruthy(); + + const amountField = await screen.findByLabelText("Amount"); + expect(amountField).toBeTruthy(); + + fireEvent.change(recipientField, { target: { value: "agoric12se" } }); + fireEvent.change(amountField, { target: { value: "1000000" } }); + + // const submitButton = await screen.findByRole("button", { + // name: "Sign & Submit", + // }); + // fireEvent.click(submitButton); + + // expect(makeSignAndBroadcast).toHaveBeenCalledWith( + // expect.any(Object), // stargateClient mock + // 'agoric12se', + // expect.any(String), // netName + // ); + + + + }); }); diff --git a/src/config/agoric/agoric.tsx b/src/config/agoric/agoric.tsx index 6562977..c1254f5 100644 --- a/src/config/agoric/agoric.tsx +++ b/src/config/agoric/agoric.tsx @@ -293,7 +293,7 @@ const Agoric = () => { ), }, { - title: 'Community Pool Spend Proposal', + title: 'Community Pool Spend', msgType: 'communityPoolSpendProposal', content: ( Date: Tue, 11 Jun 2024 20:19:45 +0300 Subject: [PATCH 3/5] lint --- src/components/ProposalForm.tsx | 116 +++++++++--------- src/config/agoric/agoric.spec.tsx | 20 ++- src/config/agoric/agoric.tsx | 194 +++++++++++++++--------------- src/index.d.ts | 18 +-- src/lib/messageBuilder.ts | 56 ++++----- 5 files changed, 200 insertions(+), 204 deletions(-) diff --git a/src/components/ProposalForm.tsx b/src/components/ProposalForm.tsx index 254c3e7..b0599e4 100644 --- a/src/components/ProposalForm.tsx +++ b/src/components/ProposalForm.tsx @@ -5,16 +5,16 @@ import { forwardRef, FormEvent, ReactNode, -} from 'react'; -import { CodeInputGroup } from './CodeInputGroup'; -import { CoreEval } from '@agoric/cosmic-proto/swingset/swingset.js'; -import { Button } from './Button'; -import { ParamChange } from 'cosmjs-types/cosmos/params/v1beta1/params'; -import { ParameterChangeFormSection } from './ParameterChangeForm'; -import { DepositSection } from './DepositSection'; -import { paramOptions } from '../config/agoric/params'; -import type { ParameterChangeTypeOption } from '../types/form'; -import { TitleDescriptionInputs } from './TitleDescriptionInputs'; +} from "react"; +import { CodeInputGroup } from "./CodeInputGroup"; +import { CoreEval } from "@agoric/cosmic-proto/swingset/swingset.js"; +import { Button } from "./Button"; +import { ParamChange } from "cosmjs-types/cosmos/params/v1beta1/params"; +import { ParameterChangeFormSection } from "./ParameterChangeForm"; +import { DepositSection } from "./DepositSection"; +import { paramOptions } from "../config/agoric/params"; +import type { ParameterChangeTypeOption } from "../types/form"; +import { TitleDescriptionInputs } from "./TitleDescriptionInputs"; type BaseProposalArgs = { title: string; @@ -24,17 +24,17 @@ type BaseProposalArgs = { export type ProposalArgs = BaseProposalArgs & ProposalDetail; -export type QueryType = ReturnType<(typeof paramOptions)[number]['query']>; +export type QueryType = ReturnType<(typeof paramOptions)[number]["query"]>; export type SelectorReturnType = ReturnType< - (typeof paramOptions)[number]['selector'] + (typeof paramOptions)[number]["selector"] >; export type ProposalDetail = - | { msgType: 'textProposal' } - | { msgType: 'coreEvalProposal'; evals: CoreEval[] } - | { msgType: 'parameterChangeProposal'; changes: ParamChange[] } + | { msgType: "textProposal" } + | { msgType: "coreEvalProposal"; evals: CoreEval[] } + | { msgType: "parameterChangeProposal"; changes: ParamChange[] } | { - msgType: 'communityPoolSpendProposal'; + msgType: "communityPoolSpendProposal"; recipient: string; amount: string; }; @@ -44,7 +44,7 @@ interface ProposalFormProps { description: string | ReactNode; handleSubmit: (proposal: ProposalArgs) => void; titleDescOnly?: boolean; - msgType: QueryParams['msgType']; + msgType: QueryParams["msgType"]; governanceForumLink: string; } @@ -76,38 +76,38 @@ const ProposalForm = forwardRef( if (formRef?.current) { const formData = new FormData(formRef.current); if (formData) { - const title = (formData.get('title') as string) || ''; - const description = (formData.get('description') as string) || ''; - const depositBld = (formData.get('deposit') as string) || ''; + const title = (formData.get("title") as string) || ""; + const description = (formData.get("description") as string) || ""; + const depositBld = (formData.get("deposit") as string) || ""; const deposit = Number(depositBld) * 1_000_000; const args: BaseProposalArgs = { title, description, deposit }; - if (msgType === 'coreEvalProposal' && evals.length) { + if (msgType === "coreEvalProposal" && evals.length) { return handleSubmit({ ...args, msgType, evals }); - } else if (msgType == 'textProposal') { + } else if (msgType == "textProposal") { return handleSubmit({ ...args, msgType }); - } else if (msgType === 'parameterChangeProposal') { + } else if (msgType === "parameterChangeProposal") { const changes = paramChangeRef.current?.getChanges(); - if (!Array.isArray(changes)) throw new Error('No changes'); + if (!Array.isArray(changes)) throw new Error("No changes"); return handleSubmit({ ...args, msgType, changes }); - } else if (msgType === 'communityPoolSpendProposal') { - const recipient = (formData.get('recipient') as string) || ''; - const amount = (formData.get('amount') as string) || ''; + } else if (msgType === "communityPoolSpendProposal") { + const recipient = (formData.get("recipient") as string) || ""; + const amount = (formData.get("amount") as string) || ""; return handleSubmit({ ...args, msgType, recipient, amount }); } } } - throw new Error('Error reading form data.'); + throw new Error("Error reading form data."); }; return ( -
-
+ +
-

{title}

-

{description}

+

{title}

+

{description}

-
- {msgType === 'parameterChangeProposal' ? ( +
+ {msgType === "parameterChangeProposal" ? ( ref={paramChangeRef} options={ @@ -122,44 +122,44 @@ const ProposalForm = forwardRef( - {msgType === 'communityPoolSpendProposal' ? ( -
+ {msgType === "communityPoolSpendProposal" ? ( +
) : null} - {msgType === 'coreEvalProposal' ? ( -
+ {msgType === "coreEvalProposal" ? ( +
-
+
(
-
+
diff --git a/src/config/agoric/agoric.spec.tsx b/src/config/agoric/agoric.spec.tsx index 34d44e2..95534b4 100644 --- a/src/config/agoric/agoric.spec.tsx +++ b/src/config/agoric/agoric.spec.tsx @@ -29,10 +29,10 @@ describe("Agoric Config", () => { "Community Pool Spend", ]); }); - - vi.mock('../../hooks/useWallet', () => ({ + + vi.mock("../../hooks/useWallet", () => ({ useWallet: vi.fn(() => ({ - walletAddress: 'agoric12se', + walletAddress: "agoric12se", stargateClient: { simulate: vi.fn(), signAndBroadcast: vi.fn(), @@ -40,11 +40,10 @@ describe("Agoric Config", () => { })), })); - vi.mock('../../lib/signAndBroadcast', () => ({ + vi.mock("../../lib/signAndBroadcast", () => ({ makeSignAndBroadcast: vi.fn(), })); it(" renders comm spend proposal form", async () => { - // const mockTxResult = { // code: 0, // events: [], @@ -53,9 +52,10 @@ describe("Agoric Config", () => { render( - - - , + + + + , , ); const communityPoolSpendTab = await screen.findByRole("tab", { @@ -63,7 +63,6 @@ describe("Agoric Config", () => { }); fireEvent.click(communityPoolSpendTab); - const recipientField = await screen.findByLabelText("Recipient"); expect(recipientField).toBeTruthy(); @@ -83,8 +82,5 @@ describe("Agoric Config", () => { // 'agoric12se', // expect.any(String), // netName // ); - - - }); }); diff --git a/src/config/agoric/agoric.tsx b/src/config/agoric/agoric.tsx index c1254f5..40b5d2f 100644 --- a/src/config/agoric/agoric.tsx +++ b/src/config/agoric/agoric.tsx @@ -1,32 +1,32 @@ -import { useMemo, useRef, useState } from 'react'; -import { toast } from 'react-toastify'; -import { Code } from '../../components/inline'; -import { BundleForm, BundleFormArgs } from '../../components/BundleForm'; -import { ProposalForm, ProposalArgs } from '../../components/ProposalForm'; -import { Tabs } from '../../components/Tabs'; -import { useNetwork } from '../../hooks/useNetwork'; -import { useWallet } from '../../hooks/useWallet'; -import { compressBundle } from '../../lib/compression'; +import { useMemo, useRef, useState } from "react"; +import { toast } from "react-toastify"; +import { Code } from "../../components/inline"; +import { BundleForm, BundleFormArgs } from "../../components/BundleForm"; +import { ProposalForm, ProposalArgs } from "../../components/ProposalForm"; +import { Tabs } from "../../components/Tabs"; +import { useNetwork } from "../../hooks/useNetwork"; +import { useWallet } from "../../hooks/useWallet"; +import { compressBundle } from "../../lib/compression"; import { makeCoreEvalProposalMsg, makeTextProposalMsg, makeInstallBundleMsg, makeParamChangeProposalMsg, makeCommunityPoolSpendProposalMsg, -} from '../../lib/messageBuilder'; -import { isValidBundle } from '../../utils/validate'; -import { makeSignAndBroadcast } from '../../lib/signAndBroadcast'; -import { useWatchBundle } from '../../hooks/useWatchBundle'; -import { coinsUnit, renderCoins } from '../../utils/coin.ts'; -import { useQueries, useQuery, UseQueryResult } from '@tanstack/react-query'; +} from "../../lib/messageBuilder"; +import { isValidBundle } from "../../utils/validate"; +import { makeSignAndBroadcast } from "../../lib/signAndBroadcast"; +import { useWatchBundle } from "../../hooks/useWatchBundle"; +import { coinsUnit, renderCoins } from "../../utils/coin.ts"; +import { useQueries, useQuery, UseQueryResult } from "@tanstack/react-query"; import { accountBalancesQuery, depositParamsQuery, votingParamsQuery, -} from '../../lib/queries.ts'; -import { selectBldCoins } from '../../lib/selectors.ts'; -import { DepositParams, VotingParams } from '../../types/gov.ts'; +} from "../../lib/queries.ts"; +import { selectBldCoins } from "../../lib/selectors.ts"; +import { DepositParams, VotingParams } from "../../types/gov.ts"; const Agoric = () => { const { netName, networkConfig } = useNetwork(); @@ -52,12 +52,12 @@ const Agoric = () => { async function handleBundle(vals: BundleFormArgs) { if (!walletAddress) { - toast.error('Wallet not connected.', { autoClose: 3000 }); - throw new Error('wallet not connected'); + toast.error("Wallet not connected.", { autoClose: 3000 }); + throw new Error("wallet not connected"); } if (!isValidBundle(vals.bundle)) { - toast.error('Invalid bundle format.', { autoClose: 3000 }); - throw new Error('Invalid bundle.'); + toast.error("Invalid bundle format.", { autoClose: 3000 }); + throw new Error("Invalid bundle."); } const { compressedBundle, uncompressedSize } = await compressBundle( JSON.parse(vals.bundle), @@ -68,7 +68,7 @@ const Agoric = () => { submitter: walletAddress, }); try { - const txResponse = await signAndBroadcast(proposalMsg, 'bundle'); + const txResponse = await signAndBroadcast(proposalMsg, "bundle"); if (txResponse) { const { endoZipBase64Sha512 } = JSON.parse(vals.bundle); await watchBundle(endoZipBase64Sha512, txResponse); @@ -79,30 +79,30 @@ const Agoric = () => { } } - function handleProposal(msgType: QueryParams['msgType']) { + function handleProposal(msgType: QueryParams["msgType"]) { return async (vals: ProposalArgs) => { if (!walletAddress) { - toast.error('Wallet not connected.', { autoClose: 3000 }); - throw new Error('wallet not connected'); + toast.error("Wallet not connected.", { autoClose: 3000 }); + throw new Error("wallet not connected"); } let proposalMsg; - if (msgType === 'coreEvalProposal') { - if (!('evals' in vals)) throw new Error('Missing evals'); + if (msgType === "coreEvalProposal") { + if (!("evals" in vals)) throw new Error("Missing evals"); proposalMsg = makeCoreEvalProposalMsg({ ...vals, proposer: walletAddress, }); } - if (msgType === 'textProposal') { + if (msgType === "textProposal") { proposalMsg = makeTextProposalMsg({ ...vals, proposer: walletAddress, }); } - if (msgType === 'communityPoolSpendProposal') { - if (!('recipient' in vals) || !('amount' in vals)) { - throw new Error('Missing recipient or amount'); + if (msgType === "communityPoolSpendProposal") { + if (!("recipient" in vals) || !("amount" in vals)) { + throw new Error("Missing recipient or amount"); } proposalMsg = makeCommunityPoolSpendProposalMsg({ ...vals, @@ -110,17 +110,17 @@ const Agoric = () => { }); } - if (msgType === 'parameterChangeProposal') { - if (vals.msgType !== 'parameterChangeProposal') return; + if (msgType === "parameterChangeProposal") { + if (vals.msgType !== "parameterChangeProposal") return; proposalMsg = makeParamChangeProposalMsg({ ...vals, proposer: walletAddress, }); } - if (!proposalMsg) throw new Error('Error parsing query or inputs.'); + if (!proposalMsg) throw new Error("Error parsing query or inputs."); try { - await signAndBroadcast(proposalMsg, 'proposal'); + await signAndBroadcast(proposalMsg, "proposal"); proposalFormRef.current?.reset(); corEvalFormRef.current?.reset(); } catch (e) { @@ -153,39 +153,39 @@ const Agoric = () => { alertBox && (
-
+
-
- You need to have{' '} - +
+ You need to have{" "} + {renderCoins(minDeposit)} - {' '} + {" "} in your wallet to submit this action
-
+
setAlertBox(false)} > - +
@@ -196,16 +196,16 @@ const Agoric = () => { This is a governance proposal that can be used for signaling @@ -218,32 +218,32 @@ const Agoric = () => { ), }, { - title: 'CoreEval Proposal', - msgType: 'coreEvalProposal', + title: "CoreEval Proposal", + msgType: "coreEvalProposal", content: ( This is a governance proposal that executes code after a - passing vote. The JSON Permit grants{' '} + passing vote. The JSON Permit grants{" "} capabilities - {' '} + {" "} and the JS Script can start or update a contract. These - files can be generated with the agoric run{' '} - command. For more details, see the{' '} + files can be generated with the agoric run{" "} + command. For more details, see the{" "} official docs @@ -254,24 +254,24 @@ const Agoric = () => { ), }, { - title: 'Install Bundle', - msgType: 'installBundle', + title: "Install Bundle", + msgType: "installBundle", content: ( The install bundle message deploys and installs an external bundle generated during the agoric run process. - The resulting installation can be referenced in a{' '} + The resulting installation can be referenced in a{" "} CoreEval proposal - {' '} + {" "} that starts or updates a contract. } @@ -279,29 +279,29 @@ const Agoric = () => { ), }, { - title: 'Parameter Change Proposal', - msgType: 'parameterChangeProposal', + title: "Parameter Change Proposal", + msgType: "parameterChangeProposal", content: ( ), }, { - title: 'Community Pool Spend', - msgType: 'communityPoolSpendProposal', + title: "Community Pool Spend", + msgType: "communityPoolSpendProposal", content: ( ), }, diff --git a/src/index.d.ts b/src/index.d.ts index 6dd9843..9f6229f 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1,5 +1,5 @@ -import { NetName } from './contexts/network'; -import { Keplr } from '@keplr-wallet/types'; +import { NetName } from "./contexts/network"; +import { Keplr } from "@keplr-wallet/types"; declare global { interface Window { @@ -17,13 +17,13 @@ declare global { interface QueryParams { network: NetName; msgType: - | 'coreEvalProposal' - | 'textProposal' - | 'installBundle' - | 'parameterChangeProposal' - | 'addPSM' - | 'addVault' - | 'communityPoolSpendProposal'; + | "coreEvalProposal" + | "textProposal" + | "installBundle" + | "parameterChangeProposal" + | "addPSM" + | "addVault" + | "communityPoolSpendProposal"; paramType: string; } diff --git a/src/lib/messageBuilder.ts b/src/lib/messageBuilder.ts index 654dacd..a752598 100644 --- a/src/lib/messageBuilder.ts +++ b/src/lib/messageBuilder.ts @@ -1,18 +1,18 @@ -import { CoreEvalProposal } from '@agoric/cosmic-proto/swingset/swingset.js'; -import { MsgInstallBundle } from '@agoric/cosmic-proto/swingset/msgs.js'; -import { StdFee } from '@cosmjs/amino'; -import { fromBech32 } from '@cosmjs/encoding'; -import { coins, Registry } from '@cosmjs/proto-signing'; -import { defaultRegistryTypes } from '@cosmjs/stargate'; -import { TextProposal } from 'cosmjs-types/cosmos/gov/v1beta1/gov'; -import { ParameterChangeProposal } from 'cosmjs-types/cosmos/params/v1beta1/params'; -import { Any } from 'cosmjs-types/google/protobuf/any'; -import type { ParamChange } from 'cosmjs-types/cosmos/params/v1beta1/params'; -import { CommunityPoolSpendProposal } from 'cosmjs-types/cosmos/distribution/v1beta1/distribution'; +import { CoreEvalProposal } from "@agoric/cosmic-proto/swingset/swingset.js"; +import { MsgInstallBundle } from "@agoric/cosmic-proto/swingset/msgs.js"; +import { StdFee } from "@cosmjs/amino"; +import { fromBech32 } from "@cosmjs/encoding"; +import { coins, Registry } from "@cosmjs/proto-signing"; +import { defaultRegistryTypes } from "@cosmjs/stargate"; +import { TextProposal } from "cosmjs-types/cosmos/gov/v1beta1/gov"; +import { ParameterChangeProposal } from "cosmjs-types/cosmos/params/v1beta1/params"; +import { Any } from "cosmjs-types/google/protobuf/any"; +import type { ParamChange } from "cosmjs-types/cosmos/params/v1beta1/params"; +import { CommunityPoolSpendProposal } from "cosmjs-types/cosmos/distribution/v1beta1/distribution"; export const registry = new Registry([ ...defaultRegistryTypes, - ['/agoric.swingset.MsgInstallBundle', MsgInstallBundle], + ["/agoric.swingset.MsgInstallBundle", MsgInstallBundle], ]); export const makeCommunityPoolSpendProposalMsg = ({ @@ -34,20 +34,20 @@ export const makeCommunityPoolSpendProposalMsg = ({ title, description, recipient, - amount: coins(amount, 'ubld'), + amount: coins(amount, "ubld"), }; const msgSubmitProposal = { - typeUrl: '/cosmos.gov.v1beta1.MsgSubmitProposal', + typeUrl: "/cosmos.gov.v1beta1.MsgSubmitProposal", value: { content: { - typeUrl: '/cosmos.distribution.v1beta1.CommunityPoolSpendProposal', + typeUrl: "/cosmos.distribution.v1beta1.CommunityPoolSpendProposal", value: CommunityPoolSpendProposal.encode( communityPoolSpendProposal, ).finish(), }, proposer: proposer, ...(deposit && - Number(deposit) && { initialDeposit: coins(deposit, 'ubld') }), + Number(deposit) && { initialDeposit: coins(deposit, "ubld") }), }, }; return msgSubmitProposal; @@ -66,10 +66,10 @@ export const makeTextProposalMsg = ({ proposer, deposit, }: MakeTextProposalArgs) => ({ - typeUrl: '/cosmos.gov.v1beta1.MsgSubmitProposal', + typeUrl: "/cosmos.gov.v1beta1.MsgSubmitProposal", value: { content: Any.fromPartial({ - typeUrl: '/cosmos.gov.v1beta1.TextProposal', + typeUrl: "/cosmos.gov.v1beta1.TextProposal", value: Uint8Array.from( TextProposal.encode( TextProposal.fromPartial({ @@ -81,7 +81,7 @@ export const makeTextProposalMsg = ({ }), proposer, ...(deposit && - Number(deposit) && { initialDeposit: coins(deposit, 'ubld') }), + Number(deposit) && { initialDeposit: coins(deposit, "ubld") }), }, }); @@ -92,10 +92,10 @@ export const makeCoreEvalProposalMsg = ({ proposer, deposit, }: CoreEvalProposal & { proposer: string; deposit?: string | number }) => ({ - typeUrl: '/cosmos.gov.v1beta1.MsgSubmitProposal', + typeUrl: "/cosmos.gov.v1beta1.MsgSubmitProposal", value: { content: Any.fromPartial({ - typeUrl: '/agoric.swingset.CoreEvalProposal', + typeUrl: "/agoric.swingset.CoreEvalProposal", value: Uint8Array.from( CoreEvalProposal.encode( CoreEvalProposal.fromPartial({ @@ -108,7 +108,7 @@ export const makeCoreEvalProposalMsg = ({ }), proposer, ...(deposit && - Number(deposit) && { initialDeposit: coins(deposit, 'ubld') }), + Number(deposit) && { initialDeposit: coins(deposit, "ubld") }), }, }); @@ -127,10 +127,10 @@ export const makeParamChangeProposalMsg = ({ proposer, deposit = 1000000, }: ParamChangeArgs) => ({ - typeUrl: '/cosmos.gov.v1beta1.MsgSubmitProposal', + typeUrl: "/cosmos.gov.v1beta1.MsgSubmitProposal", value: { content: Any.fromPartial({ - typeUrl: '/cosmos.params.v1beta1.ParameterChangeProposal', + typeUrl: "/cosmos.params.v1beta1.ParameterChangeProposal", value: Uint8Array.from( ParameterChangeProposal.encode( ParameterChangeProposal.fromPartial({ @@ -143,7 +143,7 @@ export const makeParamChangeProposalMsg = ({ }), proposer, ...(deposit && - Number(deposit) && { initialDeposit: coins(deposit, 'ubld') }), + Number(deposit) && { initialDeposit: coins(deposit, "ubld") }), }, }); @@ -158,7 +158,7 @@ export const makeInstallBundleMsg = ({ uncompressedSize, submitter, }: MsgInstallArgs) => ({ - typeUrl: '/agoric.swingset.MsgInstallBundle', + typeUrl: "/agoric.swingset.MsgInstallBundle", value: { compressedBundle, uncompressedSize, @@ -174,6 +174,6 @@ interface MakeFeeObjectArgs { export const makeFeeObject = ({ denom, amount, gas }: MakeFeeObjectArgs) => ({ - amount: coins(amount || 0, denom || 'uist'), - gas: gas ? String(gas) : 'auto', + amount: coins(amount || 0, denom || "uist"), + gas: gas ? String(gas) : "auto", }) as StdFee; From cf5a6b07a59539522083629d71c7978eb2eaa065 Mon Sep 17 00:00:00 2001 From: gacogo <113232524+gacogo@users.noreply.github.com> Date: Thu, 13 Jun 2024 18:42:44 +0300 Subject: [PATCH 4/5] Update src/config/agoric/agoric.spec.tsx delete unused code Co-authored-by: 0xPatrick --- src/config/agoric/agoric.spec.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/config/agoric/agoric.spec.tsx b/src/config/agoric/agoric.spec.tsx index 95534b4..d58ebc7 100644 --- a/src/config/agoric/agoric.spec.tsx +++ b/src/config/agoric/agoric.spec.tsx @@ -44,11 +44,6 @@ describe("Agoric Config", () => { makeSignAndBroadcast: vi.fn(), })); it(" renders comm spend proposal form", async () => { - // const mockTxResult = { - // code: 0, - // events: [], - // }; - // makeSignAndBroadcast(vi.fn().mockResolvedValue(mockTxResult)); render( From fe4227dd89726106f987d641600587c184cc8881 Mon Sep 17 00:00:00 2001 From: gacogo <113232524+gacogo@users.noreply.github.com> Date: Thu, 13 Jun 2024 18:43:10 +0300 Subject: [PATCH 5/5] Update src/config/agoric/agoric.spec.tsx Co-authored-by: 0xPatrick --- src/config/agoric/agoric.spec.tsx | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/config/agoric/agoric.spec.tsx b/src/config/agoric/agoric.spec.tsx index d58ebc7..5c5a071 100644 --- a/src/config/agoric/agoric.spec.tsx +++ b/src/config/agoric/agoric.spec.tsx @@ -67,15 +67,5 @@ describe("Agoric Config", () => { fireEvent.change(recipientField, { target: { value: "agoric12se" } }); fireEvent.change(amountField, { target: { value: "1000000" } }); - // const submitButton = await screen.findByRole("button", { - // name: "Sign & Submit", - // }); - // fireEvent.click(submitButton); - - // expect(makeSignAndBroadcast).toHaveBeenCalledWith( - // expect.any(Object), // stargateClient mock - // 'agoric12se', - // expect.any(String), // netName - // ); }); });