Skip to content

Commit

Permalink
Add a new form for editing the Realm Config
Browse files Browse the repository at this point in the history
  • Loading branch information
nramadas authored Mar 27, 2023
1 parent 9edaf39 commit c4dec4b
Show file tree
Hide file tree
Showing 40 changed files with 3,579 additions and 67 deletions.
32 changes: 20 additions & 12 deletions components/treasuryV2/Details/RealmAuthorityDetails/Config.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react'
import React from 'react'
import cx from 'classnames'
import {
PencilIcon,
Expand All @@ -10,8 +10,9 @@ import {
OfficeBuildingIcon,
} from '@heroicons/react/outline'
import { BigNumber } from 'bignumber.js'
import { useRouter } from 'next/router'
import { MintMaxVoteWeightSourceType } from '@solana/spl-governance'

import RealmConfigModal from 'pages/dao/[symbol]/params/RealmConfigModal'
import { RealmAuthority } from '@models/treasury/Asset'
import useGovernanceAssets from '@hooks/useGovernanceAssets'
import Tooltip from '@components/Tooltip'
Expand All @@ -24,6 +25,7 @@ import useProgramVersion from '@hooks/useProgramVersion'
import clsx from 'clsx'
import TokenIcon from '@components/treasuryV2/icons/TokenIcon'
import { NFTVotePluginSettingsDisplay } from '@components/NFTVotePluginSettingsDisplay'
import useQueryContext from '@hooks/useQueryContext'

const DISABLED = new BigNumber(DISABLED_VOTER_WEIGHT.toString())

Expand All @@ -34,8 +36,9 @@ interface Props {

export default function Config(props: Props) {
const { canUseAuthorityInstruction } = useGovernanceAssets()
const { mint } = useRealm()
const [editRealmOpen, setEditRealmOpen] = useState(false)
const { mint, symbol } = useRealm()
const router = useRouter()
const { fmtUrlWithCluster } = useQueryContext()

const programVersion = useProgramVersion()
const councilRulesSupported = programVersion >= 3
Expand Down Expand Up @@ -65,7 +68,9 @@ export default function Config(props: Props) {
'disabled:opacity-50'
)}
disabled={!canUseAuthorityInstruction}
onClick={() => setEditRealmOpen(true)}
onClick={() =>
router.push(fmtUrlWithCluster(`/realm/${symbol}/config/edit`))
}
>
<PencilIcon className="h-4 w-4" />
<div>Edit Rules</div>
Expand All @@ -77,7 +82,16 @@ export default function Config(props: Props) {
<Section
icon={<ScaleIcon />}
name="Community mint max vote weight source"
value={props.realmAuthority.config.communityMintMaxVoteWeightSource.fmtSupplyFractionPercentage()}
value={
props.realmAuthority.config.communityMintMaxVoteWeightSource
.type === MintMaxVoteWeightSourceType.Absolute
? formatNumber(
new BigNumber(
props.realmAuthority.config.communityMintMaxVoteWeightSource.value.toString()
).shiftedBy(-(mint ? mint.decimals : 0))
)
: `${props.realmAuthority.config.communityMintMaxVoteWeightSource.fmtSupplyFractionPercentage()}%`
}
/>
)}
<Section
Expand Down Expand Up @@ -248,12 +262,6 @@ export default function Config(props: Props) {
)}
</div>
<NFTVotePluginSettingsDisplay className="mt-24" />
{editRealmOpen && (
<RealmConfigModal
isProposalModalOpen
closeProposalModal={() => setEditRealmOpen(false)}
/>
)}
</div>
)
}
4 changes: 3 additions & 1 deletion hub/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ interface Props {

export function App(props: Props) {
const router = useRouter();
const isDarkMode = router.pathname.startsWith('/realm/[id]/governance');
const isDarkMode =
router.pathname.startsWith('/realm/[id]/governance') ||
router.pathname.startsWith('/realm/[id]/config');

useEffect(() => {
if (isDarkMode) {
Expand Down
167 changes: 167 additions & 0 deletions hub/components/EditRealmConfig/AddressValidator/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import CheckmarkIcon from '@carbon/icons-react/lib/Checkmark';
import ErrorIcon from '@carbon/icons-react/lib/Error';
import WarningFilledIcon from '@carbon/icons-react/lib/WarningFilled';
import { Program, AnchorProvider } from '@coral-xyz/anchor';
import { PublicKey } from '@solana/web3.js';
import React, { useEffect, useState } from 'react';

import {
vsrPluginsPks,
nftPluginsPks,
gatewayPluginsPks,
switchboardPluginsPks,
pythPluginsPks,
} from '@hooks/useVotingPlugins';
import { Input } from '@hub/components/controls/Input';
import { useCluster } from '@hub/hooks/useCluster';
import { useWallet } from '@hub/hooks/useWallet';

const RECOGNIZED_PLUGINS = new Set([
...vsrPluginsPks,
...nftPluginsPks,
...gatewayPluginsPks,
...switchboardPluginsPks,
...pythPluginsPks,
]);

interface Props {
className?: string;
value: PublicKey | null;
onChange?(value: PublicKey | null): void;
}

export function AddressValidator(props: Props) {
const [address, setAddress] = useState(props.value?.toBase58() || '');
const [isValidAddress, setIsValidAddress] = useState(true);
const [isRecognized, setIsRecognized] = useState(true);
const [isVerified, setIsVerified] = useState(true);
const [showMessages, setShowMessages] = useState(false);
const [cluster] = useCluster();
const wallet = useWallet();

useEffect(() => {
const text = props.value?.toBase58() || '';

if (address !== text) {
setAddress(text);
}
}, [props.value]);

return (
<div className={props.className}>
<Input
className="w-full"
placeholder="e.g. GnftV5kLjd67tvHpNGyodwWveEKivz3ZWvvE3Z4xi2iw"
value={address}
onBlur={async (e) => {
const text = e.currentTarget.value;
let key: PublicKey | null = null;

try {
setIsValidAddress(true);
key = new PublicKey(text);
props.onChange?.(key);
} catch {
setIsValidAddress(false);
props.onChange?.(null);
return;
}

if (RECOGNIZED_PLUGINS.has(text)) {
setIsRecognized(true);
} else {
setIsRecognized(false);
}

try {
setIsVerified(false);
const publicKey = await wallet.connect();
const provider = new AnchorProvider(
cluster.connection,
{
publicKey,
signAllTransactions: wallet.signAllTransactions,
signTransaction: wallet.signTransaction,
},
AnchorProvider.defaultOptions(),
);
const program = await Program.at(key, provider);
if (program) {
setIsVerified(true);
} else {
setIsVerified(false);
}
} catch {
setIsVerified(false);
}

setShowMessages(true);
}}
onChange={(e) => {
const text = e.currentTarget.value;
setIsRecognized(false);
setIsVerified(false);
setShowMessages(false);

try {
new PublicKey(text);
setIsValidAddress(true);
} catch {
setIsValidAddress(false);
}

setAddress(text);
}}
onFocus={(e) => {
if (e.currentTarget.value !== props.value?.toBase58()) {
setShowMessages(false);
}
}}
/>
{showMessages &&
address &&
isValidAddress &&
(isRecognized || isVerified) && (
<div className="flex items-center justify-between mt-2">
<div className="flex items-center space-x-6">
{isRecognized && (
<div className="flex items-center space-x-1 text-emerald-400">
<CheckmarkIcon className="h-4 flex-shrink-0 fill-current w-4" />
<div className="text-xs">
Realms recognizes this program ID
</div>
</div>
)}
{isVerified && (
<div className="flex items-center space-x-1 text-emerald-400">
<CheckmarkIcon className="h-4 flex-shrink-0 fill-current w-4" />
<div className="text-xs">Anchor verified</div>
</div>
)}
</div>
<div></div>
</div>
)}
{showMessages &&
address &&
isValidAddress &&
!(isRecognized || isVerified) && (
<div className="flex items-center mt-2 text-amber-400 space-x-2">
<WarningFilledIcon className="h-4 flex-shrink-0 fill-current w-4" />
<div className="text-xs">
You are proposing an update to your DAO’s voting structure. Realms
can recognize that this as a program ID, but cannot verify it is
safe. Mistyping an address risks losing access to your DAO
forever.
</div>
</div>
)}
{showMessages && address && !isValidAddress && (
<div className="flex items-center space-x-2 mt-2">
<ErrorIcon className="h-4 flex-shrink-0 fill-rose-400 w-4" />
<div className="text-rose-400 text-xs">Not a valid program ID</div>
</div>
)}
</div>
);
}
Loading

0 comments on commit c4dec4b

Please sign in to comment.