Skip to content

Commit

Permalink
feat: add renewPoh integration in frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
Julink-eth committed May 1, 2024
1 parent f93dbdd commit 1bbc4b3
Show file tree
Hide file tree
Showing 16 changed files with 912 additions and 9 deletions.
6 changes: 5 additions & 1 deletion packages/ens-app-v3/public/locales/en/transactionFlow.json
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,8 @@
"title_one": "You do not own this name",
"title_other": "You do not own all these names",
"description_one": "Extending this name will extend the current owner's registration length. This will not give you ownership of it.",
"description_other": "Extending these names will extend the current owner's registration length. This will not give you ownership if you are not already the owner" },
"description_other": "Extending these names will extend the current owner's registration length. This will not give you ownership if you are not already the owner"
},
"invoice": {
"extension": "{{count}} year extension",
"transaction": "Transaction fee",
Expand Down Expand Up @@ -397,6 +398,9 @@
"costValue": "{{value}} + fees",
"warning": "Extending this name will not give you ownership of it"
},
"extendNamePoh": {
"actionValue": "Extend registration POH"
},
"deleteSubname": {
"warning": "Hello out there"
}
Expand Down
4 changes: 2 additions & 2 deletions packages/ens-app-v3/src/components/ProfileSnippet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export const ProfileSnippet = ({
const { t } = useTranslation('common')

const { usePreparedDataInput } = useTransactionFlow()
const showExtendNamesInput = usePreparedDataInput('ExtendNames')
const showExtendNamePohInput = usePreparedDataInput('ExtendNamePoh')
const abilities = useAbilities({ name })

const beautifiedName = useBeautifiedName(name)
Expand All @@ -193,7 +193,7 @@ export const ProfileSnippet = ({
prefix={<FastForwardSVG />}
data-testid="extend-button"
onClick={() => {
showExtendNamesInput(`extend-names-${name}`, {
showExtendNamePohInput(`extend-names-${name}`, {
names: [name],
isSelf: shouldShowExtendWarning(abilities.data),
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,11 @@ const ProfileTab = ({ nameDetails, name }: Props) => {
() => gracePeriodEndDate && gracePeriodEndDate < new Date(),
[gracePeriodEndDate],
)
const afterExpiryDate = useMemo(() => expiryDate && expiryDate < new Date(), [gracePeriodEndDate])
const userIsOwner = useMemo(() => ownerData && ownerData.owner === address, [ownerData])
const snippetButton = useMemo(() => {
if (isExpired) return 'register'
if (abilities.data?.canExtend) return 'extend'
if (abilities.data?.canExtend && userIsOwner && afterExpiryDate) return 'extend'
}, [isExpired, abilities.data?.canExtend])

const getTextRecord = (key: string) => profile?.texts?.find((x) => x.key === key)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,64 @@ export const ethRegistrarControllerErrors = [
name: 'UnexpiredCommitmentExists',
type: 'error',
},
{
inputs: [
{
name: 'owner',
type: 'address',
},
],
name: 'PohVerificationFailed',
type: 'error',
},
{
inputs: [
{
name: 'owner',
type: 'address',
},
],
name: 'OwnerAlreadyRegistered',
type: 'error',
},
{
inputs: [
{
name: 'owner',
type: 'address',
},
{
name: 'sender',
type: 'address',
},
],
name: 'SenderNotOwner',
type: 'error',
},
{
inputs: [
{
name: 'current',
type: 'uint256',
},
{
name: 'expiry',
type: 'uint256',
},
],
name: 'NotInGracePeriod',
type: 'error',
},
{
inputs: [
{
name: 'duration',
type: 'uint256',
},
],
name: 'WrongPohRegistrationDuration',
type: 'error',
},
] as const

export const ethRegistrarControllerRentPriceSnippet = [
Expand Down Expand Up @@ -254,3 +312,23 @@ export const ethRegistrarControllerRenewSnippet = [
type: 'function',
},
] as const

export const ethRegistrarControllerRenewPohSnippet = [
...ethRegistrarControllerErrors,
{
inputs: [
{
name: 'name',
type: 'string',
},
{
name: 'signature',
type: 'bytes',
},
],
name: 'renewPoh',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
] as const
187 changes: 187 additions & 0 deletions packages/ens-app-v3/src/ensJsOverrides/getExpiry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import { BaseError, decodeFunctionResult, encodeFunctionData, labelhash, type Hex } from 'viem'

import {
baseRegistrarGracePeriodSnippet,
baseRegistrarNameExpiresSnippet,
ClientWithEns,
getChainContractAddress,
multicallGetCurrentBlockTimestampSnippet,
nameWrapperGetDataSnippet,
} from '@ensdomains/ensjs/contracts'
import { DateWithValue, SimpleTransactionRequest } from '@ensdomains/ensjs/dist/types/types'
import { multicallWrapper } from '@ensdomains/ensjs/public'
import { makeSafeSecondsDate, namehash } from '@ensdomains/ensjs/utils'

import { Prettify } from '@app/types'

import { GeneratedFunction, generateFunction } from './generateFunction'
import { checkIs3LDEth } from './utils/validation'

type ContractOption = 'registrar' | 'nameWrapper'
type ExpiryStatus = 'active' | 'expired' | 'gracePeriod'

export type GetExpiryParameters = Prettify<{
/** Name to get expiry for */
name: string
/** Optional specific contract to use to get expiry */
contract?: ContractOption
}>

export type GetExpiryReturnType = Prettify<{
/** Expiry value */
expiry: DateWithValue<bigint>
/** Grace period value (in seconds) */
gracePeriod: number
/** Status of name */
status: ExpiryStatus
} | null>

const getContractToUse = (contract: ContractOption | undefined, labels: string[]) => {
if (contract) return contract
if (checkIs3LDEth(labels)) {
return 'registrar'
}
return 'nameWrapper'
}

const encode = (
client: ClientWithEns,
{ name, contract }: GetExpiryParameters,
): SimpleTransactionRequest => {
const labels = name.split('.')

const contractToUse = getContractToUse(contract, labels)

const calls: SimpleTransactionRequest[] = [
{
to: getChainContractAddress({ client, contract: 'multicall3' }),
data: encodeFunctionData({
abi: multicallGetCurrentBlockTimestampSnippet,
functionName: 'getCurrentBlockTimestamp',
}),
},
]

if (contractToUse === 'nameWrapper') {
calls.push({
to: getChainContractAddress({ client, contract: 'ensNameWrapper' }),
data: encodeFunctionData({
abi: nameWrapperGetDataSnippet,
functionName: 'getData',
args: [BigInt(namehash(labels.join('.')))],
}),
})
} else {
const baseRegistrarImplementationAddress = getChainContractAddress({
client,
contract: 'ensBaseRegistrarImplementation',
})
calls.push({
to: baseRegistrarImplementationAddress,
data: encodeFunctionData({
abi: baseRegistrarNameExpiresSnippet,
functionName: 'nameExpires',
args: [BigInt(labelhash(labels[0]))],
}),
})
calls.push({
to: baseRegistrarImplementationAddress,
data: encodeFunctionData({
abi: baseRegistrarGracePeriodSnippet,
functionName: 'GRACE_PERIOD',
}),
})
}

return multicallWrapper.encode(client, { transactions: calls })
}

const decode = async (
client: ClientWithEns,
data: Hex | BaseError,
{ name, contract }: GetExpiryParameters,
): Promise<GetExpiryReturnType> => {
if (typeof data === 'object') throw data
const labels = name.split('.')
const result = await multicallWrapper.decode(client, data, [])

const blockTimestamp = decodeFunctionResult({
abi: multicallGetCurrentBlockTimestampSnippet,
functionName: 'getCurrentBlockTimestamp',
data: result[0].returnData,
})

const contractToUse = getContractToUse(contract, labels)

let expiry: bigint
let gracePeriod: bigint = 0n

if (contractToUse === 'nameWrapper') {
;[, , expiry] = decodeFunctionResult({
abi: nameWrapperGetDataSnippet,
functionName: 'getData',
data: result[1].returnData,
})
} else {
expiry = decodeFunctionResult({
abi: baseRegistrarNameExpiresSnippet,
functionName: 'nameExpires',
data: result[1].returnData,
})
gracePeriod = decodeFunctionResult({
abi: baseRegistrarGracePeriodSnippet,
functionName: 'GRACE_PERIOD',
data: result[2].returnData,
})
}

if (expiry === 0n) {
return null
}

let status: ExpiryStatus = 'active'

if (blockTimestamp > expiry + gracePeriod) {
status = 'expired'
} else if (blockTimestamp > expiry) {
status = 'gracePeriod'
}

return {
expiry: {
date: makeSafeSecondsDate(expiry),
value: expiry,
},
gracePeriod: Number(gracePeriod),
status,
}
}

type BatchableFunctionObject = GeneratedFunction<typeof encode, typeof decode>

/**
* Gets the expiry for a name
* @param client - {@link ClientWithEns}
* @param parameters - {@link GetExpiryParameters}
* @returns Expiry object, or `null` if no expiry. {@link GetExpiryReturnType}
*
* @example
* import { createPublicClient, http } from 'viem'
* import { mainnet } from 'viem/chains'
* import { addEnsContracts } from '@ensdomains/ensjs'
* import { getExpiry } from '@ensdomains/ensjs/public'
*
* const client = createPublicClient({
* chain: addEnsContracts(mainnet),
* transport: http(),
* })
* const result = await getExpiry(client, { name: 'ens.eth' })
* // { expiry: { date: Date, value: 1913933217n }, gracePeriod: 7776000, status: 'active' }
*/
const getExpiry = generateFunction({ encode, decode }) as ((
client: ClientWithEns,
{ name, contract }: GetExpiryParameters,
) => Promise<GetExpiryReturnType>) &
BatchableFunctionObject

export default getExpiry
Loading

0 comments on commit 1bbc4b3

Please sign in to comment.