Skip to content

Commit

Permalink
Merge pull request #1 from rarimo/feature/exec-delegate
Browse files Browse the repository at this point in the history
add exec delegate,undelegate, vote
  • Loading branch information
napalmpapalam authored Oct 30, 2023
2 parents ca7a43c + 4623567 commit bc72c81
Show file tree
Hide file tree
Showing 14 changed files with 484 additions and 201 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog], and this project adheres to [Semantic Versioning].

## [Unreleased]
### Added
- Ability to delegate, undelegate and vote proposal via authorization grants

### Changed
- Updated the Rarimo client to the latest version
- Updated next to the latest version

## [1.0.4] - 2023-10-27
### Fixed
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@
"@hookform/resolvers": "^3.3.1",
"@mui/icons-material": "^5.14.3",
"@mui/material": "^5.14.3",
"@rarimo/client": "^2.0.0-rc.22",
"@rarimo/client": "^2.0.0-rc.27",
"graphql": "^16.7.1",
"graphql-tag": "^2.12.6",
"lodash-es": "^4.17.21",
"loglevel": "^1.8.1",
"mitt": "^3.0.1",
"negotiator": "^0.6.3",
"next": "13.5.3",
"next": "^13.5.5",
"next-international": "^0.9.3",
"react": "18.2.0",
"react-dom": "18.2.0",
Expand Down
48 changes: 42 additions & 6 deletions src/callers/validators.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { FetcherError } from '@distributedlab/fetcher'
import { BondStatus, Coin, DelegationResponse } from '@rarimo/client'
import {
AuthorizationTypes,
BondStatus,
Coin,
DelegationResponse,
GrantAuthorization,
MessageTypeUrls,
} from '@rarimo/client'
import { isUndefined } from 'lodash-es'

import { getClient } from '@/client'
Expand Down Expand Up @@ -27,6 +33,7 @@ import { ErrorHandler } from '@/helpers'
import { SortOrder, ValidatorListColumnIds, ValidatorListSortBy } from '@/types'

type OnSubmitHandler = (args: { address: string; amount: string }) => Promise<void>
type ErrorWithStatusCode = { response: { status: number } }

const createValidatorWhere = (status?: number, jailed?: boolean) => {
const where = {
Expand Down Expand Up @@ -223,8 +230,9 @@ export const getValidatorDelegations = async (
try {
resp = await getClient().query.getDelegation(delegator, operator)
} catch (e) {
if (!(e instanceof FetcherError)) throw e
if (e.response.status === 404) return resp
const status = (e as ErrorWithStatusCode)?.response?.status
if (status === 404 || status === 400 || status === 500) return resp
throw e
}
return resp
}
Expand All @@ -237,8 +245,9 @@ export const getDelegationRewards = async (
try {
resp = await getClient().query.getDelegationRewards(delegator, operator)
} catch (e) {
if (!(e instanceof FetcherError)) throw e
if (e.response.status === 400 || e.response.status === 500) return resp
const status = (e as ErrorWithStatusCode)?.response?.status
if (status === 404 || status === 400 || status === 500) return resp
throw e
}
return resp
}
Expand Down Expand Up @@ -274,3 +283,30 @@ export const withdrawValidatorCommission = async (address: string, onSubmit: OnS
ErrorHandler.process(e)
}
}

export const getGrants = async (grantee: string): Promise<GrantAuthorization[]> => {
let resp: GrantAuthorization[] = []
try {
resp = await getClient().query.getGrantAuthorizationsByGrantee(grantee)
} catch (e) {
ErrorHandler.process(e)
}
return resp
}

export const filterGrantsByMessageType = (
grants: GrantAuthorization[],
types: MessageTypeUrls[],
): GrantAuthorization[] => {
if (!grants.length) return []

return grants.reduce<GrantAuthorization[]>((acc, grant) => {
const auth = grant.authorization
if (auth?.['@type'] !== AuthorizationTypes.GenericAuthorization) return acc
if (types.includes(auth.msg as MessageTypeUrls)) {
acc.push(grant)
}

return acc
}, [])
}
146 changes: 121 additions & 25 deletions src/components/Forms/DelegateForm.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
import { BN } from '@distributedlab/tools'
import { InputAdornment, TextField, Typography } from '@mui/material'
import { DelegateTypes } from '@rarimo/client'
import { BN, isFixedPointString } from '@distributedlab/tools'
import {
FormControl,
FormHelperText,
InputAdornment,
InputLabel,
MenuItem,
Select,
TextField,
Typography,
} from '@mui/material'
import {
DelegateTypes,
DelegationResponse,
GenericAuthorization,
GrantAuthorization,
MessageTypeUrls,
} from '@rarimo/client'
import { useMemo } from 'react'
import { Controller } from 'react-hook-form'
import { NumericFormat } from 'react-number-format'

import { filterGrantsByMessageType } from '@/callers'
import { getClient } from '@/client'
import FormWrapper from '@/components/Forms/FormWrapper'
import { CONFIG } from '@/config'
Expand All @@ -13,37 +29,63 @@ import { useForm, useWeb3 } from '@/hooks'
import { useI18n } from '@/locales/client'
import { FormProps } from '@/types'

const EXECUTE_OPTIONS_LABEL_ID = 'sender-label-id'

enum DelegateFormFieldNames {
Validator = 'validator',
Amount = 'amount',
Delegator = 'delegator',
}

const defaultValues = {
[DelegateFormFieldNames.Amount]: '',
const DELEGATE_TYPE_BY_MSG: { [key in MessageTypeUrls]?: DelegateTypes } = {
[MessageTypeUrls.Delegate]: DelegateTypes.Delegate,
[MessageTypeUrls.Undelegate]: DelegateTypes.Undelegate,
}

export type DelegateFormData = typeof defaultValues

export default function DelegateForm({
id,
onSubmit,
setIsDialogDisabled,
operator,
minDelegationAmount,
delegateType,
grants,
reloadDelegation,
maxUndelegationAmount,
accountDelegations,
}: FormProps & {
operator?: string
grants: GrantAuthorization[]
accountDelegations: DelegationResponse[]
minDelegationAmount?: string
delegateType: DelegateTypes
maxUndelegationAmount?: string
reloadDelegation: () => Promise<void>
}) {
const { address } = useWeb3()
const t = useI18n()

const isDelegation = useMemo(() => delegateType === DelegateTypes.Delegate, [delegateType])
const availableUndelegators = useMemo(() => {
return accountDelegations.map(i => i.delegation_response.delegation.delegator_address)
}, [accountDelegations])

const defaultValues = {
[DelegateFormFieldNames.Amount]: '',
[DelegateFormFieldNames.Delegator]: isDelegation
? address
: availableUndelegators.includes(address)
? address
: availableUndelegators[0],
}

const granters = useMemo(() => {
return filterGrantsByMessageType(grants, [
MessageTypeUrls.Delegate,
MessageTypeUrls.Undelegate,
]).map(i => ({
granter: i.granter,
type: DELEGATE_TYPE_BY_MSG[(i.authorization as GenericAuthorization).msg as MessageTypeUrls],
}))
}, [grants])

const {
handleSubmit,
Expand All @@ -59,33 +101,43 @@ export default function DelegateForm({
[DelegateFormFieldNames.Amount]: yup
.string()
.required()
.when(DelegateFormFieldNames.Amount, {
is: () => isDelegation,
then: rule => {
return rule.minNumber(
BN.fromBigInt(String(minDelegationAmount), CONFIG.DECIMALS).value,
)
},
})
.when(DelegateFormFieldNames.Amount, {
is: () => !isDelegation,
then: rule => rule.maxNumber(String(maxUndelegationAmount)),
.when(DelegateFormFieldNames.Delegator, ([delegator], schema) => {
const delegation = accountDelegations.find(
i => i.delegation_response.delegation.delegator_address === delegator,
)

return isDelegation
? schema.minNumber(BN.fromBigInt(String(minDelegationAmount), CONFIG.DECIMALS).value)
: schema.maxNumber(
BN.fromBigInt(
delegation?.delegation_response.balance.amount ?? '0',
CONFIG.DECIMALS,
).value,
)
}),
[DelegateFormFieldNames.Delegator]: yup.string().required(),
},
[[DelegateFormFieldNames.Amount, DelegateFormFieldNames.Amount]],
[[DelegateFormFieldNames.Delegator, DelegateFormFieldNames.Delegator]],
),
)

const submit = async (formData: DelegateFormData) => {
const submit = async (formData: typeof defaultValues) => {
disableForm()
setIsDialogDisabled(true)
try {
const client = getClient()
const isExec = formData.delegator !== address
const txFn = isDelegation ? client.tx.delegate : client.tx.undelegate
await txFn(address, String(operator), {
const txExecFn = isDelegation ? client.tx.execDelegate : client.tx.execUndelegate
const validator = String(operator)
const amount = {
denom: CONFIG.MINIMAL_DENOM,
amount: BN.fromRaw(formData.amount, CONFIG.DECIMALS).value,
})
}

isExec
? await txExecFn(address, formData.delegator, validator, amount)
: await txFn(address, validator, amount)

const args = {
amount: `${formData.amount} ${CONFIG.DENOM}`,
Expand All @@ -105,6 +157,11 @@ export default function DelegateForm({
setIsDialogDisabled(false)
}

const delegatorItems = useMemo(() => {
const grantersByType = granters.filter(i => i.type === delegateType)
return [...(isDelegation ? [{ granter: address, type: delegateType }] : []), ...grantersByType]
}, [address, delegateType, granters, isDelegation])

return (
<FormWrapper id={id} onSubmit={handleSubmit(submit)} isFormDisabled={isFormDisabled}>
<Typography variant={'body2'} color={'var(--col-txt-secondary)'}>
Expand All @@ -131,12 +188,51 @@ export default function DelegateForm({
label={t('delegate-form.amount-lbl')}
error={Boolean(formErrors[DelegateFormFieldNames.Amount])}
disabled={isFormDisabled}
onValueChange={values => onChange(values.floatValue)}
onValueChange={values => {
const isValid = isFixedPointString(values.value)
if (isValid) onChange(values.value)
}}
onBlur={onBlur}
helperText={getErrorMessage(formErrors[DelegateFormFieldNames.Amount])}
/>
)}
/>
{granters.length ? (
<Controller
name={DelegateFormFieldNames.Delegator}
control={control}
render={({ field }) => (
<FormControl>
<InputLabel
id={EXECUTE_OPTIONS_LABEL_ID}
error={Boolean(formErrors[DelegateFormFieldNames.Delegator])}
>
{t('delegate-form.execution-type-lbl')}
</InputLabel>
<Select
{...field}
labelId={EXECUTE_OPTIONS_LABEL_ID}
label={t('delegate-form.execution-type-lbl')}
disabled={isFormDisabled}
error={Boolean(formErrors[DelegateFormFieldNames.Delegator])}
>
{delegatorItems.map((item, idx) => (
<MenuItem value={item.granter} key={idx}>
{item.granter}
</MenuItem>
))}
</Select>
{Boolean(formErrors[DelegateFormFieldNames.Delegator]) && (
<FormHelperText error>
{getErrorMessage(formErrors[DelegateFormFieldNames.Delegator])}
</FormHelperText>
)}
</FormControl>
)}
/>
) : (
<></>
)}
</FormWrapper>
)
}
Loading

0 comments on commit bc72c81

Please sign in to comment.