diff --git a/libs/chains/package.json b/libs/chains/package.json index 580f74bcb0f..35403ce3f92 100644 --- a/libs/chains/package.json +++ b/libs/chains/package.json @@ -31,6 +31,7 @@ "protobufjs": "^6.1.13" }, "devDependencies": { + "@atomone/atomone-types-long": "^1.0.3", "tsx": "^4.7.2" } } diff --git a/libs/chains/src/index.ts b/libs/chains/src/index.ts index 760d4e7e524..f60cde6f5cb 100644 --- a/libs/chains/src/index.ts +++ b/libs/chains/src/index.ts @@ -17,6 +17,9 @@ export { type QueryVotesResponseSDKType, } from './cosmos-ts/src/codegen/cosmos/gov/v1/query'; export { LCDQueryClient as GovV1Client } from './cosmos-ts/src/codegen/cosmos/gov/v1/query.lcd'; + +export { LCDQueryClient as GovV1AtomOneClient } from '@atomone/atomone-types-long/atomone/gov/v1/query.lcd'; +export { createLCDClient as createAtomOneLCDClient } from '@atomone/atomone-types-long/atomone/lcd'; export { createLCDClient } from './cosmos-ts/src/codegen/cosmos/lcd'; export * from './cosmos-ts/src/codegen/google/protobuf/any'; export * from './cosmos-ts/src/codegen/helpers'; diff --git a/libs/shared/src/types/protocol.ts b/libs/shared/src/types/protocol.ts index 84239a5011a..c5e1a156fc7 100644 --- a/libs/shared/src/types/protocol.ts +++ b/libs/shared/src/types/protocol.ts @@ -129,6 +129,7 @@ export enum ChainNetwork { */ export enum CosmosGovernanceVersion { v1 = 'v1', + v1atomone = 'v1atomone', v1beta1govgen = 'v1beta1govgen', v1beta1 = 'v1beta1', v1beta1Failed = 'v1beta1-attempt-failed', diff --git a/packages/commonwealth/client/scripts/controllers/chain/cosmos/adapter.ts b/packages/commonwealth/client/scripts/controllers/chain/cosmos/adapter.ts index 178b12c322d..ff57d499760 100644 --- a/packages/commonwealth/client/scripts/controllers/chain/cosmos/adapter.ts +++ b/packages/commonwealth/client/scripts/controllers/chain/cosmos/adapter.ts @@ -6,6 +6,7 @@ import IChainAdapter from '../../../models/IChainAdapter'; import type CosmosAccount from './account'; import CosmosAccounts from './accounts'; import CosmosChain from './chain'; +import CosmosGovernanceV1AtomOne from './gov/atomone/governance-v1'; import CosmosGovernanceGovgen from './gov/govgen/governance-v1beta1'; import CosmosGovernanceV1 from './gov/v1/governance-v1'; import CosmosGovernance from './gov/v1beta1/governance-v1beta1'; @@ -17,7 +18,8 @@ class Cosmos extends IChainAdapter { public governance: | CosmosGovernance | CosmosGovernanceV1 - | CosmosGovernanceGovgen; + | CosmosGovernanceGovgen + | CosmosGovernanceV1AtomOne; public readonly base = ChainBase.CosmosSDK; @@ -28,9 +30,11 @@ class Cosmos extends IChainAdapter { this.governance = meta?.ChainNode?.cosmos_gov_version === 'v1beta1govgen' ? new CosmosGovernanceGovgen(this.app) - : meta?.ChainNode?.cosmos_gov_version === 'v1' - ? new CosmosGovernanceV1(this.app) - : new CosmosGovernance(this.app); + : meta?.ChainNode?.cosmos_gov_version === 'v1atomone' + ? new CosmosGovernanceV1AtomOne(this.app) + : meta?.ChainNode?.cosmos_gov_version === 'v1' + ? new CosmosGovernanceV1(this.app) + : new CosmosGovernance(this.app); } public async initApi() { diff --git a/packages/commonwealth/client/scripts/controllers/chain/cosmos/chain.ts b/packages/commonwealth/client/scripts/controllers/chain/cosmos/chain.ts index 648ed63a131..0326b200c93 100644 --- a/packages/commonwealth/client/scripts/controllers/chain/cosmos/chain.ts +++ b/packages/commonwealth/client/scripts/controllers/chain/cosmos/chain.ts @@ -17,7 +17,7 @@ import { import BN from 'bn.js'; import { CosmosToken } from 'controllers/chain/cosmos/types'; import moment from 'moment'; -import { LCD } from 'shared/chain/types/cosmos'; +import { AtomOneLCD, LCD } from 'shared/chain/types/cosmos'; import type { IApp } from 'state'; import { ApiStatus } from 'state'; import { SERVER_URL } from 'state/api/config'; @@ -36,12 +36,14 @@ import { getCosmosChains } from '../../app/webWallets/utils'; import WebWalletController from '../../app/web_wallets'; import type CosmosAccount from './account'; import { + getAtomOneLCDClient, getLCDClient, getRPCClient, getSigningClient, getTMClient, } from './chain.utils'; import EthSigningClient from './eth_signing_client'; +import type { AtomOneGovExtension } from './gov/atomone/queries-v1'; import type { GovgenGovExtension } from './gov/govgen/queries-v1beta1'; /* eslint-disable @typescript-eslint/no-unused-vars */ @@ -54,16 +56,19 @@ export interface ICosmosTXData extends ITXData { // skip simulating the tx twice by saving the original estimated gas gas: number; } - +export const isAtomoneLCD = (lcd: LCD | AtomOneLCD): lcd is AtomOneLCD => { + return (lcd as AtomOneLCD).atomone !== undefined; +}; export type CosmosApiType = QueryClient & StakingExtension & GovExtension & GovgenGovExtension & + AtomOneGovExtension & BankExtension; class CosmosChain implements IChainModule { private _api: CosmosApiType; - private _lcd: LCD; + private _lcd: LCD | AtomOneLCD; public get api() { return this._api; @@ -136,7 +141,18 @@ class CosmosChain implements IChainModule { console.error('Error starting LCD client: ', e); } } - + if ( + chain?.ChainNode?.cosmos_gov_version === CosmosGovernanceVersion.v1atomone + ) { + try { + const lcdUrl = `${window.location.origin}${SERVER_URL}/cosmosProxy/v1/${chain.id}`; + console.log(`Starting LCD API at ${lcdUrl}...`); + const lcd = await getAtomOneLCDClient(lcdUrl); + this._lcd = lcd; + } catch (e) { + console.error('Error starting LCD client: ', e); + } + } await this.fetchBlock(); // Poll for new block immediately } diff --git a/packages/commonwealth/client/scripts/controllers/chain/cosmos/chain.utils.ts b/packages/commonwealth/client/scripts/controllers/chain/cosmos/chain.utils.ts index 837cbb47a3f..876b2af55a6 100644 --- a/packages/commonwealth/client/scripts/controllers/chain/cosmos/chain.utils.ts +++ b/packages/commonwealth/client/scripts/controllers/chain/cosmos/chain.utils.ts @@ -1,16 +1,21 @@ -import { OfflineSigner } from '@cosmjs/proto-signing'; +import { registry as atomoneRegistry } from '@atomone/atomone-types-long/atomone/gov/v1/tx.registry'; +import { registry as govgenRegistry } from '@atomone/govgen-types-long/govgen/gov/v1beta1/tx.registry'; +import { OfflineSigner, Registry } from '@cosmjs/proto-signing'; import { AminoTypes, SigningStargateClient, createDefaultAminoConverters, + defaultRegistryTypes, } from '@cosmjs/stargate'; import { Tendermint34Client } from '@cosmjs/tendermint-rpc'; -import { LCD } from '../../../../../shared/chain/types/cosmos'; +import { AtomOneLCD, LCD } from '../../../../../shared/chain/types/cosmos'; import { CosmosApiType } from './chain'; import { createAltGovAminoConverters, + createAtomoneGovAminoConverters, createGovgenGovAminoConverters, } from './gov/aminomessages'; +import { setupAtomOneExtension } from './gov/atomone/queries-v1'; import { setupGovgenExtension } from './gov/govgen/queries-v1beta1'; export const getTMClient = async ( @@ -29,6 +34,7 @@ export const getRPCClient = async ( cosm.setupGovExtension, cosm.setupStakingExtension, setupGovgenExtension, + setupAtomOneExtension, cosm.setupBankExtension, ); return client; @@ -42,6 +48,15 @@ export const getLCDClient = async (lcdUrl: string): Promise => { }); }; +export const getAtomOneLCDClient = async ( + lcdUrl: string, +): Promise => { + const { createAtomOneLCDClient } = await import('@hicommonwealth/chains'); + + return await createAtomOneLCDClient({ + restEndpoint: lcdUrl, + }); +}; export const getSigningClient = async ( url: string, signer: OfflineSigner, @@ -50,9 +65,15 @@ export const getSigningClient = async ( ...createDefaultAminoConverters(), ...createAltGovAminoConverters(), ...createGovgenGovAminoConverters(), + ...createAtomoneGovAminoConverters(), }); return await SigningStargateClient.connectWithSigner(url, signer, { + registry: new Registry([ + ...defaultRegistryTypes, + ...atomoneRegistry, + ...govgenRegistry, + ]), aminoTypes, }); }; diff --git a/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/aminomessages.ts b/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/aminomessages.ts index 1eba95517d2..1fb4ed2609a 100644 --- a/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/aminomessages.ts +++ b/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/aminomessages.ts @@ -1,3 +1,4 @@ +import { AminoConverter as AtomOneAminoConverter } from '@atomone/atomone-types-long/atomone/gov/v1beta1/tx.amino'; import { AminoConverter } from '@atomone/govgen-types-long/govgen/gov/v1beta1/tx.amino'; import { AminoMsg } from '@cosmjs/amino'; import { AminoMsgSubmitProposal } from '@cosmjs/stargate'; @@ -17,6 +18,9 @@ export function isAminoMsgSubmitProposal( return msg.type === 'cosmos-sdk/MsgSubmitProposal'; } export function createGovgenGovAminoConverters(): AminoConverters { + return AtomOneAminoConverter; +} +export function createAtomoneGovAminoConverters(): AminoConverters { return AminoConverter; } export function createAltGovAminoConverters(): AminoConverters { diff --git a/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/atomone/governance-v1.ts b/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/atomone/governance-v1.ts new file mode 100644 index 00000000000..508ecd899f3 --- /dev/null +++ b/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/atomone/governance-v1.ts @@ -0,0 +1,125 @@ +import { Any, numberToLong } from '@hicommonwealth/chains'; +import type { + CosmosToken, + ICosmosProposal, +} from 'controllers/chain/cosmos/types'; +import ProposalModule from 'models/ProposalModule'; +import { ITXModalData } from 'models/interfaces'; +import type CosmosAccount from '../../account'; +import type CosmosAccounts from '../../accounts'; +import type CosmosChain from '../../chain'; +import { isAtomoneLCD, type CosmosApiType } from '../../chain'; +import { CosmosProposalV1AtomOne } from './proposal-v1'; +import { encodeMsgSubmitProposalAtomOne, propToIProposal } from './utils-v1'; + +/** This file is a copy of controllers/chain/cosmos/governance.ts, modified for + * gov module version v1. This is considered a patch to make sure v1-enabled chains + * load proposals. Eventually we will ideally move back to one governance.ts file. + * Patch state: + * + * - governance.ts uses cosmJS v1beta1 gov + * - governance-v1.ts uses telescope-generated v1 gov */ +class CosmosGovernanceV1AtomOne extends ProposalModule< + CosmosApiType, + ICosmosProposal, + // @ts-expect-error StrictNullChecks + CosmosProposalV1AtomOne +> { + private _minDeposit: CosmosToken; + + public get minDeposit() { + return this._minDeposit; + } + + public setMinDeposit(minDeposit: CosmosToken) { + this._minDeposit = minDeposit; + } + + private _Chain: CosmosChain; + private _Accounts: CosmosAccounts; + + /* eslint-disable-next-line @typescript-eslint/require-await */ + public async init( + ChainInfo: CosmosChain, + Accounts: CosmosAccounts, + ): Promise { + this._Chain = ChainInfo; + this._Accounts = Accounts; + this._initialized = true; + } + + public async getProposal( + proposalId: number, + ): Promise { + const existingProposal = this.store.getByIdentifier(proposalId); + if (existingProposal) { + return existingProposal; + } + return this._initProposal(proposalId); + } + + private async _initProposal( + proposalId: number, + // @ts-expect-error StrictNullChecks + ): Promise { + try { + // @ts-expect-error StrictNullChecks + if (!proposalId) return; + // @ts-expect-error StrictNullChecks + if (!isAtomoneLCD(this._Chain.lcd)) return; + const { proposal } = await this._Chain.lcd.atomone.gov.v1.proposal({ + proposalId: numberToLong(proposalId), + }); + const cosmosProposal = new CosmosProposalV1AtomOne( + this._Chain, + this._Accounts, + this, + // @ts-expect-error StrictNullChecks + propToIProposal(proposal), + ); + await cosmosProposal.init(); + return cosmosProposal; + } catch (error) { + console.error('Error fetching proposal: ', error); + } + } + + public createTx( + sender: CosmosAccount, + title: string, + description: string, + initialDeposit: CosmosToken, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + memo = '', + ): ITXModalData { + throw new Error('unsupported'); + } + + // TODO: support multiple deposit types + public async submitProposalTx( + sender: CosmosAccount, + initialDeposit: CosmosToken, + content: Any, + ): Promise { + const msg = encodeMsgSubmitProposalAtomOne( + sender.address, + initialDeposit, + content, + ); + + // fetch completed proposal from returned events + const events = await this._Chain.sendTx(sender, msg); + console.log(events); + const submitEvent = events?.find((e) => e.type === 'submit_proposal'); + const cosm = await import('@cosmjs/encoding'); + const idAttribute = submitEvent?.attributes.find( + ({ key }) => key && cosm.fromAscii(key) === 'proposal_id', + ); + // @ts-expect-error StrictNullChecks + const id = +cosm.fromAscii(idAttribute.value); + await this._initProposal(id); + return id; + } +} + +export default CosmosGovernanceV1AtomOne; diff --git a/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/atomone/proposal-v1.ts b/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/atomone/proposal-v1.ts new file mode 100644 index 00000000000..28179cd29d7 --- /dev/null +++ b/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/atomone/proposal-v1.ts @@ -0,0 +1,351 @@ +import type { + QueryDepositsResponseSDKType, + QueryTallyResultResponseSDKType, + QueryVotesResponseSDKType, +} from '@atomone/atomone-types-long/atomone/gov/v1/query'; +import { EncodeObject } from '@cosmjs/proto-signing'; +import { longify } from '@cosmjs/stargate/build/queryclient'; +import { ProposalType } from '@hicommonwealth/shared'; +import BN from 'bn.js'; +import type { + CosmosProposalState, + CosmosToken, + CosmosVoteChoice, + ICosmosProposal, +} from 'controllers/chain/cosmos/types'; +import Long from 'long'; +import Proposal from 'models/Proposal'; +import { ITXModalData } from 'models/interfaces'; +import { + ProposalEndTime, + ProposalStatus, + VotingType, + VotingUnit, +} from 'models/types'; +import { DepositVote } from 'models/votes'; +import moment from 'moment'; +import CosmosAccount from '../../account'; +import type CosmosAccounts from '../../accounts'; +import type CosmosChain from '../../chain'; +import { isAtomoneLCD, type CosmosApiType } from '../../chain'; +import { CosmosVote } from '../v1beta1/proposal-v1beta1'; +import { encodeMsgVote } from '../v1beta1/utils-v1beta1'; +import CosmosGovernanceV1AtomOne from './governance-v1'; +import { marshalTallyV1 } from './utils-v1'; + +const voteToEnumV1 = (voteOption: number | string): CosmosVoteChoice => { + switch (voteOption) { + case 'VOTE_OPTION_YES': + return 'Yes'; + case 'VOTE_OPTION_NO': + return 'No'; + case 'VOTE_OPTION_ABSTAIN': + return 'Abstain'; + case 'VOTE_OPTION_NO_WITH_VETO': + return 'NoWithVeto'; + default: + // @ts-expect-error StrictNullChecks + return null; + } +}; + +export class CosmosProposalV1AtomOne extends Proposal< + CosmosApiType, + CosmosToken, + ICosmosProposal, + CosmosVote +> { + public get shortIdentifier() { + return `#${this.identifier.toString()}`; + } + + public get title(): string { + return this.data.title || this._metadata?.title; + } + + public get description() { + return ( + this.data.description || + this._metadata?.summary || + this._metadata?.description + ); + } + + // @ts-expect-error StrictNullChecks + public get author() { + return this.data.proposer + ? this._Accounts.fromAddress(this.data.proposer) + : null; + } + + public get votingType() { + if (this.status === 'DepositPeriod') { + return VotingType.SimpleYesApprovalVoting; + } + return VotingType.YesNoAbstainVeto; + } + + public get votingUnit() { + return VotingUnit.CoinVote; + } + + public canVoteFrom(account) { + // TODO: balance check + return account instanceof CosmosAccount; + } + + public get status(): CosmosProposalState { + return this.data.state.status; + } + + public get depositorsAsVotes(): Array> { + return this.data.state.depositors.map( + ([a, n]) => + new DepositVote(this._Accounts.fromAddress(a), this._Chain.coins(n)), + ); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private _metadata: any; + public get metadata() { + return this._metadata; + } + + private _Chain: CosmosChain; + private _Accounts: CosmosAccounts; + private _Governance: CosmosGovernanceV1AtomOne; + + constructor( + ChainInfo: CosmosChain, + Accounts: CosmosAccounts, + Governance: CosmosGovernanceV1AtomOne, + data: ICosmosProposal, + ) { + super(ProposalType.CosmosProposal, data); + this._Chain = ChainInfo; + this._Accounts = Accounts; + this._Governance = Governance; + this.createdAt = data.submitTime; + this._Governance.store.add(this); + } + + public update() { + throw new Error('unimplemented'); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public updateMetadata(metadata: any) { + this._metadata = metadata; + if (!this.data.title) { + this.data.title = metadata.title; + } + if (!this.data.description) { + this.data.description = metadata.description || metadata.summary; + } + this._Governance.store.update(this); + } + + // eslint-disable-next-line @typescript-eslint/require-await + public async init() { + if (!this.initialized) { + this._initialized = true; + } + if (this.data.state.completed) { + // @ts-expect-error StrictNullChecks + super.complete(this._Governance.store); + } + } + + public async fetchDeposits(): Promise { + const proposalId = longify(this.data.identifier) as Long; + // @ts-expect-error StrictNullChecks + if (!isAtomoneLCD(this._Chain.lcd)) return; + const deposits = await this._Chain.lcd.atomone.gov.v1.deposits({ + proposalId, + }); + this.setDeposits(deposits); + return deposits; + } + + public async fetchTally(): Promise { + const proposalId = longify(this.data.identifier) as Long; + // @ts-expect-error StrictNullChecks + if (!isAtomoneLCD(this._Chain.lcd)) return; + const tally = await this._Chain.lcd.atomone.gov.v1.tallyResult({ + proposalId, + }); + this.setTally(tally); + return tally; + } + + public async fetchVotes(): Promise { + const proposalId = longify(this.data.identifier) as Long; + // @ts-expect-error StrictNullChecks + if (!isAtomoneLCD(this._Chain.lcd)) return; + const votes = await this._Chain.lcd.atomone.gov.v1.votes({ + proposalId, + }); + this.setVotes(votes); + return votes; + } + + public setDeposits(depositResp: QueryDepositsResponseSDKType) { + if (depositResp?.deposits) { + for (const deposit of depositResp.deposits) { + if (deposit.amount && deposit.amount[0]) { + this.data.state.depositors.push([ + deposit.depositor, + new BN(deposit.amount[0].amount), + ]); + } + } + } + } + + public setTally(tallyResp: QueryTallyResultResponseSDKType) { + if (tallyResp?.tally) { + this.data.state.tally = marshalTallyV1(tallyResp?.tally); + } + } + + public setVotes(votesResp: QueryVotesResponseSDKType) { + if (votesResp) { + for (const voter of votesResp.votes) { + const vote = voteToEnumV1(voter.options[0].option); + if (vote) { + this.data.state.voters.push([voter.voter, vote]); + this.addOrUpdateVote( + new CosmosVote(this._Accounts.fromAddress(voter.voter), vote), + ); + } else { + console.error( + `voter: ${voter.voter} has invalid vote option: ${voter.options[0].option}`, + ); + } + } + } + } + + // TODO: add getters for various vote features: tally, quorum, threshold, veto + // see: https://blog.chorus.one/an-overview-of-cosmos-hub-governance/ + get support() { + if (this.status === 'DepositPeriod') { + return this._Chain.coins(this.data.state.totalDeposit); + } + if (!this.data.state.tally) return 0; + const nonAbstainingPower = this.data.state.tally.no + .add(this.data.state.tally.noWithVeto) + .add(this.data.state.tally.yes); + if (nonAbstainingPower.eqn(0)) return 0; + const ratioPpm = this.data.state.tally.yes + .muln(1_000_000) + .div(nonAbstainingPower); + return +ratioPpm / 1_000_000; + } + + get turnout() { + if (this.status === 'DepositPeriod') { + if (this.data.state.totalDeposit.eqn(0) || !this._Chain.staked) { + return 0; + } else { + const ratioInPpm = +this.data.state.totalDeposit + .muln(1_000_000) + .div(this._Chain.staked); + return +ratioInPpm / 1_000_000; + } + } + if (!this.data.state.tally) return 0; + // all voters automatically abstain, so we compute turnout as the percent non-abstaining + const totalVotingPower = this.data.state.tally.no + .add(this.data.state.tally.noWithVeto) + .add(this.data.state.tally.yes) + .add(this.data.state.tally.abstain); + if (totalVotingPower.eqn(0)) return 0; + const ratioInPpm = +this.data.state.tally.abstain + .muln(1_000_000) + .div(totalVotingPower); + return 1 - ratioInPpm / 1_000_000; + } + + get veto() { + if (!this.data.state.tally) return 0; + const totalVotingPower = this.data.state.tally.no + .add(this.data.state.tally.noWithVeto) + .add(this.data.state.tally.yes) + .add(this.data.state.tally.abstain); + if (totalVotingPower.eqn(0)) return 0; + const ratioInPpm = +this.data.state.tally.noWithVeto + .muln(1_000_000) + .div(totalVotingPower); + return ratioInPpm / 1_000_000; + } + + get endTime(): ProposalEndTime { + // if in deposit period: at most create time + maxDepositTime + if (this.status === 'DepositPeriod') { + if (!this.data.depositEndTime) return { kind: 'unavailable' }; + return { kind: 'fixed', time: moment(this.data.depositEndTime) }; + } + // if in voting period: exactly voting start time + votingTime + if (!this.data.votingEndTime) return { kind: 'unavailable' }; + return { kind: 'fixed', time: moment(this.data.votingEndTime) }; + } + + get isPassing(): ProposalStatus { + switch (this.status) { + case 'Passed': + return ProposalStatus.Passed; + case 'Rejected': + return ProposalStatus.Failed; + case 'VotingPeriod': + return +this.support > 0.5 && this.veto <= 1 / 3 + ? ProposalStatus.Passing + : ProposalStatus.Failing; + case 'DepositPeriod': + return this._Governance.minDeposit + ? this.data.state.totalDeposit.gte(this._Governance.minDeposit) + ? ProposalStatus.Passing + : ProposalStatus.Failing + : ProposalStatus.None; + default: + return ProposalStatus.None; + } + } + + // TRANSACTIONS + public async submitDepositTx(depositor: CosmosAccount, amount: CosmosToken) { + if (this.status !== 'DepositPeriod') { + throw new Error('proposal not in deposit period'); + } + const cosm = await import('@cosmjs/stargate/build/queryclient'); + const msg: EncodeObject = { + typeUrl: '/atomone.gov.v1beta1.MsgDeposit', + value: { + proposalId: cosm.longify(this.data.identifier), + depositor: depositor.address, + amount: [amount.toCoinObject()], + }, + }; + await this._Chain.sendTx(depositor, msg); + this.data.state.depositors.push([depositor.address, new BN(+amount)]); + } + + public async voteTx(vote: CosmosVote) { + if (this.status !== 'VotingPeriod') { + throw new Error('proposal not in voting period'); + } + const msg = encodeMsgVote( + vote.account.address, + this.data.identifier, + vote.option, + ); + + await this._Chain.sendTx(vote.account, msg); + this.addOrUpdateVote(vote); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public submitVoteTx(vote: CosmosVote, memo = '', cb?): ITXModalData { + throw new Error('unsupported'); + } +} diff --git a/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/atomone/queries-v1.ts b/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/atomone/queries-v1.ts new file mode 100644 index 00000000000..108ceafa666 --- /dev/null +++ b/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/atomone/queries-v1.ts @@ -0,0 +1,142 @@ +import { ProposalStatus } from '@atomone/atomone-types-long/atomone/gov/v1beta1/gov'; +import { + QueryClientImpl, + QueryDepositResponse, + QueryDepositsResponse, + QueryParamsResponse, + QueryProposalResponse, + QueryProposalsResponse, + QueryTallyResultResponse, + QueryVoteResponse, + QueryVotesResponse, +} from '@atomone/atomone-types-long/atomone/gov/v1beta1/query'; +import { Uint64 } from '@cosmjs/math'; +import { + QueryClient, + createPagination, + createProtobufRpcClient, + longify, +} from '@cosmjs/stargate/build/queryclient'; +/* +import { + createPagination, + createProtobufRpcClient, + longify, + QueryClient, +} from '../../queryclient'; +*/ +export type GovParamsType = 'deposit' | 'tallying' | 'voting'; + +export type GovProposalId = string | number | Uint64; + +export interface AtomOneGovExtension { + readonly atomone: { + readonly params: ( + parametersType: GovParamsType, + ) => Promise; + readonly proposals: ( + proposalStatus: ProposalStatus, + depositor: string, + voter: string, + paginationKey?: Uint8Array, + ) => Promise; + readonly proposal: ( + proposalId: GovProposalId, + ) => Promise; + readonly deposits: ( + proposalId: GovProposalId, + paginationKey?: Uint8Array, + ) => Promise; + readonly deposit: ( + proposalId: GovProposalId, + depositorAddress: string, + ) => Promise; + readonly tally: ( + proposalId: GovProposalId, + ) => Promise; + readonly votes: ( + proposalId: GovProposalId, + paginationKey?: Uint8Array, + ) => Promise; + readonly vote: ( + proposalId: GovProposalId, + voterAddress: string, + ) => Promise; + }; +} + +export function setupAtomOneExtension(base: QueryClient): AtomOneGovExtension { + const rpc = createProtobufRpcClient(base); + + // Use this service to get easy typed access to query methods + // This cannot be used for proof verification + const queryService = new QueryClientImpl(rpc); + + return { + atomone: { + params: async (parametersType: GovParamsType) => { + const response = await queryService.Params({ + paramsType: parametersType, + }); + return response; + }, + proposals: async ( + proposalStatus: ProposalStatus, + depositorAddress: string, + voterAddress: string, + paginationKey?: Uint8Array, + ) => { + const response = await queryService.Proposals({ + proposalStatus, + depositor: depositorAddress, + voter: voterAddress, + pagination: createPagination(paginationKey), + }); + return response; + }, + proposal: async (proposalId: GovProposalId) => { + const response = await queryService.Proposal({ + proposalId: longify(proposalId), + }); + return response; + }, + deposits: async ( + proposalId: GovProposalId, + paginationKey?: Uint8Array, + ) => { + const response = await queryService.Deposits({ + proposalId: longify(proposalId), + pagination: createPagination(paginationKey), + }); + return response; + }, + deposit: async (proposalId: GovProposalId, depositorAddress: string) => { + const response = await queryService.Deposit({ + proposalId: longify(proposalId), + depositor: depositorAddress, + }); + return response; + }, + tally: async (proposalId: GovProposalId) => { + const response = await queryService.TallyResult({ + proposalId: longify(proposalId), + }); + return response; + }, + votes: async (proposalId: GovProposalId, paginationKey?: Uint8Array) => { + const response = await queryService.Votes({ + proposalId: longify(proposalId), + pagination: createPagination(paginationKey), + }); + return response; + }, + vote: async (proposalId: GovProposalId, voterAddress: string) => { + const response = await queryService.Vote({ + proposalId: longify(proposalId), + voter: voterAddress, + }); + return response; + }, + }, + }; +} diff --git a/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/atomone/utils-v1.ts b/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/atomone/utils-v1.ts new file mode 100644 index 00000000000..90f4c66346f --- /dev/null +++ b/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/atomone/utils-v1.ts @@ -0,0 +1,220 @@ +import { + ProposalSDKType, + ProposalStatus, + TallyResultSDKType, +} from '@atomone/atomone-types-long/atomone/gov/v1/gov'; +import BN from 'bn.js'; +import moment from 'moment'; +import type { AtomOneLCD } from '../../../../../../../shared/chain/types/cosmos'; +import type { + CosmosProposalState, + ICosmosProposal, + ICosmosProposalTally, +} from '../../types'; + +import { EncodeObject } from '@cosmjs/proto-signing'; +import { CosmosToken } from 'controllers/chain/cosmos/types'; +import { Any } from 'cosmjs-types/google/protobuf/any'; +import { isCompleted } from '../v1beta1/utils-v1beta1'; + +/* Governance helper methods for Cosmos chains with gov module v1 (as of Cosmos SDK v0.46.11) */ + +export const fetchProposalsByStatusV1AtomOne = async ( + lcd: AtomOneLCD, + status: ProposalStatus, +): Promise => { + try { + const { proposals: proposalsByStatus, pagination } = + await lcd.atomone.gov.v1.proposals({ + proposalStatus: status, + voter: '', + depositor: '', + }); + + let nextKey = pagination?.next_key; + + // @ts-expect-error StrictNullChecks + while (nextKey?.length > 0) { + // TODO: temp fix to handle chains that return nextKey as a string instead of Uint8Array + // Our v1 API needs to handle this better. To be addressed in #6610 + if (typeof nextKey === 'string') { + nextKey = new Uint8Array(Buffer.from(nextKey, 'base64')); + } + + const { proposals, pagination: nextPage } = + await lcd.atomone.gov.v1.proposals({ + proposalStatus: status, + voter: '', + depositor: '', + pagination: { + // @ts-expect-error StrictNullChecks + key: nextKey, + // @ts-expect-error StrictNullChecks + limit: undefined, + // @ts-expect-error StrictNullChecks + offset: undefined, + countTotal: true, + reverse: true, + }, + }); + proposalsByStatus.push(...proposals); + // @ts-expect-error StrictNullChecks + nextKey = nextPage.next_key; + } + return proposalsByStatus; + } catch (e) { + console.error(`Error fetching proposal by status ${status}`, e); + return []; + } +}; + +export const getActiveProposalsV1AtomOne = async ( + lcd: AtomOneLCD, +): Promise => { + const votingPeriodProposals = await fetchProposalsByStatusV1AtomOne( + lcd, + ProposalStatus.PROPOSAL_STATUS_VOTING_PERIOD, + ); + const depositPeriodProposals = await fetchProposalsByStatusV1AtomOne( + lcd, + ProposalStatus.PROPOSAL_STATUS_DEPOSIT_PERIOD, + ); + return sortProposalsV1AtomOne([ + ...votingPeriodProposals, + ...depositPeriodProposals, + ]); +}; + +export const getCompletedProposalsV1AtomOne = async ( + lcd: AtomOneLCD, +): Promise => { + const passedProposals = await fetchProposalsByStatusV1AtomOne( + lcd, + ProposalStatus.PROPOSAL_STATUS_PASSED, + ); + const failedProposals = await fetchProposalsByStatusV1AtomOne( + lcd, + ProposalStatus.PROPOSAL_STATUS_FAILED, + ); + const rejectedProposals = await fetchProposalsByStatusV1AtomOne( + lcd, + ProposalStatus.PROPOSAL_STATUS_REJECTED, + ); + const combined = [ + ...passedProposals, + ...failedProposals, + ...rejectedProposals, + ]; + return sortProposalsV1AtomOne(combined); +}; + +export const sortProposalsV1AtomOne = ( + proposals: ProposalSDKType[], +): ICosmosProposal[] => { + return proposals + .map((p) => propToIProposal(p)) + .filter((p): p is ICosmosProposal => !!p) + .sort((p1, p2) => +p2!.identifier - +p1!.identifier); +}; + +export const propToIProposal = (p: ProposalSDKType): ICosmosProposal | null => { + const status = stateEnumToStringV1(p.status.toString()); + const identifier = p.id.toString(); + let title = ''; + let description = ''; + let messages = []; + if (p.messages?.length > 0) { + // @ts-expect-error StrictNullChecks + messages = p.messages.map((m) => { + const content = m['content']; + // get title and description from 1st message if no top-level title/desc + if (!title) title = content?.title; + if (!description) description = content?.description; + return m; + }); + } + + return { + identifier, + type: 'text', + title, + description, + messages, + metadata: p.metadata, + // @ts-expect-error StrictNullChecks + submitTime: moment.unix(new Date(p.submit_time).valueOf() / 1000), + // @ts-expect-error StrictNullChecks + depositEndTime: moment.unix(new Date(p.deposit_end_time).valueOf() / 1000), + // @ts-expect-error StrictNullChecks + votingEndTime: moment.unix(new Date(p.voting_end_time).valueOf() / 1000), + votingStartTime: moment.unix( + // @ts-expect-error StrictNullChecks + new Date(p.voting_start_time).valueOf() / 1000, + ), + // @ts-expect-error StrictNullChecks + proposer: null, + state: { + identifier, + completed: isCompleted(status), + status, + // TODO: handle non-default amount + totalDeposit: + p.total_deposit && p.total_deposit[0] + ? new BN(p.total_deposit[0].amount) + : new BN(0), + depositors: [], + voters: [], + // @ts-expect-error StrictNullChecks + tally: p.final_tally_result && marshalTallyV1(p.final_tally_result), + }, + }; +}; + +const stateEnumToStringV1 = (status: string): CosmosProposalState => { + switch (status) { + case 'PROPOSAL_STATUS_UNSPECIFIED': + return 'Unspecified'; + case 'PROPOSAL_STATUS_DEPOSIT_PERIOD': + return 'DepositPeriod'; + case 'PROPOSAL_STATUS_VOTING_PERIOD': + return 'VotingPeriod'; + case 'PROPOSAL_STATUS_PASSED': + return 'Passed'; + case 'PROPOSAL_STATUS_FAILED': + return 'Failed'; + case 'PROPOSAL_STATUS_REJECTED': + return 'Rejected'; + case 'UNRECOGNIZED': + return 'Unrecognized'; + default: + throw new Error(`Invalid proposal state: ${status}`); + } +}; + +export const marshalTallyV1 = ( + tally: TallyResultSDKType, +): ICosmosProposalTally => { + // @ts-expect-error StrictNullChecks + if (!tally) return null; + return { + yes: new BN(tally.yes_count), + abstain: new BN(tally.abstain_count), + no: new BN(tally.no_count), + noWithVeto: new BN(0), + }; +}; + +export const encodeMsgSubmitProposalAtomOne = ( + sender: string, + initialDeposit: CosmosToken, + content: Any, +): EncodeObject => { + return { + typeUrl: '/atomone.gov.v1beta1.MsgSubmitProposal', + value: { + initialDeposit: [initialDeposit.toCoinObject()], + proposer: sender, + content, + }, + }; +}; diff --git a/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/govgen/utils-v1beta1.ts b/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/govgen/utils-v1beta1.ts index ac961301f2d..d93730681f1 100644 --- a/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/govgen/utils-v1beta1.ts +++ b/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/govgen/utils-v1beta1.ts @@ -251,7 +251,8 @@ export const getDepositParams = async ( ): Promise => { const govController = cosmosChain.governance as CosmosGovernanceGovgen; let minDeposit; - const { depositParams } = await cosmosChain.chain.api.gov.params('deposit'); + const { depositParams } = + await cosmosChain.chain.api.govgen.params('deposit'); // TODO: support off-denom deposits // @ts-expect-error StrictNullChecks diff --git a/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/utils.ts b/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/utils.ts index 56f2a933227..3b34b542c48 100644 --- a/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/utils.ts +++ b/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/utils.ts @@ -1,5 +1,13 @@ import { CosmosGovernanceVersion } from '@hicommonwealth/shared'; +import { LCD } from 'shared/chain/types/cosmos'; import Cosmos from '../adapter'; +import { isAtomoneLCD } from '../chain'; +import CosmosGovernanceV1AtomOne from './atomone/governance-v1'; +import { CosmosProposalV1AtomOne } from './atomone/proposal-v1'; +import { + getActiveProposalsV1AtomOne, + getCompletedProposalsV1AtomOne, +} from './atomone/utils-v1'; import CosmosGovernanceGovgen from './govgen/governance-v1beta1'; import { CosmosProposalGovgen } from './govgen/proposal-v1beta1'; import { @@ -24,6 +32,8 @@ export const getCompletedProposals = async ( ): Promise => { const { chain, accounts, governance, meta } = cosmosChain; console.log(cosmosChain); + const isAtomone = + meta.ChainNode?.cosmos_gov_version === CosmosGovernanceVersion.v1atomone; const isGovgen = meta.ChainNode?.cosmos_gov_version === CosmosGovernanceVersion.v1beta1govgen; @@ -34,7 +44,19 @@ export const getCompletedProposals = async ( CosmosGovernanceVersion.v1beta1Failed; let cosmosProposals = []; - if (isGovgen) { + if (isAtomone && isAtomoneLCD(chain.lcd)) { + const v1proposals = await getCompletedProposalsV1AtomOne(chain.lcd); + // @ts-expect-error StrictNullChecks + cosmosProposals = v1proposals.map( + (p) => + new CosmosProposalV1AtomOne( + chain, + accounts, + governance as CosmosGovernanceV1AtomOne, + p, + ), + ); + } else if (isGovgen) { const v1Beta1Proposals = await getCompletedProposalsGovgen(chain.api); // @ts-expect-error StrictNullChecks cosmosProposals = v1Beta1Proposals.map( @@ -47,7 +69,7 @@ export const getCompletedProposals = async ( ), ); } else if (!isGovgen && (isV1 || betaAttemptFailed)) { - const v1Proposals = await getCompletedProposalsV1(chain.lcd); + const v1Proposals = await getCompletedProposalsV1(chain.lcd as LCD); // @ts-expect-error StrictNullChecks cosmosProposals = v1Proposals.map( @@ -78,6 +100,8 @@ export const getActiveProposals = async ( cosmosChain: Cosmos, ): Promise => { const { chain, accounts, governance, meta } = cosmosChain; + const isAtomone = + meta.ChainNode?.cosmos_gov_version === CosmosGovernanceVersion.v1atomone; const isGovgen = meta.ChainNode?.cosmos_gov_version === CosmosGovernanceVersion.v1beta1govgen; @@ -87,15 +111,30 @@ export const getActiveProposals = async ( meta.ChainNode?.cosmos_gov_version === CosmosGovernanceVersion.v1beta1Failed; let cosmosProposals = []; - - if (isGovgen) { + if (isAtomone && isAtomoneLCD(chain.lcd)) { + const v1Proposals = await getActiveProposalsV1AtomOne(chain.lcd); + // @ts-expect-error StrictNullChecks + cosmosProposals = v1Proposals.map( + (p) => + new CosmosProposalV1AtomOne( + chain, + accounts, + governance as CosmosGovernanceV1AtomOne, + p, + ), + ); + } else if (isGovgen) { const v1Beta1Proposals = await getActiveProposalsGovgen(chain.api); // @ts-expect-error StrictNullChecks cosmosProposals = v1Beta1Proposals.map( (p) => new CosmosProposal(chain, accounts, governance as CosmosGovernance, p), ); - } else if (!isGovgen && (isV1 || betaAttemptFailed)) { + } else if ( + !isGovgen && + (isV1 || betaAttemptFailed) && + !isAtomoneLCD(chain.lcd) + ) { const v1Proposals = await getActiveProposalsV1(chain.lcd); // @ts-expect-error StrictNullChecks cosmosProposals = v1Proposals.map( diff --git a/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/v1/governance-v1.ts b/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/v1/governance-v1.ts index d2df65635d4..deee76e7b8f 100644 --- a/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/v1/governance-v1.ts +++ b/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/v1/governance-v1.ts @@ -8,7 +8,7 @@ import { ITXModalData } from 'models/interfaces'; import type CosmosAccount from '../../account'; import type CosmosAccounts from '../../accounts'; import type CosmosChain from '../../chain'; -import type { CosmosApiType } from '../../chain'; +import { isAtomoneLCD, type CosmosApiType } from '../../chain'; import { encodeMsgSubmitProposal } from '../v1beta1/utils-v1beta1'; import { CosmosProposalV1 } from './proposal-v1'; import { propToIProposal } from './utils-v1'; @@ -62,6 +62,8 @@ class CosmosGovernanceV1 extends ProposalModule< try { // @ts-expect-error StrictNullChecks if (!proposalId) return; + // @ts-expect-error StrictNullChecks + if (isAtomoneLCD(this._Chain.lcd)) return; const { proposal } = await this._Chain.lcd.cosmos.gov.v1.proposal({ proposalId: numberToLong(proposalId), }); diff --git a/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/v1/proposal-v1.ts b/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/v1/proposal-v1.ts index def3c91bcb5..2995d61df01 100644 --- a/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/v1/proposal-v1.ts +++ b/packages/commonwealth/client/scripts/controllers/chain/cosmos/gov/v1/proposal-v1.ts @@ -27,7 +27,7 @@ import moment from 'moment'; import CosmosAccount from '../../account'; import type CosmosAccounts from '../../accounts'; import type CosmosChain from '../../chain'; -import type { CosmosApiType } from '../../chain'; +import { isAtomoneLCD, type CosmosApiType } from '../../chain'; import { CosmosVote } from '../v1beta1/proposal-v1beta1'; import { encodeMsgVote } from '../v1beta1/utils-v1beta1'; import type CosmosGovernanceV1 from './governance-v1'; @@ -155,6 +155,8 @@ export class CosmosProposalV1 extends Proposal< public async fetchDeposits(): Promise { const proposalId = longify(this.data.identifier) as Long; + // @ts-expect-error StrictNullChecks + if (isAtomoneLCD(this._Chain.lcd)) return; const deposits = await this._Chain.lcd.cosmos.gov.v1.deposits({ proposalId, }); @@ -164,6 +166,8 @@ export class CosmosProposalV1 extends Proposal< public async fetchTally(): Promise { const proposalId = longify(this.data.identifier) as Long; + // @ts-expect-error StrictNullChecks + if (isAtomoneLCD(this._Chain.lcd)) return; const tally = await this._Chain.lcd.cosmos.gov.v1.tallyResult({ proposalId, }); @@ -173,6 +177,8 @@ export class CosmosProposalV1 extends Proposal< public async fetchVotes(): Promise { const proposalId = longify(this.data.identifier) as Long; + // @ts-expect-error StrictNullChecks + if (isAtomoneLCD(this._Chain.lcd)) return; const votes = await this._Chain.lcd.cosmos.gov.v1.votes({ proposalId }); this.setVotes(votes); return votes; diff --git a/packages/commonwealth/client/scripts/state/api/chainParams/fetchDepositParams.ts b/packages/commonwealth/client/scripts/state/api/chainParams/fetchDepositParams.ts index fd403069f45..ec1177007e2 100644 --- a/packages/commonwealth/client/scripts/state/api/chainParams/fetchDepositParams.ts +++ b/packages/commonwealth/client/scripts/state/api/chainParams/fetchDepositParams.ts @@ -1,6 +1,7 @@ import { ChainBase } from '@hicommonwealth/shared'; import { useQuery } from '@tanstack/react-query'; import Cosmos from 'controllers/chain/cosmos/adapter'; +import { getDepositParams as getGovgenDepositParams } from 'controllers/chain/cosmos/gov/govgen/utils-v1beta1'; import { CosmosDepositParams, getDepositParams, @@ -12,16 +13,22 @@ const DEPOSIT_PARAMS_STALE_TIME = 1000 * 60 * 15; const fetchDepositParams = async ( stakingDenom: string, + isGovgen: boolean = false, ): Promise => { - return getDepositParams(app.chain as Cosmos, stakingDenom); + return isGovgen + ? getGovgenDepositParams(app.chain as Cosmos, stakingDenom) + : getDepositParams(app.chain as Cosmos, stakingDenom); }; -const useDepositParamsQuery = (stakingDenom: string) => { +const useDepositParamsQuery = ( + stakingDenom: string, + isGovgen: boolean = false, +) => { const communityId = app.activeChainId(); return useQuery({ // fetchDepositParams depends on stakingDenom being defined - queryKey: ['depositParams', communityId, stakingDenom], - queryFn: () => fetchDepositParams(stakingDenom), + queryKey: ['depositParams', communityId, stakingDenom, isGovgen], + queryFn: () => fetchDepositParams(stakingDenom, isGovgen), enabled: app.chain?.base === ChainBase.CosmosSDK && !!stakingDenom, cacheTime: DEPOSIT_PARAMS_CACHE_TIME, staleTime: DEPOSIT_PARAMS_STALE_TIME, diff --git a/packages/commonwealth/client/scripts/state/api/proposals/cosmos/fetchCosmosProposal.ts b/packages/commonwealth/client/scripts/state/api/proposals/cosmos/fetchCosmosProposal.ts index 90cdffbf1b8..005e848b4ad 100644 --- a/packages/commonwealth/client/scripts/state/api/proposals/cosmos/fetchCosmosProposal.ts +++ b/packages/commonwealth/client/scripts/state/api/proposals/cosmos/fetchCosmosProposal.ts @@ -1,5 +1,6 @@ import { ChainBase } from '@hicommonwealth/shared'; import { useQuery } from '@tanstack/react-query'; +import { CosmosProposalV1AtomOne } from 'client/scripts/controllers/chain/cosmos/gov/atomone/proposal-v1'; import { CosmosProposalGovgen } from 'client/scripts/controllers/chain/cosmos/gov/govgen/proposal-v1beta1'; import Cosmos from 'controllers/chain/cosmos/adapter'; import { CosmosProposalV1 } from 'controllers/chain/cosmos/gov/v1/proposal-v1'; @@ -11,7 +12,12 @@ const PROPOSAL_STALE_TIME = 1000 * 10; const fetchCosmosProposal = async ( proposalId: string, -): Promise => { +): Promise< + | CosmosProposal + | CosmosProposalV1 + | CosmosProposalGovgen + | CosmosProposalV1AtomOne +> => { const { governance } = app.chain as Cosmos; return governance.getProposal(+proposalId); }; diff --git a/packages/commonwealth/client/scripts/views/components/proposals/proposal_extensions.tsx b/packages/commonwealth/client/scripts/views/components/proposals/proposal_extensions.tsx index b71f345a5b2..2405da6a335 100644 --- a/packages/commonwealth/client/scripts/views/components/proposals/proposal_extensions.tsx +++ b/packages/commonwealth/client/scripts/views/components/proposals/proposal_extensions.tsx @@ -5,6 +5,8 @@ import './proposal_extensions.scss'; import app from 'state'; +import { CosmosProposalV1AtomOne } from 'client/scripts/controllers/chain/cosmos/gov/atomone/proposal-v1'; +import { CosmosProposalGovgen } from 'client/scripts/controllers/chain/cosmos/gov/govgen/proposal-v1beta1'; import Cosmos from 'controllers/chain/cosmos/adapter'; import { CosmosProposalV1 } from 'controllers/chain/cosmos/gov/v1/proposal-v1'; import { CosmosProposal } from 'controllers/chain/cosmos/gov/v1beta1/proposal-v1beta1'; @@ -26,8 +28,17 @@ type ProposalExtensionsProps = { export const ProposalExtensions = (props: ProposalExtensionsProps) => { const { setCosmosDepositAmount, setDemocracyVoteAmount, proposal } = props; const { data: stakingDenom } = useStakingParamsQuery(); - // @ts-expect-error - const { data: cosmosDepositParams } = useDepositParamsQuery(stakingDenom); + + let isGovgen = false; + if (proposal instanceof CosmosProposalGovgen) { + isGovgen = true; + } + + const { data: cosmosDepositParams } = useDepositParamsQuery( + // @ts-expect-error + stakingDenom, + isGovgen, + ); useEffect(() => { if (setDemocracyVoteAmount) setDemocracyVoteAmount(0); @@ -39,7 +50,9 @@ export const ProposalExtensions = (props: ProposalExtensionsProps) => { if ( (proposal instanceof CosmosProposal || - proposal instanceof CosmosProposalV1) && + proposal instanceof CosmosProposalV1 || + proposal instanceof CosmosProposalGovgen || + proposal instanceof CosmosProposalV1AtomOne) && proposal.status === 'DepositPeriod' ) { const cosmos = app.chain as Cosmos; diff --git a/packages/commonwealth/client/scripts/views/components/proposals/voting_actions.tsx b/packages/commonwealth/client/scripts/views/components/proposals/voting_actions.tsx index b2760abe653..3951448d006 100644 --- a/packages/commonwealth/client/scripts/views/components/proposals/voting_actions.tsx +++ b/packages/commonwealth/client/scripts/views/components/proposals/voting_actions.tsx @@ -16,6 +16,7 @@ import './voting_actions.scss'; import app from 'state'; import { getChainDecimals } from 'client/scripts/controllers/app/webWallets/utils'; +import { CosmosProposalV1AtomOne } from 'client/scripts/controllers/chain/cosmos/gov/atomone/proposal-v1'; import { CosmosProposalGovgen } from 'client/scripts/controllers/chain/cosmos/gov/govgen/proposal-v1beta1'; import useUserStore from 'state/ui/user'; import { naturalDenomToMinimal } from '../../../../../shared/utils'; @@ -62,7 +63,8 @@ export const VotingActions = ({ if ( proposal instanceof CosmosProposal || proposal instanceof CosmosProposalV1 || - proposal instanceof CosmosProposalGovgen + proposal instanceof CosmosProposalGovgen || + proposal instanceof CosmosProposalV1AtomOne ) { user = userData.activeAccount as CosmosAccount; } else { @@ -79,7 +81,9 @@ export const VotingActions = ({ if ( proposal instanceof CosmosProposal || - proposal instanceof CosmosProposalV1 + proposal instanceof CosmosProposalV1 || + proposal instanceof CosmosProposalGovgen || + proposal instanceof CosmosProposalV1AtomOne ) { if (proposal.status === 'DepositPeriod') { const chain = app.chain as Cosmos; @@ -119,7 +123,9 @@ export const VotingActions = ({ if ( proposal instanceof CosmosProposal || - proposal instanceof CosmosProposalV1 + proposal instanceof CosmosProposalV1 || + proposal instanceof CosmosProposalGovgen || + proposal instanceof CosmosProposalV1AtomOne ) { try { await proposal.voteTx(new CosmosVote(user, 'No')); @@ -143,7 +149,9 @@ export const VotingActions = ({ if ( proposal instanceof CosmosProposal || - proposal instanceof CosmosProposalV1 + proposal instanceof CosmosProposalV1 || + proposal instanceof CosmosProposalGovgen || + proposal instanceof CosmosProposalV1AtomOne ) { proposal .voteTx(new CosmosVote(user, 'Abstain')) @@ -161,7 +169,9 @@ export const VotingActions = ({ if ( proposal instanceof CosmosProposal || - proposal instanceof CosmosProposalV1 + proposal instanceof CosmosProposalV1 || + proposal instanceof CosmosProposalGovgen || + proposal instanceof CosmosProposalV1AtomOne ) { proposal .voteTx(new CosmosVote(user, 'NoWithVeto')) diff --git a/packages/commonwealth/package.json b/packages/commonwealth/package.json index e579e10fc4b..bc93ce2a997 100644 --- a/packages/commonwealth/package.json +++ b/packages/commonwealth/package.json @@ -109,12 +109,12 @@ "@hicommonwealth/adapters": "workspace:*", "@hicommonwealth/chains": "workspace:*", "@hicommonwealth/core": "workspace:*", + "@hicommonwealth/evm-protocols": "workspace:*", "@hicommonwealth/evm-testing": "workspace:*", "@hicommonwealth/model": "workspace:*", "@hicommonwealth/schemas": "workspace:*", "@hicommonwealth/shared": "workspace:*", "@hicommonwealth/sitemaps": "workspace:*", - "@hicommonwealth/evm-protocols": "workspace:*", "@hookform/resolvers": "^3.3.1", "@ipld/dag-json": "^10.2.0", "@keplr-wallet/types": "^0.12.23", @@ -285,6 +285,7 @@ "zustand": "^4.3.8" }, "devDependencies": { + "@atomone/atomone-types-long": "^1.0.3", "@ethersproject/keccak256": "5.7.0", "@types/express": "^4.17.21", "@types/passport": "^1.0.16", diff --git a/packages/commonwealth/shared/chain/types/cosmos.ts b/packages/commonwealth/shared/chain/types/cosmos.ts index cf1caf3c8c5..b5d0b8704f8 100644 --- a/packages/commonwealth/shared/chain/types/cosmos.ts +++ b/packages/commonwealth/shared/chain/types/cosmos.ts @@ -1,4 +1,4 @@ -import { GovV1Client } from '@hicommonwealth/chains'; +import { GovV1AtomOneClient, GovV1Client } from '@hicommonwealth/chains'; // currently just used for gov v1, but this can be expanded export type LCD = { @@ -8,3 +8,10 @@ export type LCD = { }; }; }; +export type AtomOneLCD = { + atomone: { + gov: { + v1: GovV1AtomOneClient; + }; + }; +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8ba7ed69a68..0096e7c997d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -434,6 +434,9 @@ importers: specifier: ^6.1.13 version: 6.11.4 devDependencies: + '@atomone/atomone-types-long': + specifier: ^1.0.3 + version: 1.0.3 tsx: specifier: ^4.7.2 version: 4.9.3 @@ -1401,6 +1404,9 @@ importers: specifier: ^4.3.8 version: 4.5.2(@types/react@18.3.3)(react@18.3.1) devDependencies: + '@atomone/atomone-types-long': + specifier: ^1.0.3 + version: 1.0.3 '@ethersproject/keccak256': specifier: 5.7.0 version: 5.7.0 @@ -1502,6 +1508,9 @@ packages: resolution: {integrity: sha512-UQFQ6SgyJ6LX42W8rHCs8KVc0JS0tzVL9ct4XYedJukskYVWTo49tNiMEK9C2HTyarbNiT/RVIRSY82vH+6sTg==} engines: {node: '>=4'} + '@atomone/atomone-types-long@1.0.3': + resolution: {integrity: sha512-UvNQpoiGThQfF66/qWZZZCLrOmYZSpraWtwi7jjdcOdit6U6xYDpqRvc96LQCYYcXCjfoxjglS1eG5DMpiHaDA==} + '@atomone/govgen-types-long@0.3.9': resolution: {integrity: sha512-TcjEuvqWXuOegAqpBbZt1HUX6CePZtrNmj2ZNxxv7AFlpjNVK47pcrqFH9zErwCGnPVL4lxawu7eZRlGO2CRqw==} @@ -3068,6 +3077,9 @@ packages: '@cosmjs/utils@0.32.3': resolution: {integrity: sha512-WCZK4yksj2hBDz4w7xFZQTRZQ/RJhBX26uFHmmQFIcNUUVAihrLO+RerqJgk0dZqC42wstM9pEUQGtPmLcIYvg==} + '@cosmology/lcd@0.14.0': + resolution: {integrity: sha512-qy/q56NWpoQfyUX3nl80YjxKMf70QKKQ1Rh9EqJO9OMysj48UeLPcxEWwv0jfOBzPsCX/Jj8Ha6Sb1McFVSk9g==} + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} @@ -6605,24 +6617,12 @@ packages: resolution: {integrity: sha512-KV321z5m/0nuAg83W1dPLy85HpHDk7Sdi4fJbwvacWsEhAh+rZUW4ZfGcXmUIvjZg4ss2bcwNlRhJ7GBEUG08w==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - '@sinonjs/commons@2.0.0': - resolution: {integrity: sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==} - '@sinonjs/commons@3.0.1': resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} - '@sinonjs/fake-timers@11.2.2': - resolution: {integrity: sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==} - - '@sinonjs/samsam@8.0.0': - resolution: {integrity: sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==} - - '@sinonjs/text-encoding@0.7.2': - resolution: {integrity: sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==} - '@smithy/abort-controller@3.0.0': resolution: {integrity: sha512-p6GlFGBt9K4MYLu72YuJ523NVR4A8oHlC5M2JO6OmQqN8kAc/uh1JqLE+FizTokrSJGg0CSvC+BrsmGzKtsZKA==} engines: {node: '>=16.0.0'} @@ -7525,12 +7525,6 @@ packages: '@types/serve-static@1.15.7': resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} - '@types/sinon@17.0.3': - resolution: {integrity: sha512-j3uovdn8ewky9kRBG19bOwaZbexJu/XjtkHyjvUgt4xfPFz18dcORIMqnYh66Fx3Powhcr85NT5+er3+oViapw==} - - '@types/sinonjs__fake-timers@8.1.5': - resolution: {integrity: sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==} - '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} @@ -8340,6 +8334,9 @@ packages: axios@1.6.8: resolution: {integrity: sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==} + axios@1.7.4: + resolution: {integrity: sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==} + axios@1.7.5: resolution: {integrity: sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==} @@ -11846,9 +11843,6 @@ packages: resolution: {integrity: sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ==} engines: {node: '>=12.20'} - just-extend@6.2.0: - resolution: {integrity: sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==} - jwa@1.4.1: resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} @@ -12811,9 +12805,6 @@ packages: nice-try@1.0.5: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} - nise@5.1.9: - resolution: {integrity: sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==} - no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} @@ -13341,9 +13332,6 @@ packages: path-to-regexp@3.3.0: resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} - path-to-regexp@6.2.2: - resolution: {integrity: sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==} - path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} @@ -14824,10 +14812,6 @@ packages: simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} - sinon@17.0.2: - resolution: {integrity: sha512-uihLiaB9FhzesElPDFZA7hDcNABzsVHwr3YfmM9sBllVwab3l0ltGlRV1XhpNfIacNDLGD1QRZNLs5nU5+hTuA==} - deprecated: There - sirv@2.0.4: resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} engines: {node: '>= 10'} @@ -16918,6 +16902,12 @@ snapshots: '@arr/every@1.0.1': {} + '@atomone/atomone-types-long@1.0.3': + dependencies: + '@cosmology/lcd': 0.14.0 + transitivePeerDependencies: + - debug + '@atomone/govgen-types-long@0.3.9': {} '@aws-crypto/crc32@3.0.0': @@ -20412,6 +20402,12 @@ snapshots: '@cosmjs/utils@0.32.3': {} + '@cosmology/lcd@0.14.0': + dependencies: + axios: 1.7.4 + transitivePeerDependencies: + - debug + '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 @@ -25422,10 +25418,6 @@ snapshots: '@sindresorhus/fnv1a@3.1.0': {} - '@sinonjs/commons@2.0.0': - dependencies: - type-detect: 4.0.8 - '@sinonjs/commons@3.0.1': dependencies: type-detect: 4.0.8 @@ -25434,18 +25426,6 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 - '@sinonjs/fake-timers@11.2.2': - dependencies: - '@sinonjs/commons': 3.0.1 - - '@sinonjs/samsam@8.0.0': - dependencies: - '@sinonjs/commons': 2.0.0 - lodash.get: 4.4.2 - type-detect: 4.0.8 - - '@sinonjs/text-encoding@0.7.2': {} - '@smithy/abort-controller@3.0.0': dependencies: '@smithy/types': 3.0.0 @@ -26753,12 +26733,6 @@ snapshots: '@types/node': 20.12.10 '@types/send': 0.17.4 - '@types/sinon@17.0.3': - dependencies: - '@types/sinonjs__fake-timers': 8.1.5 - - '@types/sinonjs__fake-timers@8.1.5': {} - '@types/stack-utils@2.0.3': {} '@types/superagent@4.1.13': @@ -28567,6 +28541,14 @@ snapshots: transitivePeerDependencies: - debug + axios@1.7.4: + dependencies: + follow-redirects: 1.15.6 + form-data: 4.0.1 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + axios@1.7.5: dependencies: follow-redirects: 1.15.6 @@ -30940,7 +30922,7 @@ snapshots: ethereumjs-util@4.5.1: dependencies: - bn.js: 4.12.0 + bn.js: 4.12.1 create-hash: 1.2.0 elliptic: 6.5.5 ethereum-cryptography: 0.1.3 @@ -33152,8 +33134,6 @@ snapshots: junk@4.0.1: {} - just-extend@6.2.0: {} - jwa@1.4.1: dependencies: buffer-equal-constant-time: 1.0.1 @@ -34640,14 +34620,6 @@ snapshots: nice-try@1.0.5: {} - nise@5.1.9: - dependencies: - '@sinonjs/commons': 3.0.1 - '@sinonjs/fake-timers': 11.2.2 - '@sinonjs/text-encoding': 0.7.2 - just-extend: 6.2.0 - path-to-regexp: 6.2.2 - no-case@3.0.4: dependencies: lower-case: 2.0.2 @@ -35321,8 +35293,6 @@ snapshots: path-to-regexp@3.3.0: {} - path-to-regexp@6.2.2: {} - path-to-regexp@6.3.0: {} path-type@3.0.0: @@ -37157,15 +37127,6 @@ snapshots: dependencies: is-arrayish: 0.3.2 - sinon@17.0.2: - dependencies: - '@sinonjs/commons': 3.0.1 - '@sinonjs/fake-timers': 11.2.2 - '@sinonjs/samsam': 8.0.0 - diff: 5.2.0 - nise: 5.1.9 - supports-color: 7.2.0 - sirv@2.0.4: dependencies: '@polka/url': 1.0.0-next.25