Skip to content
This repository was archived by the owner on Feb 9, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions components/instructions/programs/nameService.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { NAME_PROGRAM_ID } from '@bonfida/spl-name-service'
import { AccountMetaData } from '@solana/spl-governance'
import { Connection, PublicKey } from '@solana/web3.js'

export const NAME_SERVICE_INSTRUCTIONS = {
[NAME_PROGRAM_ID.toBase58()]: {
2: {
name: 'Domain Name Service: Transfer Domain Name',
accounts: [{ name: 'Domain Name Address' }, { name: 'Treasury Account' }],
getDataUI: async (
_connection: Connection,
data: Uint8Array,
_accounts: AccountMetaData[]
) => {
const decodedData = new PublicKey(data.slice(1))
return (
<>
<span>New Owner: {decodedData.toBase58()}</span>
</>
)
},
},
},
}
2 changes: 2 additions & 0 deletions components/instructions/programs/names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
LIDO_PROGRAM_ID,
LIDO_PROGRAM_ID_DEVNET,
} from '@components/TreasuryAccount/ConvertToStSol'
import { NAME_PROGRAM_ID } from '@bonfida/spl-name-service'

export const GOVERNANCE_PROGRAM_NAMES = {
GqTPL6qRf5aUuqscLh8Rg2HTxPUXfhhAXDptTLhp1t2J: 'Mango Governance Program',
Expand Down Expand Up @@ -43,6 +44,7 @@ export const PROGRAM_NAMES = {
VotEn9AWwTFtJPJSMV5F9jsMY6QwWM5qn3XP9PATGW7:
'PsyDO Voter Stake Registry Program',
[foresightConsts.PROGRAM_ID]: 'Foresight Dex',
[NAME_PROGRAM_ID.toBase58()]: 'Solana Name Service Program',
AwyKDr1Z5BfdvK3jX1UWopyjsJSV5cq4cuJpoYLofyEn: 'Validator Dao',
Stake11111111111111111111111111111111111111: 'Stake Program',
StakeConfig11111111111111111111111111111111: 'Stake Config',
Expand Down
3 changes: 3 additions & 0 deletions components/instructions/tools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ import { PROGRAM_IDS } from '@castlefinance/vault-sdk'
import { FORESIGHT_INSTRUCTIONS } from './programs/foresight'
import { SAGA_PHONE } from './programs/SagaPhone'
import { LIDO_INSTRUCTIONS } from './programs/lido'
import { NAME_SERVICE_INSTRUCTIONS } from './programs/nameService'
import { TOKEN_AUCTION_INSTRUCTIONS } from './programs/tokenAuction'
import { VALIDATORDAO_INSTRUCTIONS } from './programs/validatordao'

/**
* Default governance program id instance
*/
Expand Down Expand Up @@ -258,6 +260,7 @@ export const INSTRUCTION_DESCRIPTORS = {
...VOTE_STAKE_REGISTRY_INSTRUCTIONS,
...NFT_VOTER_INSTRUCTIONS,
...STREAMFLOW_INSTRUCTIONS,
...NAME_SERVICE_INSTRUCTIONS,
...SAGA_PHONE,
...TOKEN_AUCTION_INSTRUCTIONS,
...VALIDATORDAO_INSTRUCTIONS,
Expand Down
43 changes: 43 additions & 0 deletions hooks/useDomainNames.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useEffect, useState } from 'react'

import {
getAllDomains,
performReverseLookupBatch,
} from '@bonfida/spl-name-service'

interface Domains {
domainName: string | undefined
domainAddress: string
}

const useDomainsForAccount = (connection, governedAccount) => {
const [accountDomains, setAccountDomains] = useState<Domains[]>([])
const [isLoading, setIsLoading] = useState(false)

useEffect(() => {
// eslint-disable-next-line @typescript-eslint/no-extra-semi
;(async () => {
setIsLoading(true)
const domains = await getAllDomains(connection, governedAccount.pubkey)

if (domains.length > 0) {
const reverse = await performReverseLookupBatch(connection, domains)
const results: Domains[] = []

for (let i = 0; i < domains.length; i++) {
results.push({
domainAddress: domains[i].toBase58(),
domainName: reverse[i],
})
}

setAccountDomains(results)
}
setIsLoading(false)
})()
}, [governedAccount, connection])

return { accountDomains, isLoading }
}

export { useDomainsForAccount }
7 changes: 6 additions & 1 deletion hooks/useGovernanceAssets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { vsrPluginsPks } from './useVotingPlugins'

export default function useGovernanceAssets() {
const { ownVoterWeight, realm, symbol, governances, config } = useRealm()

const governedTokenAccounts: AssetAccount[] = useGovernanceAssetsStore(
(s) => s.governedTokenAccounts
)
Expand Down Expand Up @@ -256,6 +257,11 @@ export default function useGovernanceAssets() {
name: 'Withdraw validator stake',
isVisible: canUseAnyInstruction,
},
{
id: Instructions.TransferDomainName,
name: 'SNS Transfer Out Domain Name',
isVisible: canUseAnyInstruction,
},
{
id: Instructions.EverlendDeposit,
name: 'Everlend Deposit Funds',
Expand Down Expand Up @@ -503,7 +509,6 @@ export default function useGovernanceAssets() {
},
...foresightInstructions,
]

return {
governancesArray,
getGovernancesByAccountType,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"dependencies": {
"@blockworks-foundation/mango-client": "^3.6.14",
"@blockworks-foundation/mango-v4": "^0.0.2",
"@bonfida/spl-name-service": "^0.1.47",
"@bundlr-network/client": "^0.7.15",
"@cardinal/namespaces-components": "^2.5.5",
"@castlefinance/vault-core": "^0.1.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import React, { useContext, useEffect, useState } from 'react'
import * as yup from 'yup'
import useWalletStore from 'stores/useWalletStore'
import {
Governance,
ProgramAccount,
serializeInstructionToBase64,
} from '@solana/spl-governance'
import { PublicKey } from '@solana/web3.js'
import { validateInstruction } from '@utils/instructionTools'
import {
DomainNameTransferForm,
UiInstruction,
} from '@utils/uiTypes/proposalCreationTypes'
import { transferInstruction, NAME_PROGRAM_ID } from '@bonfida/spl-name-service'
import { NewProposalContext } from '../../new'
import GovernedAccountSelect from '../GovernedAccountSelect'

import { LoadingDots } from '@components/Loading'
import useGovernanceAssets from '@hooks/useGovernanceAssets'
import Select from '@components/inputs/Select'
import Input from '@components/inputs/Input'
import { useDomainsForAccount } from '@hooks/useDomainNames'
import { isPublicKey } from '@tools/core/pubkey'

const TransferDomainName = ({
index,
governance,
}: {
index: number
governance: ProgramAccount<Governance> | null
}) => {
const connection = useWalletStore((s) => s.connection.current)
const shouldBeGoverned = index !== 0 && governance
const { handleSetInstructions } = useContext(NewProposalContext)

const { assetAccounts } = useGovernanceAssets()
const governedAccount = assetAccounts.filter((acc) => acc.isSol)[0]
const { accountDomains, isLoading } = useDomainsForAccount(
connection,
governedAccount
)

const [formErrors, setFormErrors] = useState({})
const [form, setForm] = useState<DomainNameTransferForm>({
destinationAccount: '',
governedAccount: undefined,
domainAddress: undefined,
})

const handleSetForm = ({ propertyName, value }) => {
setFormErrors({})
setForm({ ...form, [propertyName]: value })
}

async function getInstruction(): Promise<UiInstruction> {
const isValid = await validateInstruction({
schema,
form,
setFormErrors,
})

const obj: UiInstruction = {
serializedInstruction: '',
isValid,
governance: governedAccount?.governance,
}

if (
isValid &&
form.destinationAccount &&
form.domainAddress &&
form.governedAccount
) {
const nameProgramId = new PublicKey(NAME_PROGRAM_ID)
const nameAccountKey = new PublicKey(form.domainAddress)
const newOwnerKey = new PublicKey(form.destinationAccount)
const nameOwner = governedAccount.pubkey

const transferIx = transferInstruction(
nameProgramId,
nameAccountKey,
newOwnerKey,
nameOwner
)

obj.serializedInstruction = serializeInstructionToBase64(transferIx)
}
return obj
}

useEffect(() => {
handleSetInstructions(
{ governedAccount: governedAccount?.governance, getInstruction },
index
)
}, [form])

const schema = yup.object().shape({
governedAccount: yup
.object()
.nullable()
.required('Governed account is required'),
destinationAccount: yup
.string()
.required('Please provide a valid destination account')
.test({
name: 'is-valid-account',
test(val, ctx) {
if (!val || !isPublicKey(val)) {
return ctx.createError({
message: 'Please verify the account address',
})
}
return true
},
}),
domainAddress: yup.string().required('Please select a domain name'),
})

return (
<>
<GovernedAccountSelect
label="Governance"
governedAccounts={assetAccounts.filter((acc) => acc.isSol)}
onChange={(value) => {
handleSetForm({ value, propertyName: 'governedAccount' })
}}
value={governedAccount}
error={formErrors['governedAccount']}
shouldBeGoverned={shouldBeGoverned}
governance={governance}
/>
<Input
label="Destination Account"
value={form.destinationAccount}
type="text"
onChange={(element) =>
handleSetForm({
propertyName: 'destinationAccount',
value: element.target.value,
})
}
error={formErrors['destinationAccount']}
/>
{isLoading ? (
<div className="mt-5">
<div>Looking up accountDomains...</div>
<LoadingDots />
</div>
) : (
<Select
className=""
label="Domain"
value={
form.domainAddress
? accountDomains.find(
(d) => d.domainAddress === form.domainAddress
)?.domainName + '.sol'
: ''
}
placeholder="Please select..."
error={formErrors['domainAddress']}
onChange={(value) => {
handleSetForm({
value: accountDomains.find((d) => d.domainName === value)
?.domainAddress,
propertyName: 'domainAddress',
})
}}
>
{accountDomains?.map(
(domain, index) =>
domain.domainAddress && (
<Select.Option
key={domain.domainName! + index}
value={domain.domainName}
>
<div className="text-fgd-1 mb-2">{domain.domainName}.sol</div>
<div className="">{domain.domainAddress?.toString()}</div>
</Select.Option>
)
)}
</Select>
)}
</>
)
}

export default TransferDomainName
4 changes: 4 additions & 0 deletions pages/dao/[symbol]/proposal/new.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ import PerpEdit from './components/instructions/Mango/MangoV4/PerpEdit'
import Serum3RegisterMarket from './components/instructions/Mango/MangoV4/Serum3RegisterMarket'
import PerpCreate from './components/instructions/Mango/MangoV4/PerpCreate'
import TokenRegisterTrustless from './components/instructions/Mango/MangoV4/TokenRegisterTrustless'
import TransferDomainName from './components/instructions/TransferDomainName'
import DepositForm from './components/instructions/Everlend/DepositForm'
import WithdrawForm from './components/instructions/Everlend/WithdrawForm'
import MakeChangeReferralFeeParams2 from './components/instructions/Mango/MakeChangeReferralFeeParams2'
Expand Down Expand Up @@ -713,10 +714,13 @@ const New = () => {
return <CreateTokenMetadata index={idx} governance={governance} />
case Instructions.UpdateTokenMetadata:
return <UpdateTokenMetadata index={idx} governance={governance} />
case Instructions.TransferDomainName:
return <TransferDomainName index={idx} governance={governance}></TransferDomainName>
case Instructions.EverlendDeposit:
return <DepositForm index={idx} governance={governance} />
case Instructions.EverlendWithdraw:
return <WithdrawForm index={idx} governance={governance} />

default:
null
}
Expand Down
7 changes: 7 additions & 0 deletions utils/uiTypes/proposalCreationTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ export interface SplTokenTransferForm {
mintInfo: MintInfo | undefined
}

export interface DomainNameTransferForm {
destinationAccount: string
governedAccount: AssetAccount | undefined
domainAddress: string | undefined
}

export interface CastleDepositForm {
amount: number | undefined
governedTokenAccount: AssetAccount | undefined
Expand Down Expand Up @@ -486,6 +492,7 @@ export enum Instructions {
DeactivateValidatorStake,
WithdrawValidatorStake,
DifferValidatorStake,
TransferDomainName,
EverlendDeposit,
EverlendWithdraw,
}
Expand Down
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1566,6 +1566,7 @@
"@bonfida/name-offers" "^0.0.1"
"@ethersproject/sha2" "^5.5.0"


"@bonfida/spl-name-service@^0.1.50":
version "0.1.50"
resolved "https://registry.yarnpkg.com/@bonfida/spl-name-service/-/spl-name-service-0.1.50.tgz#462560199f6869fd97c8a19a8a09851ac15191db"
Expand Down