Skip to content
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
143 changes: 143 additions & 0 deletions BonkVotePlugin/components/BasicDetailsForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// PATH: BonkVotePlugin/components/BasicDetailsForm.tsx
import { useEffect } from 'react'
import * as React from 'react' // safer for TypeScript
import { useForm, Controller, useWatch } from 'react-hook-form'
import { yupResolver } from '@hookform/resolvers/yup'
import * as yup from 'yup'

import FormHeader from '@components/NewRealmWizard/components/FormHeader'
import FormField from '@components/NewRealmWizard/components/FormField'
import FormFooter from '@components/NewRealmWizard/components/FormFooter'
import AdvancedOptionsDropdown from '@components/NewRealmWizard/components/AdvancedOptionsDropdown'
import Input from '@components/NewRealmWizard/components/Input'
import { DEFAULT_GOVERNANCE_PROGRAM_ID } from '@components/instructions/tools'
import { updateUserInput, validateSolAddress } from '@utils/formValidation'
import { FORM_NAME as MUTISIG_FORM } from 'pages/realms/new/multisig'
import { useProgramVersionByIdQuery } from '@hooks/queries/useProgramVersionQuery'
import { PublicKey } from '@solana/web3.js'

// Import your GovernancePowerTitle correctly
import { GovernancePowerTitle } from './GovernancePowerTitle'

export const BasicDetailsSchema = {
avatar: yup.string(),
name: yup
.string()
.typeError('Required')
.required('Required')
.max(32, 'Name must not be longer than 32 characters'),
programId: yup
.string()
.test('is-valid-address', 'Please enter a valid Solana address', (value) =>
value ? validateSolAddress(value) : true,
),
}

export interface BasicDetails {
name: string
programId?: string
}

export default function BasicDetailsForm({
type,
formData,
currentStep,
totalSteps,
onSubmit,
onPrevClick,
}: {
type: any
formData: BasicDetails
currentStep: any
totalSteps: any
onSubmit: Function
onPrevClick: Function
}) {
const schema = yup.object(BasicDetailsSchema).required()
const { setValue, control, handleSubmit, formState: { errors, isValid } } = useForm({
mode: 'all',
resolver: yupResolver(schema),
})

const programIdInput = useWatch({ name: 'programId', control })
const validProgramId =
programIdInput && validateSolAddress(programIdInput)
? new PublicKey(programIdInput)
: undefined
const programVersionQuery = useProgramVersionByIdQuery(validProgramId)

useEffect(() => {
updateUserInput(formData, BasicDetailsSchema, setValue)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

function serializeValues(values) {
onSubmit({ step: currentStep, data: values })
}

return (
<form onSubmit={handleSubmit(serializeValues)} data-testid="basic-details-form">
<FormHeader type={type} currentStep={currentStep} totalSteps={totalSteps} title="Let's get started." />
<div className="mt-16 space-y-10 md:space-y-12">
<Controller
name="name"
control={control}
defaultValue=""
render={({ field }) => (
<FormField
title={type === MUTISIG_FORM ? 'What is the name of your wallet?' : 'What is the name of your DAO?'}
description="It's best to choose a descriptive, memorable name for you and your members."
>
<Input
placeholder={type === MUTISIG_FORM ? 'e.g. Realms wallet' : 'e.g. Realms DAO'}
data-testid="dao-name-input"
error={errors.name?.message || ''}
{...field}
/>
</FormField>
)}
/>

<AdvancedOptionsDropdown>
<Controller
name="programId"
defaultValue=""
control={control}
render={({ field }) => (
<FormField
title="Custom Program ID"
description="Provide the program ID of your own instance of spl-governance you want to use for the organisation. This cannot be changed after the organisation is created."
advancedOption
>
<Input
placeholder={`e.g. ${DEFAULT_GOVERNANCE_PROGRAM_ID}`}
data-testid="programId-input"
error={errors.programId?.message || ''}
autoComplete="on"
success={
!programVersionQuery.isLoading && programVersionQuery.data !== 1
? `Program version ${programVersionQuery.data}`
: undefined
}
warning={
!programVersionQuery.isLoading && programVersionQuery.data === 1
? 'Program version could not be verified'
: programVersionQuery.isFetching
? 'Fetching program version...'
: undefined
}
{...field}
/>
</FormField>
)}
/>
</AdvancedOptionsDropdown>

{/* Example usage of GovernancePowerTitle */}
<GovernancePowerTitle />
</div>

<FormFooter isValid={isValid} prevClickHandler={() => onPrevClick(currentStep)} />
</form>
)
}
13 changes: 13 additions & 0 deletions BonkVotePlugin/components/GovernancePowerTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// PATH: BonkVotePlugin/components/GovernancePowerTitle.tsx
import React from 'react'

// Only declare it here
export const GovernancePowerTitle: React.FC = () => {
return (
<div className="text-lg font-bold text-white">
Governance Power
</div>
)
}

export default GovernancePowerTitle
2 changes: 1 addition & 1 deletion HeliumVotePlugin/utils/getPositions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
init,
delegatedPositionKey,
} from '@helium/helium-sub-daos-sdk'
import { tryGetMint } from '@utils/tokens'
import tryGetMint from '@utils/tokens'
import { calcPositionVotingPower } from './calcPositionVotingPower'
import { HeliumVsrClient } from '../sdk/client'
import {
Expand Down
69 changes: 33 additions & 36 deletions NftVotePlugin/getCnftParamAndProof.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,61 @@
import { Connection, PublicKey } from '@solana/web3.js'
import { ConcurrentMerkleTreeAccount } from '@solana/spl-account-compression'
import * as bs58 from 'bs58'
import { fetchDasAssetProofById } from '@hooks/queries/digitalAssets'
import { getNetworkFromEndpoint } from '@utils/connection'
import * as anchor from '@coral-xyz/anchor'
// ⚡️ TS2339: Property 'decode' does not exist on type 'typeof bs58'.
// Fix: Use the correct default import for bs58!
import bs58 from 'bs58'

export function decode(stuff: string) {
return bufferToArray(bs58.decode(stuff))
/**
* Decode a base58 string into a number array.
*/
function decode(stuff: string): number[] {
// bs58 returns Uint8Array directly as the default import
return Array.from(bs58.decode(stuff))
}

function bufferToArray(buffer: Buffer): number[] {
const nums: number[] = []
for (let i = 0; i < buffer.length; i++) {
nums.push(buffer[i])
}
return nums
}
export default decode

/**
* This is a helper function only for nft-voter-v2 used.
* Given a cNFT, getCnftParamAndProof will to get its the metadata, leaf information and merkle proof.
* All these data will be sent to the program to verify the ownership of the cNFT.
* @param connection
* @param compressedNft
* @returns {param, additionalAccounts}
* Given a cNFT, getCnftParamAndProof will get its metadata, leaf information and merkle proof.
*/
export async function getCnftParamAndProof(
connection: Connection,
compressedNft: any,
) {
connection: Connection,
compressedNft: any,
): Promise<{ param: any; additionalAccounts: any[] }> {
const network = getNetworkFromEndpoint(connection.rpcEndpoint)
if (network === 'localnet') throw new Error()
if (network === 'localnet') throw new Error('Localnet not supported for this op')

const { result: assetProof } = await fetchDasAssetProofById(
network,
new PublicKey(compressedNft.id),
network,
new PublicKey(compressedNft.id),
)

const treeAccount = await ConcurrentMerkleTreeAccount.fromAccountAddress(
connection,
new PublicKey(compressedNft.compression.tree),
connection,
new PublicKey(compressedNft.compression.tree),
)
const canopyHeight = treeAccount.getCanopyDepth()
const root = decode(assetProof.root)
const proofLength = assetProof.proof.length

const reducedProofs = assetProof.proof.slice(
0,
proofLength - (canopyHeight ? canopyHeight : 0),
0,
proofLength - (canopyHeight ? canopyHeight : 0),
)

const creators = compressedNft.creators.map((creator) => {
return {
address: new PublicKey(creator.address),
verified: creator.verified,
share: creator.share,
}
})
const creators = compressedNft.creators.map((creator: any) => ({
address: new PublicKey(creator.address),
verified: creator.verified,
share: creator.share,
}))

const rawCollection = compressedNft.grouping.find(
(x) => x.group_key === 'collection',
(x: any) => x.group_key === 'collection',
)

const param = {
name: compressedNft.content.metadata.name,
symbol: compressedNft.content.metadata.symbol,
Expand All @@ -75,7 +72,7 @@ export async function getCnftParamAndProof(
root,
leafOwner: new PublicKey(compressedNft.ownership.owner),
leafDelegate: new PublicKey(
compressedNft.ownership.delegate || compressedNft.ownership.owner,
compressedNft.ownership.delegate || compressedNft.ownership.owner,
),
nonce: new anchor.BN(compressedNft.compression.leaf_id),
index: compressedNft.compression.leaf_id,
Expand All @@ -84,5 +81,5 @@ export async function getCnftParamAndProof(

const additionalAccounts = [compressedNft.compression.tree, ...reducedProofs]

return { param: param, additionalAccounts: additionalAccounts }
}
return { param, additionalAccounts }
}
2 changes: 1 addition & 1 deletion VoteStakeRegistry/components/instructions/Clawback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import React, {
useState,
} from 'react'
import { PublicKey, TransactionInstruction } from '@solana/web3.js'
import { tryGetMint } from '@utils/tokens'
import tryGetMint from '@utils/tokens'
import {
ClawbackForm,
UiInstruction,
Expand Down
2 changes: 1 addition & 1 deletion VoteStakeRegistry/tools/deposits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { SIMULATION_WALLET } from '@tools/constants'
import { DAYS_PER_MONTH, SECS_PER_DAY } from '@utils/dateTools'
import { chunks } from '@utils/helpers'
import { tryGetMint } from '@utils/tokens'
import tryGetMint from '@utils/tokens'
import {
getRegistrarPDA,
getVoterPDA,
Expand Down
Loading