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

Feature/identity proposals #5

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
lts/fermium
63 changes: 44 additions & 19 deletions hooks/useGovernanceAssets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,16 @@ export default function useGovernanceAssets() {
},
[PackageEnum.GatewayPlugin]: {
name: 'Gateway Plugin',
image: '/img/civic.svg',
},
[PackageEnum.GoblinGold]: {
name: 'Goblin Gold',
image: '/img/goblingold.png',
},
[PackageEnum.Identity]: {
name: 'Identity',
image: '/img/identity.png',
},
[PackageEnum.NftPlugin]: {
name: 'NFT Plugin',
},
Expand Down Expand Up @@ -441,25 +446,6 @@ export default function useGovernanceAssets() {
packageId: PackageEnum.Friktion,
},

/*
██████ █████ ████████ ███████ ██ ██ █████ ██ ██ ██████ ██ ██ ██ ██████ ██ ███ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██
██ ███ ███████ ██ █████ ██ █ ██ ███████ ████ ██████ ██ ██ ██ ██ ███ ██ ██ ██ ██
██ ██ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██ ██ ██ ███████ ███ ███ ██ ██ ██ ██ ███████ ██████ ██████ ██ ██ ████
*/

[Instructions.ConfigureGatewayPlugin]: {
name: 'Configure',
isVisible: canUseAuthorityInstruction,
packageId: PackageEnum.GatewayPlugin,
},
[Instructions.CreateGatewayPluginRegistrar]: {
name: 'Create registrar',
isVisible: canUseAuthorityInstruction,
packageId: PackageEnum.GatewayPlugin,
},

/*
██████ ██████ ██████ ██ ██ ███ ██ ██████ ██████ ██ ██████
██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██
Expand All @@ -477,6 +463,45 @@ export default function useGovernanceAssets() {
packageId: PackageEnum.GoblinGold,
},

/*
██ ██████ ███████ ███ ██ ████████ ██ ████████ ██ ██
██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██
██ ██ ██ █████ ██ ██ ██ ██ ██ ██ ████
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██████ ███████ ██ ████ ██ ██ ██ ██
*/

[Instructions.ConfigureGatewayPlugin]: {
name: 'Configure',
isVisible: canUseAuthorityInstruction,
packageId: PackageEnum.GatewayPlugin,
},
[Instructions.CreateGatewayPluginRegistrar]: {
name: 'Create registrar',
isVisible: canUseAuthorityInstruction,
packageId: PackageEnum.GatewayPlugin,
},
[Instructions.AddKeyToDID]: {
name: 'Add Key to DID',
isVisible: canUseAnyInstruction,
packageId: PackageEnum.Identity,
},
[Instructions.RemoveKeyFromDID]: {
name: 'Remove Key from DID',
isVisible: canUseAnyInstruction,
packageId: PackageEnum.Identity,
},
[Instructions.AddServiceToDID]: {
name: 'Add Service to DID',
isVisible: canUseAnyInstruction,
packageId: PackageEnum.Identity,
},
[Instructions.RemoveServiceFromDID]: {
name: 'Remove Service from DID',
isVisible: canUseAnyInstruction,
packageId: PackageEnum.Identity,
},

/*
███ ██ ███████ ████████ ██████ ██ ██ ██ ██████ ██ ███ ██
████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"type-check": "tsc --pretty --noEmit --skipLibCheck",
"format": "prettier --write .",
"lint": "eslint . --ext ts --ext tsx --ext js --ext jsx",
"test": "jest ",
"test": "jest",
"test-all": "yarn lint && yarn type-check && yarn test",
"notifier": "ts-node scripts/governance-notifier.ts",
"postinstall": "echo '\\033[35mIf you just added a package, consider running `\\033[1m\\033[36;1mnpx yarn-deduplicate\\033[0m\\033[35m`!\\033[00m'"
Expand Down Expand Up @@ -47,6 +47,7 @@
"@headlessui/react": "1.6.6",
"@heroicons/react": "1.0.6",
"@hookform/resolvers": "2.8.10",
"@identity.com/sol-did-client": "3.1.4",
"@marinade.finance/marinade-ts-sdk": "2.0.9",
"@mean-dao/msp": "3.0.1-alpha.13",
"@metaplex-foundation/js": "0.17.5",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import React, { useContext, useEffect, useState } from 'react'
import * as yup from 'yup'
import {
Governance,
ProgramAccount,
serializeInstructionToBase64,
} from '@solana/spl-governance'
import { validateInstruction } from '@utils/instructionTools'
import { UiInstruction } from '@utils/uiTypes/proposalCreationTypes'

import useWalletStore from 'stores/useWalletStore'
import useRealm from '@hooks/useRealm'
import { NewProposalContext } from '../../../new'
import InstructionForm, { InstructionInput } from '../FormCreator'
import { AssetAccount } from '@utils/uiTypes/assets'
import useGovernanceAssets from '@hooks/useGovernanceAssets'
import { PublicKey } from '@solana/web3.js'
import {
BitwiseVerificationMethodFlag,
DidSolIdentifier,
DidSolService,
VerificationMethodType,
} from '@identity.com/sol-did-client'
import {
governanceInstructionInput,
governedAccountToWallet,
instructionInputs,
SchemaComponents,
} from '@utils/instructions/Identity/util'

interface AddKeyToDIDForm {
governedAccount: AssetAccount | undefined
did: string // manual entry for now - replace with dropdown once did-registry is introduced
key: string // manual entry
alias: string // manual entry
}

const AddKeyToDID = ({
index,
governance,
}: {
index: number
governance: ProgramAccount<Governance> | null
}) => {
const { realm } = useRealm()
const { assetAccounts } = useGovernanceAssets()
const connection = useWalletStore((s) => s.connection)
const shouldBeGoverned = index !== 0 && governance
const [form, setForm] = useState<AddKeyToDIDForm>()
const [formErrors, setFormErrors] = useState({})
const { handleSetInstructions } = useContext(NewProposalContext)

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

// getInstruction must return something, even if it is an invalid instruction
let serializedInstructions = ['']

if (
isValid &&
form!.governedAccount?.governance?.pubkey &&
connection?.current
) {
const service = DidSolService.build(DidSolIdentifier.parse(form!.did), {
connection: connection.current,
wallet: governedAccountToWallet(form!.governedAccount),
})

const addKeyIxs = await service
.addVerificationMethod({
flags: [BitwiseVerificationMethodFlag.CapabilityInvocation],
fragment: form!.alias,
keyData: new PublicKey(form!.key).toBytes(),
// TODO support eth keys too
methodType: VerificationMethodType.Ed25519VerificationKey2018,
})
// Adds a DID resize instruction if needed
// The resize instruction performs a SOL transfer, so needs to be from
// an account with no data, otherwise the Solana runtime will reject it.
// this is why we use the governed account here as opposed to the governance
// itself.
.withAutomaticAlloc(form!.governedAccount.pubkey)
.instructions()

serializedInstructions = addKeyIxs.map(serializeInstructionToBase64)
}

// Realms appears to put additionalSerializedInstructions first, so reverse the order of the instructions
// to ensure the resize function comes first.
const [
serializedInstruction,
...additionalSerializedInstructions
] = serializedInstructions.reverse()

return {
serializedInstruction,
additionalSerializedInstructions,
isValid,
governance: form!.governedAccount?.governance,
chunkSplitByDefault: true,
}
}
useEffect(() => {
handleSetInstructions(
{ governedAccount: form?.governedAccount?.governance, getInstruction },
index
)
}, [form])
const schema = yup.object().shape(SchemaComponents)
const inputs: InstructionInput[] = [
governanceInstructionInput(
realm,
governance || undefined,
assetAccounts,
shouldBeGoverned
),
instructionInputs.did,
instructionInputs.key,
instructionInputs.alias,
]

return (
<>
<InstructionForm
outerForm={form}
setForm={setForm}
inputs={inputs}
setFormErrors={setFormErrors}
formErrors={formErrors}
></InstructionForm>
</>
)
}

export default AddKeyToDID
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import React, { useContext, useEffect, useState } from 'react'
import * as yup from 'yup'
import {
Governance,
ProgramAccount,
serializeInstructionToBase64,
} from '@solana/spl-governance'
import { validateInstruction } from '@utils/instructionTools'
import { UiInstruction } from '@utils/uiTypes/proposalCreationTypes'

import useWalletStore from 'stores/useWalletStore'
import useRealm from '@hooks/useRealm'
import { NewProposalContext } from '../../../new'
import InstructionForm, { InstructionInput } from '../FormCreator'
import { AssetAccount } from '@utils/uiTypes/assets'
import useGovernanceAssets from '@hooks/useGovernanceAssets'
import { DidSolIdentifier, DidSolService } from '@identity.com/sol-did-client'
import {
governanceInstructionInput,
governedAccountToWallet,
instructionInputs,
SchemaComponents,
} from '@utils/instructions/Identity/util'

interface AddServiceToDIDForm {
governedAccount: AssetAccount | undefined
did: string // manual entry for now - replace with dropdown once did-registry is introduced
alias: string // manual entry
serviceEndpoint: string // manual entry
serviceType: string // manual entry
}

const AddServiceToDID = ({
index,
governance,
}: {
index: number
governance: ProgramAccount<Governance> | null
}) => {
const { realm } = useRealm()
const { assetAccounts } = useGovernanceAssets()
const connection = useWalletStore((s) => s.connection)
const shouldBeGoverned = index !== 0 && governance
const [form, setForm] = useState<AddServiceToDIDForm>()
const [formErrors, setFormErrors] = useState({})
const { handleSetInstructions } = useContext(NewProposalContext)

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

// getInstruction must return something, even if it is an invalid instruction
let serializedInstructions = ['']

if (
isValid &&
form!.governedAccount?.governance?.pubkey &&
connection?.current
) {
const service = DidSolService.build(DidSolIdentifier.parse(form!.did), {
connection: connection.current,
wallet: governedAccountToWallet(form!.governedAccount),
})

const addServiceIxs = await service
.addService({
fragment: form!.alias,
serviceEndpoint: form!.serviceEndpoint,
serviceType: form!.serviceType,
})
// Adds a DID resize instruction if needed
// The resize instruction performs a SOL transfer, so needs to be from
// an account with no data, otherwise the Solana runtime will reject it.
// this is why we use the governed account here as opposed to the governance
// itself.
.withAutomaticAlloc(form!.governedAccount.pubkey)
.instructions()

serializedInstructions = addServiceIxs.map(serializeInstructionToBase64)
}

// Realms appears to put additionalSerializedInstructions first, so reverse the order of the instructions
// to ensure the resize function comes first.
const [
serializedInstruction,
...additionalSerializedInstructions
] = serializedInstructions.reverse()

return {
serializedInstruction,
additionalSerializedInstructions,
isValid,
governance: form!.governedAccount?.governance,
chunkSplitByDefault: true,
}
}
useEffect(() => {
handleSetInstructions(
{ governedAccount: form?.governedAccount?.governance, getInstruction },
index
)
}, [form])
const schema = yup.object().shape(SchemaComponents)
const inputs: InstructionInput[] = [
governanceInstructionInput(
realm,
governance || undefined,
assetAccounts,
shouldBeGoverned
),
instructionInputs.did,
instructionInputs.serviceEndpoint,
instructionInputs.serviceType,
instructionInputs.alias,
]

return (
<>
<InstructionForm
outerForm={form}
setForm={setForm}
inputs={inputs}
setFormErrors={setFormErrors}
formErrors={formErrors}
></InstructionForm>
</>
)
}

export default AddServiceToDID
Loading