Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement transferring out .sol domains from treasury #1001

Merged
merged 14 commits into from
Sep 1, 2022
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