diff --git a/apps/governance/.env b/apps/governance/.env index 2c6151228..a616f4faa 100644 --- a/apps/governance/.env +++ b/apps/governance/.env @@ -34,3 +34,4 @@ NX_SUCCESSOR_MARKETS=true NX_METAMASK_SNAPS=true NX_PRODUCT_PERPETUALS=false NX_UPDATE_MARKET_STATE=false +NX_GOVERNANCE_TRANSFERS=false diff --git a/apps/governance/.env.stagnet1 b/apps/governance/.env.stagnet1 index 708b234db..589886a1d 100644 --- a/apps/governance/.env.stagnet1 +++ b/apps/governance/.env.stagnet1 @@ -22,3 +22,4 @@ NX_SUCCESSOR_MARKETS=true NX_METAMASK_SNAPS=true NX_PRODUCT_PERPETUALS=true NX_UPDATE_MARKET_STATE=true +NX_GOVERNANCE_TRANSFERS=true diff --git a/apps/governance/src/i18n/translations/dev.json b/apps/governance/src/i18n/translations/dev.json index 1d573d970..d64845619 100644 --- a/apps/governance/src/i18n/translations/dev.json +++ b/apps/governance/src/i18n/translations/dev.json @@ -888,5 +888,7 @@ "HowToPropose": "How to make a proposal", "HowToProposeRawStep1": "1. Sense check your proposal with the community on the forum:", "HowToProposeRawStep2": "2. Use the appropriate proposal template in the docs:", - "HowToProposeRawStep3": "3. Submit on-chain below" + "HowToProposeRawStep3": "3. Submit on-chain below", + "proposalTransferDetails": "New governance transfer details", + "proposalCancelTransferDetails": "Cancel governance transfer details" } diff --git a/apps/governance/src/routes/proposals/components/proposal-detail-header/proposal-header.tsx b/apps/governance/src/routes/proposals/components/proposal-detail-header/proposal-header.tsx index 1fb61ccbf..7de9d967f 100644 --- a/apps/governance/src/routes/proposals/components/proposal-detail-header/proposal-header.tsx +++ b/apps/governance/src/routes/proposals/components/proposal-detail-header/proposal-header.tsx @@ -7,12 +7,17 @@ import type { ProposalQuery } from '../../proposal/__generated__/Proposal'; import { truncateMiddle } from '../../../../lib/truncate-middle'; import { CurrentProposalState } from '../current-proposal-state'; import { ProposalInfoLabel } from '../proposal-info-label'; -import { useSuccessorMarketProposalDetails } from '@vegaprotocol/proposals'; +import { + useCancelTransferProposalDetails, + useNewTransferProposalDetails, + useSuccessorMarketProposalDetails, +} from '@vegaprotocol/proposals'; import { FLAGS } from '@vegaprotocol/environment'; import Routes from '../../../routes'; import { Link } from 'react-router-dom'; import type { VoteState } from '../vote-details/use-user-vote'; import { VoteBreakdown } from '../vote-breakdown'; +import { GovernanceTransferKindMapping } from '@vegaprotocol/types'; export const ProposalHeader = ({ proposal, @@ -147,6 +152,20 @@ export const ProposalHeader = ({ ); break; } + case 'NewTransfer': + proposalType = 'NewTransfer'; + fallbackTitle = t('NewTransferProposal'); + details = FLAGS.GOVERNANCE_TRANSFERS ? ( + + ) : null; + break; + case 'CancelTransfer': + proposalType = 'CancelTransfer'; + fallbackTitle = t('CancelTransferProposal'); + details = FLAGS.GOVERNANCE_TRANSFERS ? ( + + ) : null; + break; } return ( @@ -224,3 +243,36 @@ const SuccessorCode = ({ proposalId }: { proposalId?: string | null }) => { ) : null; }; + +const NewTransferSummary = ({ proposalId }: { proposalId?: string | null }) => { + const { t } = useTranslation(); + const details = useNewTransferProposalDetails(proposalId); + + if (!details) return null; + + return ( + + {GovernanceTransferKindMapping[details.kind.__typename]}{' '} + {t('transfer from')} {truncateMiddle(details.source)}{' '} + {t('to')} {truncateMiddle(details.destination)} + + ); +}; + +const CancelTransferSummary = ({ + proposalId, +}: { + proposalId?: string | null; +}) => { + const { t } = useTranslation(); + const details = useCancelTransferProposalDetails(proposalId); + + if (!details) return null; + + return ( + + {t('Cancel transfer: ')}{' '} + {truncateMiddle(details.transferId)} + + ); +}; diff --git a/apps/governance/src/routes/proposals/components/proposal-transfer/index.ts b/apps/governance/src/routes/proposals/components/proposal-transfer/index.ts new file mode 100644 index 000000000..8dce32b4e --- /dev/null +++ b/apps/governance/src/routes/proposals/components/proposal-transfer/index.ts @@ -0,0 +1,2 @@ +export * from './proposal-transfer-details'; +export * from './proposal-cancel-transfer-details'; diff --git a/apps/governance/src/routes/proposals/components/proposal-transfer/proposal-cancel-transfer-details.tsx b/apps/governance/src/routes/proposals/components/proposal-transfer/proposal-cancel-transfer-details.tsx new file mode 100644 index 000000000..6f51bd39b --- /dev/null +++ b/apps/governance/src/routes/proposals/components/proposal-transfer/proposal-cancel-transfer-details.tsx @@ -0,0 +1,37 @@ +import { useTranslation } from 'react-i18next'; +import type { ProposalQuery } from '../../proposal/__generated__/Proposal'; +import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals'; +import { useCancelTransferProposalDetails } from '@vegaprotocol/proposals'; +import { + KeyValueTable, + KeyValueTableRow, + RoundedWrapper, +} from '@vegaprotocol/ui-toolkit'; +import { SubHeading } from '../../../../components/heading'; + +export const ProposalCancelTransferDetails = ({ + proposal, +}: { + proposal: ProposalFieldsFragment | ProposalQuery['proposal']; +}) => { + const { t } = useTranslation(); + const details = useCancelTransferProposalDetails(proposal?.id); + + if (!details) { + return null; + } + + return ( + <> + + + + + {t('transferId')} + {details.transferId} + + + + + ); +}; diff --git a/apps/governance/src/routes/proposals/components/proposal-transfer/proposal-transfer-details.tsx b/apps/governance/src/routes/proposals/components/proposal-transfer/proposal-transfer-details.tsx new file mode 100644 index 000000000..59d8c5314 --- /dev/null +++ b/apps/governance/src/routes/proposals/components/proposal-transfer/proposal-transfer-details.tsx @@ -0,0 +1,145 @@ +import { useState } from 'react'; +import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals'; +import type { ProposalQuery } from '../../proposal/__generated__/Proposal'; +import { CollapsibleToggle } from '../../../../components/collapsible-toggle'; +import { SubHeading } from '../../../../components/heading'; +import { useTranslation } from 'react-i18next'; +import { + KeyValueTable, + KeyValueTableRow, + RoundedWrapper, + Tooltip, +} from '@vegaprotocol/ui-toolkit'; +import { useNewTransferProposalDetails } from '@vegaprotocol/proposals'; +import { + AccountTypeMapping, + DescriptionGovernanceTransferTypeMapping, + GovernanceTransferKindMapping, + GovernanceTransferTypeMapping, +} from '@vegaprotocol/types'; +import { + addDecimalsFormatNumberQuantum, + formatDateWithLocalTimezone, +} from '@vegaprotocol/utils'; + +export const ProposalTransferDetails = ({ + proposal, +}: { + proposal: ProposalFieldsFragment | ProposalQuery['proposal']; +}) => { + const { t } = useTranslation(); + const [show, setShow] = useState(false); + + const details = useNewTransferProposalDetails(proposal?.id); + if (!details) { + return null; + } + + return ( + <> + + + + {show && ( + + + {/* The source account */} + + {t('Source')} + {details.source} + + + {/* The type of source account */} + + {t('Source Type')} + {AccountTypeMapping[details.sourceType]} + + + {/* The destination account */} + + {t('Destination')} + {details.destination} + + + {/* The type of destination account */} + + {t('Destination Type')} + {AccountTypeMapping[details.destinationType]} + + + {/* The asset to transfer */} + + {t('Asset')} + {details.asset.symbol} + + + {/*The fraction of the balance to be transfer */} + + {t('Fraction Of Balance')} + {`${Number(details.fraction_of_balance) * 100}%`} + + + {/* The maximum amount to be transferred */} + + {t('Amount')} + {addDecimalsFormatNumberQuantum( + details.amount, + details.asset.decimals, + details.asset.quantum + )} + + + {/* The type of the governance transfer */} + + {t('Transfer Type')} + + + {GovernanceTransferTypeMapping[details.transferType]} + + + + + {/* The type of governance transfer being made, i.e. a one-off or recurring trans */} + + {t('Kind')} + {GovernanceTransferKindMapping[details.kind.__typename]} + + + {details.kind.__typename === 'OneOffGovernanceTransfer' && + details.kind.deliverOn && ( + + {t('Deliver On')} + {formatDateWithLocalTimezone( + new Date(details.kind.deliverOn) + )} + + )} + + {details.kind.__typename === 'RecurringGovernanceTransfer' && ( + <> + + {t('Start On')} + {details.kind.startEpoch} + + {details.kind.endEpoch && ( + + {t('End on')} + {details.kind.endEpoch} + + )} + + )} + + + )} + + ); +}; diff --git a/apps/governance/src/routes/proposals/components/proposal/proposal.tsx b/apps/governance/src/routes/proposals/components/proposal/proposal.tsx index 32df596c5..7b4c42763 100644 --- a/apps/governance/src/routes/proposals/components/proposal/proposal.tsx +++ b/apps/governance/src/routes/proposals/components/proposal/proposal.tsx @@ -20,6 +20,11 @@ import { ProposalUpdateMarketState } from '../proposal-update-market-state'; import type { NetworkParamsResult } from '@vegaprotocol/network-parameters'; import { useVoteSubmit } from '@vegaprotocol/proposals'; import { useUserVote } from '../vote-details/use-user-vote'; +import { + ProposalCancelTransferDetails, + ProposalTransferDetails, +} from '../proposal-transfer'; +import { FLAGS } from '@vegaprotocol/environment'; export interface ProposalProps { proposal: ProposalQuery['proposal']; @@ -99,9 +104,37 @@ export const Proposal = ({ minVoterBalance = networkParams.governance_proposal_freeform_minVoterBalance; break; + case 'NewTransfer': + // TODO: check minVoterBalance for 'NewTransfer' + minVoterBalance = + networkParams.governance_proposal_freeform_minVoterBalance; + break; + case 'CancelTransfer': + // TODO: check minVoterBalance for 'CancelTransfer' + minVoterBalance = + networkParams.governance_proposal_freeform_minVoterBalance; } } + // Show governance transfer details only if the GOVERNANCE_TRANSFERS flag is on. + const governanceTransferDetails = FLAGS.GOVERNANCE_TRANSFERS && ( + <> + {proposal.terms.change.__typename === 'NewTransfer' && ( + /** Governance New Transfer Details */ +
+ +
+ )} + + {proposal.terms.change.__typename === 'CancelTransfer' && ( + /** Governance Cancel Transfer Details */ +
+ +
+ )} + + ); + return (
@@ -187,6 +220,8 @@ export const Proposal = ({
)} + {governanceTransferDetails} +
{ ].includes(m.node.state); }); - // check rows length is correct - const rows = container.getAllByRole('row'); - expect(rows).toHaveLength(expectedRows.length); + await waitFor(() => { + // check rows length is correct + const rows = container.getAllByRole('row'); + expect(rows).toHaveLength(expectedRows.length); + }); // check that only included ids are shown const cells = screen diff --git a/libs/environment/src/hooks/use-environment.ts b/libs/environment/src/hooks/use-environment.ts index 0c0b3689f..2c1ccd4e4 100644 --- a/libs/environment/src/hooks/use-environment.ts +++ b/libs/environment/src/hooks/use-environment.ts @@ -423,6 +423,12 @@ function compileFeatureFlags(): FeatureFlags { process.env['NX_UPDATE_MARKET_STATE'] ) as string ), + GOVERNANCE_TRANSFERS: TRUTHY.includes( + windowOrDefault( + 'NX_GOVERNANCE_TRANSFERS', + process.env['NX_GOVERNANCE_TRANSFERS'] + ) as string + ), }; const EXPLORER_FLAGS = { EXPLORER_ASSETS: TRUTHY.includes( diff --git a/libs/environment/src/types.ts b/libs/environment/src/types.ts index fa5219c4a..0f8226222 100644 --- a/libs/environment/src/types.ts +++ b/libs/environment/src/types.ts @@ -25,6 +25,7 @@ export type CosmicElevatorFlags = Pick< | 'METAMASK_SNAPS' | 'REFERRALS' | 'UPDATE_MARKET_STATE' + | 'GOVERNANCE_TRANSFERS' >; export type Configuration = z.infer; export const CUSTOM_NODE_KEY = 'custom' as const; diff --git a/libs/environment/src/utils/validate-environment.ts b/libs/environment/src/utils/validate-environment.ts index 059d66156..609faba92 100644 --- a/libs/environment/src/utils/validate-environment.ts +++ b/libs/environment/src/utils/validate-environment.ts @@ -80,6 +80,7 @@ const COSMIC_ELEVATOR_FLAGS = { METAMASK_SNAPS: z.optional(z.boolean()), REFERRALS: z.optional(z.boolean()), UPDATE_MARKET_STATE: z.optional(z.boolean()), + GOVERNANCE_TRANSFERS: z.optional(z.boolean()), }; const EXPLORER_FLAGS = { diff --git a/libs/markets/src/lib/markets.mock.ts b/libs/markets/src/lib/markets.mock.ts index 6c092b55b..18852d131 100644 --- a/libs/markets/src/lib/markets.mock.ts +++ b/libs/markets/src/lib/markets.mock.ts @@ -96,6 +96,7 @@ export const createMarketFragment = ( filters: [ { __typename: 'Filter', + conditions: [], key: { __typename: 'PropertyKey', name: 'settlement-data-property', @@ -129,6 +130,7 @@ export const createMarketFragment = ( filters: [ { __typename: 'Filter', + conditions: [], key: { __typename: 'PropertyKey', name: 'settlement-data-property', diff --git a/libs/proposals/src/lib/proposals-data-provider/Proposals.graphql b/libs/proposals/src/lib/proposals-data-provider/Proposals.graphql index 69a96a8cc..26a68252e 100644 --- a/libs/proposals/src/lib/proposals-data-provider/Proposals.graphql +++ b/libs/proposals/src/lib/proposals-data-provider/Proposals.graphql @@ -334,6 +334,36 @@ fragment UpdateNetworkParameterFields on UpdateNetworkParameter { } } +fragment NewTransferFields on NewTransfer { + source + sourceType + destination + destinationType + asset { + id + symbol + decimals + quantum + } + fraction_of_balance + amount + transferType + kind { + __typename + ... on OneOffGovernanceTransfer { + deliverOn + } + ... on RecurringGovernanceTransfer { + startEpoch + endEpoch + } + } +} + +fragment CancelTransferFields on CancelTransfer { + transferId +} + fragment ProposalListFields on Proposal { id rationale { diff --git a/libs/proposals/src/lib/proposals-data-provider/__generated__/Proposals.ts b/libs/proposals/src/lib/proposals-data-provider/__generated__/Proposals.ts index 238799d5d..4650f41ca 100644 --- a/libs/proposals/src/lib/proposals-data-provider/__generated__/Proposals.ts +++ b/libs/proposals/src/lib/proposals-data-provider/__generated__/Proposals.ts @@ -13,6 +13,10 @@ export type UpdateAssetFieldsFragment = { __typename?: 'UpdateAsset', assetId: s export type UpdateNetworkParameterFieldsFragment = { __typename?: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } }; +export type NewTransferFieldsFragment = { __typename?: 'NewTransfer', source: string, sourceType: Types.AccountType, destination: string, destinationType: Types.AccountType, fraction_of_balance: string, amount: string, transferType: Types.GovernanceTransferType, asset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, quantum: string }, kind: { __typename: 'OneOffGovernanceTransfer', deliverOn?: any | null } | { __typename: 'RecurringGovernanceTransfer', startEpoch: number, endEpoch?: number | null } }; + +export type CancelTransferFieldsFragment = { __typename?: 'CancelTransfer', transferId: string }; + export type ProposalListFieldsFragment = { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, datetime: any, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null, requiredMajority: string, requiredParticipation: string, requiredLpMajority?: string | null, requiredLpParticipation?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string }, party: { __typename?: 'Party', id: string }, votes: { __typename?: 'ProposalVotes', yes: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalWeight: string }, no: { __typename?: 'ProposalVoteSide', totalTokens: string, totalNumber: string, totalWeight: string } }, terms: { __typename?: 'ProposalTerms', closingDatetime: any, enactmentDatetime?: any | null, change: { __typename: 'CancelTransfer' } | { __typename: 'NewAsset', name: string, symbol: string, decimals: number, quantum: string, source: { __typename?: 'BuiltinAsset', maxFaucetAmountMint: string } | { __typename?: 'ERC20', contractAddress: string, lifetimeLimit: string, withdrawThreshold: string } } | { __typename: 'NewFreeform' } | { __typename: 'NewMarket', decimalPlaces: number, metadata?: Array | null, instrument: { __typename?: 'InstrumentConfiguration', name: string, code: string, futureProduct?: { __typename?: 'FutureProduct', quoteName: string, settlementAsset: { __typename?: 'Asset', id: string, name: string, symbol: string, decimals: number, quantum: string }, dataSourceSpecForSettlementData: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal' } }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal' } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } | null }, riskParameters: { __typename?: 'LogNormalRiskModel', riskAversionParameter: number, tau: number, params: { __typename?: 'LogNormalModelParams', mu: number, r: number, sigma: number } } | { __typename?: 'SimpleRiskModel', params: { __typename?: 'SimpleRiskModelParams', factorLong: number, factorShort: number } }, successorConfiguration?: { __typename?: 'SuccessorConfiguration', parentMarketId: string } | null } | { __typename: 'NewSpotMarket' } | { __typename: 'NewTransfer' } | { __typename: 'UpdateAsset', assetId: string, quantum: string, source: { __typename?: 'UpdateERC20', lifetimeLimit: string, withdrawThreshold: string } } | { __typename: 'UpdateMarket', marketId: string, updateMarketConfiguration: { __typename?: 'UpdateMarketConfiguration', metadata?: Array | null, instrument: { __typename?: 'UpdateInstrumentConfiguration', code: string, product: { __typename?: 'UpdateFutureProduct', quoteName: string, dataSourceSpecForSettlementData: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal' } }, dataSourceSpecForTradingTermination: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal' } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecToFutureBinding', settlementDataProperty: string, tradingTerminationProperty: string } } | { __typename?: 'UpdatePerpetualProduct', quoteName: string, dataSourceSpecForSettlementData: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal' } }, dataSourceSpecForSettlementSchedule: { __typename?: 'DataSourceDefinition', sourceType: { __typename?: 'DataSourceDefinitionExternal', sourceType: { __typename?: 'DataSourceSpecConfiguration', signers?: Array<{ __typename?: 'Signer', signer: { __typename?: 'ETHAddress', address?: string | null } | { __typename?: 'PubKey', key?: string | null } }> | null, filters?: Array<{ __typename?: 'Filter', key: { __typename?: 'PropertyKey', name?: string | null, type: Types.PropertyKeyType }, conditions?: Array<{ __typename?: 'Condition', operator: Types.ConditionOperator, value?: string | null }> | null }> | null } | { __typename?: 'EthCallSpec' } } | { __typename?: 'DataSourceDefinitionInternal' } }, dataSourceSpecBinding: { __typename?: 'DataSourceSpecPerpetualBinding', settlementDataProperty: string, settlementScheduleProperty: string } } }, priceMonitoringParameters: { __typename?: 'PriceMonitoringParameters', triggers?: Array<{ __typename?: 'PriceMonitoringTrigger', horizonSecs: number, probability: number, auctionExtensionSecs: number }> | null }, liquidityMonitoringParameters: { __typename?: 'LiquidityMonitoringParameters', triggeringRatio: string, targetStakeParameters: { __typename?: 'TargetStakeParameters', timeWindow: number, scalingFactor: number } }, riskParameters: { __typename: 'UpdateMarketLogNormalRiskModel', logNormal?: { __typename?: 'LogNormalRiskModel', riskAversionParameter: number, tau: number, params: { __typename?: 'LogNormalModelParams', mu: number, r: number, sigma: number } } | null } | { __typename: 'UpdateMarketSimpleRiskModel', simple?: { __typename?: 'SimpleRiskModelParams', factorLong: number, factorShort: number } | null } } } | { __typename: 'UpdateMarketState' } | { __typename: 'UpdateNetworkParameter', networkParameter: { __typename?: 'NetworkParameter', key: string, value: string } } | { __typename: 'UpdateReferralProgram' } | { __typename: 'UpdateSpotMarket' } | { __typename: 'UpdateVolumeDiscountProgram' } } }; export type ProposalsListQueryVariables = Types.Exact<{ @@ -32,6 +36,38 @@ export type SuccessorProposalsListQueryVariables = Types.Exact<{ [key: string]: export type SuccessorProposalsListQuery = { __typename?: 'Query', proposalsConnection?: { __typename?: 'ProposalsConnection', edges?: Array<{ __typename?: 'ProposalEdge', node: { __typename?: 'Proposal', id?: string | null, terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket', instrument: { __typename?: 'InstrumentConfiguration', name: string }, successorConfiguration?: { __typename?: 'SuccessorConfiguration', parentMarketId: string } | null } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } } } | null> | null } | null }; +export const NewTransferFieldsFragmentDoc = gql` + fragment NewTransferFields on NewTransfer { + source + sourceType + destination + destinationType + asset { + id + symbol + decimals + quantum + } + fraction_of_balance + amount + transferType + kind { + __typename + ... on OneOffGovernanceTransfer { + deliverOn + } + ... on RecurringGovernanceTransfer { + startEpoch + endEpoch + } + } +} + `; +export const CancelTransferFieldsFragmentDoc = gql` + fragment CancelTransferFields on CancelTransfer { + transferId +} + `; export const NewMarketFieldsFragmentDoc = gql` fragment NewMarketFields on NewMarket { instrument { diff --git a/libs/proposals/src/lib/proposals-hooks/Proposal.graphql b/libs/proposals/src/lib/proposals-hooks/Proposal.graphql index ca2158b94..6c93babae 100644 --- a/libs/proposals/src/lib/proposals-hooks/Proposal.graphql +++ b/libs/proposals/src/lib/proposals-hooks/Proposal.graphql @@ -67,3 +67,29 @@ query InstrumentDetails($marketId: ID!) { } } } + +query NewTransferDetails($proposalId: ID!) { + proposal(id: $proposalId) { + id + terms { + change { + ... on NewTransfer { + ...NewTransferFields + } + } + } + } +} + +query CancelTransferDetails($proposalId: ID!) { + proposal(id: $proposalId) { + id + terms { + change { + ... on CancelTransfer { + ...CancelTransferFields + } + } + } + } +} diff --git a/libs/proposals/src/lib/proposals-hooks/__generated__/Proposal.ts b/libs/proposals/src/lib/proposals-hooks/__generated__/Proposal.ts index 1255b0db6..8252d5f5d 100644 --- a/libs/proposals/src/lib/proposals-hooks/__generated__/Proposal.ts +++ b/libs/proposals/src/lib/proposals-hooks/__generated__/Proposal.ts @@ -1,7 +1,7 @@ import * as Types from '@vegaprotocol/types'; import { gql } from '@apollo/client'; -import { UpdateNetworkParameterFieldsFragmentDoc } from '../../proposals-data-provider/__generated__/Proposals'; +import { UpdateNetworkParameterFieldsFragmentDoc, NewTransferFieldsFragmentDoc, CancelTransferFieldsFragmentDoc } from '../../proposals-data-provider/__generated__/Proposals'; import * as Apollo from '@apollo/client'; const defaultOptions = {} as const; export type ProposalEventFieldsFragment = { __typename?: 'Proposal', id?: string | null, reference: string, state: Types.ProposalState, rejectionReason?: Types.ProposalRejectionReason | null, errorDetails?: string | null }; @@ -41,6 +41,20 @@ export type InstrumentDetailsQueryVariables = Types.Exact<{ export type InstrumentDetailsQuery = { __typename?: 'Query', market?: { __typename?: 'Market', tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', code: string, name: string } } } | null }; +export type NewTransferDetailsQueryVariables = Types.Exact<{ + proposalId: Types.Scalars['ID']; +}>; + + +export type NewTransferDetailsQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer' } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer', source: string, sourceType: Types.AccountType, destination: string, destinationType: Types.AccountType, fraction_of_balance: string, amount: string, transferType: Types.GovernanceTransferType, asset: { __typename?: 'Asset', id: string, symbol: string, decimals: number, quantum: string }, kind: { __typename: 'OneOffGovernanceTransfer', deliverOn?: any | null } | { __typename: 'RecurringGovernanceTransfer', startEpoch: number, endEpoch?: number | null } } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } } | null }; + +export type CancelTransferDetailsQueryVariables = Types.Exact<{ + proposalId: Types.Scalars['ID']; +}>; + + +export type CancelTransferDetailsQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, terms: { __typename?: 'ProposalTerms', change: { __typename?: 'CancelTransfer', transferId: string } | { __typename?: 'NewAsset' } | { __typename?: 'NewFreeform' } | { __typename?: 'NewMarket' } | { __typename?: 'NewSpotMarket' } | { __typename?: 'NewTransfer' } | { __typename?: 'UpdateAsset' } | { __typename?: 'UpdateMarket' } | { __typename?: 'UpdateMarketState' } | { __typename?: 'UpdateNetworkParameter' } | { __typename?: 'UpdateReferralProgram' } | { __typename?: 'UpdateSpotMarket' } | { __typename?: 'UpdateVolumeDiscountProgram' } } } | null }; + export const ProposalEventFieldsFragmentDoc = gql` fragment ProposalEventFields on Proposal { id @@ -246,4 +260,88 @@ export function useInstrumentDetailsLazyQuery(baseOptions?: Apollo.LazyQueryHook } export type InstrumentDetailsQueryHookResult = ReturnType; export type InstrumentDetailsLazyQueryHookResult = ReturnType; -export type InstrumentDetailsQueryResult = Apollo.QueryResult; \ No newline at end of file +export type InstrumentDetailsQueryResult = Apollo.QueryResult; +export const NewTransferDetailsDocument = gql` + query NewTransferDetails($proposalId: ID!) { + proposal(id: $proposalId) { + id + terms { + change { + ... on NewTransfer { + ...NewTransferFields + } + } + } + } +} + ${NewTransferFieldsFragmentDoc}`; + +/** + * __useNewTransferDetailsQuery__ + * + * To run a query within a React component, call `useNewTransferDetailsQuery` and pass it any options that fit your needs. + * When your component renders, `useNewTransferDetailsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useNewTransferDetailsQuery({ + * variables: { + * proposalId: // value for 'proposalId' + * }, + * }); + */ +export function useNewTransferDetailsQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(NewTransferDetailsDocument, options); + } +export function useNewTransferDetailsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(NewTransferDetailsDocument, options); + } +export type NewTransferDetailsQueryHookResult = ReturnType; +export type NewTransferDetailsLazyQueryHookResult = ReturnType; +export type NewTransferDetailsQueryResult = Apollo.QueryResult; +export const CancelTransferDetailsDocument = gql` + query CancelTransferDetails($proposalId: ID!) { + proposal(id: $proposalId) { + id + terms { + change { + ... on CancelTransfer { + ...CancelTransferFields + } + } + } + } +} + ${CancelTransferFieldsFragmentDoc}`; + +/** + * __useCancelTransferDetailsQuery__ + * + * To run a query within a React component, call `useCancelTransferDetailsQuery` and pass it any options that fit your needs. + * When your component renders, `useCancelTransferDetailsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useCancelTransferDetailsQuery({ + * variables: { + * proposalId: // value for 'proposalId' + * }, + * }); + */ +export function useCancelTransferDetailsQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(CancelTransferDetailsDocument, options); + } +export function useCancelTransferDetailsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(CancelTransferDetailsDocument, options); + } +export type CancelTransferDetailsQueryHookResult = ReturnType; +export type CancelTransferDetailsLazyQueryHookResult = ReturnType; +export type CancelTransferDetailsQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/libs/proposals/src/lib/proposals-hooks/index.ts b/libs/proposals/src/lib/proposals-hooks/index.ts index 744b0bc5f..45d0ad444 100644 --- a/libs/proposals/src/lib/proposals-hooks/index.ts +++ b/libs/proposals/src/lib/proposals-hooks/index.ts @@ -4,3 +4,5 @@ export * from './use-proposal-submit'; export * from './use-update-proposal'; export * from './use-update-network-paramaters-toasts'; export * from './use-successor-market-proposal-details'; +export * from './use-new-transfer-proposal-details'; +export * from './use-cancel-transfer-proposal-details'; diff --git a/libs/proposals/src/lib/proposals-hooks/use-cancel-transfer-proposal-details.ts b/libs/proposals/src/lib/proposals-hooks/use-cancel-transfer-proposal-details.ts new file mode 100644 index 000000000..d3ecf5f5f --- /dev/null +++ b/libs/proposals/src/lib/proposals-hooks/use-cancel-transfer-proposal-details.ts @@ -0,0 +1,19 @@ +import type { CancelTransferFieldsFragment } from '../proposals-data-provider'; +import { useCancelTransferDetailsQuery } from './__generated__/Proposal'; + +export const useCancelTransferProposalDetails = ( + proposalId?: string | null +) => { + const { data } = useCancelTransferDetailsQuery({ + variables: { + proposalId: proposalId || '', + }, + skip: !proposalId || proposalId.length === 0, + }); + + if (data?.proposal?.terms.change.__typename === 'CancelTransfer') { + return data?.proposal?.terms.change as CancelTransferFieldsFragment; + } + + return undefined; +}; diff --git a/libs/proposals/src/lib/proposals-hooks/use-new-transfer-proposal-details.ts b/libs/proposals/src/lib/proposals-hooks/use-new-transfer-proposal-details.ts new file mode 100644 index 000000000..70f1204bb --- /dev/null +++ b/libs/proposals/src/lib/proposals-hooks/use-new-transfer-proposal-details.ts @@ -0,0 +1,17 @@ +import type { NewTransferFieldsFragment } from '../proposals-data-provider'; +import { useNewTransferDetailsQuery } from './__generated__/Proposal'; + +export const useNewTransferProposalDetails = (proposalId?: string | null) => { + const { data } = useNewTransferDetailsQuery({ + variables: { + proposalId: proposalId || '', + }, + skip: !proposalId || proposalId.length === 0, + }); + + if (data?.proposal?.terms.change.__typename === 'NewTransfer') { + return data?.proposal?.terms.change as NewTransferFieldsFragment; + } + + return undefined; +}; diff --git a/libs/types/src/global-types-mappings.ts b/libs/types/src/global-types-mappings.ts index 3b4360e51..dd658d5f8 100644 --- a/libs/types/src/global-types-mappings.ts +++ b/libs/types/src/global-types-mappings.ts @@ -1,4 +1,9 @@ -import type { ConditionOperator, PeggedReference } from './__generated__/types'; +import type { + ConditionOperator, + GovernanceTransferKind, + GovernanceTransferType, + PeggedReference, +} from './__generated__/types'; import type { AccountType } from './__generated__/types'; import type { AuctionTrigger, @@ -519,6 +524,34 @@ export const DescriptionTransferTypeMapping: TransferTypeMap = { TRANSFER_TYPE_SUCCESSOR_INSURANCE_FRACTION: 'Successor insurance fraction', }; +/** + * Governance transfers + */ +type GovernanceTransferTypeMap = { + [T in GovernanceTransferType]: string; +}; +export const GovernanceTransferTypeMapping: GovernanceTransferTypeMap = { + GOVERNANCE_TRANSFER_TYPE_ALL_OR_NOTHING: 'All or nothing', + GOVERNANCE_TRANSFER_TYPE_BEST_EFFORT: 'Best effort', + GOVERNANCE_TRANSFER_TYPE_UNSPECIFIED: 'Unspecified', +}; +export const DescriptionGovernanceTransferTypeMapping: GovernanceTransferTypeMap = + { + GOVERNANCE_TRANSFER_TYPE_ALL_OR_NOTHING: + 'Transfers the specified amount or does not transfer anything', + GOVERNANCE_TRANSFER_TYPE_BEST_EFFORT: + 'Transfers the specified amount or the max allowable amount if this is less than the specified amount', + GOVERNANCE_TRANSFER_TYPE_UNSPECIFIED: 'Default value, always invalid', + }; + +type GovernanceTransferKindMap = { + [T in NonNullable]: string; +}; +export const GovernanceTransferKindMapping: GovernanceTransferKindMap = { + OneOffGovernanceTransfer: 'One off', + RecurringGovernanceTransfer: 'Recurring', +}; + type DispatchMetricLabel = { [T in DispatchMetric]: string; };