Skip to content

Commit

Permalink
Integrate AllDomains (#49)
Browse files Browse the repository at this point in the history
* feat: invite member

* feat: members page

* feat: add member resolve

* feat: new proposal member input support

* feat: domains by ad

* fix: typos

* fix: typo

* upgrade tldparser package

* fix undefined error

* revert: domains in treasury page

* remove tld domains from the hook

---------

Co-authored-by: Utkarsh <83659045+0xShuk@users.noreply.github.com>
  • Loading branch information
thearyanag and 0xShuk authored Dec 16, 2024
1 parent 51929df commit 3711ca7
Show file tree
Hide file tree
Showing 11 changed files with 416 additions and 84 deletions.
77 changes: 60 additions & 17 deletions components/Members/AddMemberForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import AddMemberIcon from '@components/AddMemberIcon'
import {
ArrowCircleDownIcon,
ArrowCircleUpIcon,
RefreshIcon,
} from '@heroicons/react/outline'
import useCreateProposal from '@hooks/useCreateProposal'
import { AssetAccount } from '@utils/uiTypes/assets'
Expand All @@ -33,6 +34,8 @@ import { useRealmQuery } from '@hooks/queries/realm'
import { DEFAULT_GOVERNANCE_PROGRAM_VERSION } from '@components/instructions/tools'
import useLegacyConnectionContext from '@hooks/useLegacyConnectionContext'
import {useVoteByCouncilToggle} from "@hooks/useVoteByCouncilToggle";
import { resolveDomain } from '@utils/domains'
import debounce from 'lodash/debounce'

interface AddMemberForm extends Omit<MintForm, 'mintAccount'> {
description: string
Expand Down Expand Up @@ -95,6 +98,44 @@ const AddMemberForm: FC<{ close: () => void; mintAccount: AssetAccount }> = ({
// note the lack of space is not a typo
const proposalTitle = `Add ${govpop}member ${abbrevAddress}`

const [isResolvingDomain, setIsResolvingDomain] = useState(false)

const resolveDomainDebounced = useMemo(
() =>
debounce(async (domain: string) => {
try {
console.log('Attempting to resolve domain:', domain)
const resolved = await resolveDomain(connection.current, domain)
console.log('Domain resolved to:', resolved?.toBase58() || 'null')

if (resolved) {
handleSetForm({
value: resolved.toBase58(),
propertyName: 'destinationAccount',
})
}
} catch (error) {
console.error('Error resolving domain:', error)
} finally {
setIsResolvingDomain(false)
}
}, 500),
[connection]
)

const handleDestinationAccountChange = async (event) => {
const value = event.target.value
handleSetForm({
value,
propertyName: 'destinationAccount',
})

if (value.includes('.')) {
setIsResolvingDomain(true)
resolveDomainDebounced(value)
}
}

const setAmount = (event) => {
const value = event.target.value

Expand Down Expand Up @@ -264,23 +305,25 @@ const AddMemberForm: FC<{ close: () => void; mintAccount: AssetAccount }> = ({
<h2 className="text-xl">Add new member to {realmInfo?.displayName}</h2>
</div>

<Input
useDefaultStyle={false}
className="p-4 w-full bg-bkg-3 border border-bkg-3 default-transition text-sm text-fgd-1 rounded-md focus:border-bkg-3 focus:outline-none"
wrapperClassName="my-6"
label="Member's wallet"
placeholder="Member's wallet"
value={form.destinationAccount}
type="text"
onChange={(event) =>
handleSetForm({
value: event.target.value,
propertyName: 'destinationAccount',
})
}
noMaxWidth
error={formErrors['destinationAccount']}
/>
<div className="relative">
<Input
useDefaultStyle={false}
className="p-4 w-full bg-bkg-3 border border-bkg-3 default-transition text-sm text-fgd-1 rounded-md focus:border-bkg-3 focus:outline-none"
wrapperClassName="my-6"
label="Member's wallet"
placeholder="Member's wallet or domain name (e.g. domain.solana)"
value={form.destinationAccount}
type="text"
onChange={handleDestinationAccountChange}
noMaxWidth
error={formErrors['destinationAccount']}
/>
{isResolvingDomain && (
<div className="absolute right-3 top-1/2 transform -translate-y-1/2">
<RefreshIcon className="h-4 w-4 animate-spin text-primary-light" />
</div>
)}
</div>

<div
className={'flex items-center hover:cursor-pointer w-24 my-3'}
Expand Down
72 changes: 67 additions & 5 deletions components/NewRealmWizard/components/steps/InviteMembersForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import { updateUserInput, validateSolAddress } from '@utils/formValidation'
import { FORM_NAME as MULTISIG_FORM } from 'pages/realms/new/multisig'
import { textToAddressList } from '@utils/textToAddressList'
import useWalletOnePointOh from '@hooks/useWalletOnePointOh'
import { resolveDomain } from '@utils/domains'
import { Connection } from '@solana/web3.js'
// import { DEFAULT_RPC_ENDPOINT } from '@constants/endpoints'
import { useConnection } from '@solana/wallet-adapter-react'

/**
* Convert a list of addresses into a list of uniques and duplicates
Expand Down Expand Up @@ -131,6 +135,58 @@ export interface InviteMembers {
memberAddresses: string[]
}

/**
* Convert a list of addresses/domains into a list of resolved addresses
*/
async function resolveAddressList(connection: Connection, textBlock: string) {
const items = textBlock.split(/[\n,]+/).map((item) => item.trim()).filter(Boolean)
console.log('Resolving items:', items)

const results = await Promise.all(
items.map(async (item) => {
try {
// If it's already a valid address, return it
if (validateSolAddress(item)) {
console.log(`${item} is already a valid address`)
return { input: item, resolved: item }
}

// Try to resolve as domain
console.log(`Attempting to resolve domain: ${item}`)
const resolved = await resolveDomain(connection, item)
console.log('Resolved:', resolved)
console.log(`Domain ${item} resolved to:`, resolved?.toBase58() || 'null')
return {
input: item,
resolved: resolved?.toBase58()
}
} catch (error) {
console.error(`Error resolving ${item}:`, error)
return { input: item, resolved: undefined }
}
})
)

const valid: string[] = []
const invalid: string[] = []

results.forEach(({ input, resolved }) => {
if (resolved && validateSolAddress(resolved)) {
valid.push(resolved)
} else {
invalid.push(input)
}
})

console.log('Final resolution results:', {
valid,
invalid,
results
})

return { valid, invalid }
}

export default function InviteMembersForm({
visible,
type,
Expand All @@ -146,6 +202,7 @@ export default function InviteMembersForm({
const [inviteList, setInviteList] = useState<string[]>([])
const [invalidAddresses, setInvalidAddresses] = useState<string[]>([])
const [lacksMintAuthority, setLackMintAuthority] = useState(false)
const { connection } = useConnection()

const schema = yup.object(InviteMembersSchema)
const {
Expand Down Expand Up @@ -211,19 +268,25 @@ export default function InviteMembersForm({
onSubmit({ step: currentStep, data: values })
}

function addToAddressList(textBlock: string) {
/**
* Update the addToAddressList function to be async
*/
async function addToAddressList(textBlock: string) {
if (lacksMintAuthority) {
return
}

const { valid, invalid } = textToAddressList(textBlock)
const { valid, invalid } = await resolveAddressList(connection, textBlock)
const { unique, duplicate } = splitUniques(inviteList.concat(valid))
setInviteList(unique)
setInvalidAddresses((currentList) =>
currentList.concat(invalid).concat(duplicate)
)
}

/**
* Update the event handlers to handle async addToAddressList
*/
function handleBlur(ev) {
addToAddressList(ev.currentTarget.value)
ev.currentTarget.value = ''
Expand All @@ -232,13 +295,12 @@ export default function InviteMembersForm({
function handlePaste(ev: React.ClipboardEvent<HTMLInputElement>) {
addToAddressList(ev.clipboardData.getData('text'))
ev.clipboardData.clearData()
// Don't allow the paste event to populate the input field
ev.preventDefault()
}

function handleKeyDown(ev) {
if (ev.defaultPrevented) {
return // Do nothing if the event was already processed
return
}

if (ev.key === 'Enter') {
Expand Down Expand Up @@ -323,7 +385,7 @@ export default function InviteMembersForm({
<Input
type="text"
name="memberAddresses"
placeholder="e.g. CWvWQWt5mTv7Zx..."
placeholder="e.g. CWvWQWt5mTv7Zx... or domain.solana"
data-testid="dao-member-list-input"
disabled={lacksMintAuthority}
ref={inputElement}
Expand Down
4 changes: 3 additions & 1 deletion components/Profile/ProfileName.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { useProfile } from '@components/Profile/useProfile';
import { PublicKey } from '@solana/web3.js';
import ContentLoader from "react-content-loader";
import {ShortAddress} from "@components/Profile/ShortAddress";
import { fetchDomainsByPubkey } from '@utils/domains'
import { useConnection } from '@solana/wallet-adapter-react'
import { useEffect, useState } from 'react'

type Props = { publicKey?: PublicKey, height?: string;
width?: string;
Expand All @@ -14,7 +17,6 @@ export const ProfileName: FC<Props> = ({ publicKey, height = "13",
style, }) => {
const { profile, loading } = useProfile(publicKey)


if (!publicKey) return <></>;
return loading ? (
<div
Expand Down
29 changes: 19 additions & 10 deletions hooks/useTreasuryInfo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import useRealm from '@hooks/useRealm'

import { assembleWallets } from './assembleWallets'
import { calculateTokenCountAndValue } from './calculateTokenCountAndValue'
import { getDomains } from './getDomains'
import { fetchDomainsByPubkey } from '@utils/domains'
import { useRealmQuery } from '@hooks/queries/realm'
import { useRealmConfigQuery } from '@hooks/queries/realmConfig'
import {
Expand Down Expand Up @@ -67,18 +67,27 @@ export default function useTreasuryInfo(
if (!loadingGovernedAccounts && accounts.length && getNftsAndDomains) {
setDomainsLoading(true)
setBuildingWallets(true)
getDomains(
accounts.filter((acc) => acc.isSol),
connection.current
).then((domainNames) => {
setDomains(domainNames)
setDomainsLoading(false)
})

Promise.all(
accounts
.filter((acc) => acc.isSol)
.map((account) =>
fetchDomainsByPubkey(connection.current, account.pubkey)
)
).then((domainResults) => {
const allDomains = domainResults.flat().map(domain => ({
name: domain.domainName?.replace('.sol', ''),
address: domain.domainAddress,
owner: domain.domainOwner,
type: domain.type
}));

setDomains(allDomains);
setDomainsLoading(false);
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps -- TODO please fix, it can cause difficult bugs. You might wanna check out https://bobbyhadz.com/blog/react-hooks-exhaustive-deps for info. -@asktree
}, [
loadingGovernedAccounts,
// eslint-disable-next-line react-hooks/exhaustive-deps -- TODO please fix, it can cause difficult bugs. You might wanna check out https://bobbyhadz.com/blog/react-hooks-exhaustive-deps for info. -@asktree
accounts.map((account) => account.pubkey.toBase58()).join('-'),
])

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"@next/bundle-analyzer": "12.1.5",
"@nivo/bar": "0.79.1",
"@nivo/core": "0.79.0",
"@onsol/tldparser": "0.6.7",
"@parcl-oss/staking": "0.0.2",
"@project-serum/common": "0.0.1-beta.3",
"@project-serum/serum": "0.13.65",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ import { NewProposalContext } from '../../../new'
import useMembershipTypes from './useMembershipTypes'
import { useRealmQuery } from '@hooks/queries/realm'
import Tooltip from '@components/Tooltip'
import { resolveDomain } from '@utils/domains'
import { RefreshIcon } from '@heroicons/react/outline'
import debounce from 'lodash/debounce'
import { useConnection } from '@solana/wallet-adapter-react'

type Form = {
memberKey?: string
Expand Down Expand Up @@ -196,6 +200,34 @@ const RevokeGoverningTokens: FC<{
governance,
])

// Add state for domain resolution
const [isResolvingDomain, setIsResolvingDomain] = useState(false)
const {connection} = useConnection()

// Add the debounced resolve function
const resolveDomainDebounced = useMemo(
() =>
debounce(async (domain: string) => {
try {
console.log('Attempting to resolve domain:', domain)
const resolved = await resolveDomain(connection, domain)
console.log('Domain resolved to:', resolved?.toBase58() || 'null')

if (resolved) {
setForm((prevForm) => ({
...prevForm,
memberKey: resolved.toBase58(),
}))
}
} catch (error) {
console.error('Error resolving domain:', error)
} finally {
setIsResolvingDomain(false)
}
}, 500),
[connection]
)

return (
<>
<Tooltip
Expand All @@ -218,13 +250,29 @@ const RevokeGoverningTokens: FC<{
))}
</Select>
</Tooltip>
<Input
label="Member Public Key"
value={form.memberKey}
type="text"
onChange={(e) => setForm((p) => ({ ...p, memberKey: e.target.value }))}
error={formErrors.memberKey}
/>
<div className="relative">
<Input
label="Member Public Key"
value={form.memberKey}
type="text"
placeholder="Member wallet or domain name (e.g. domain.solana)"
onChange={(e) => {
const value = e.target.value
setForm((p) => ({ ...p, memberKey: value }))

if (value.includes('.')) {
setIsResolvingDomain(true)
resolveDomainDebounced(value)
}
}}
error={formErrors.memberKey}
/>
{isResolvingDomain && (
<div className="absolute right-3 top-1/2 transform -translate-y-1/2">
<RefreshIcon className="h-4 w-4 animate-spin text-primary-light" />
</div>
)}
</div>
<TokenAmountInput
mint={selectedMint}
label="Amount of weight to revoke"
Expand Down
Loading

0 comments on commit 3711ca7

Please sign in to comment.