diff --git a/content/pages/editMetadata.json b/content/pages/editMetadata.json index 2030c4698..921571f2b 100644 --- a/content/pages/editMetadata.json +++ b/content/pages/editMetadata.json @@ -165,6 +165,20 @@ "options": ["This asset uses algorithm custom parameters"], "required": false }, + { + "name": "allow", + "label": "Allow ETH Address", + "placeholder": "e.g. 0xe328aB96B7CbB55A6E1c1054678137bA09780acA", + "help": "Enter an ETH address and click the ADD button to append to the list. Only ETH addresses in the allow list can consume this asset. If the list is empty anyone can download or compute this asset.", + "type": "credentials" + }, + { + "name": "deny", + "label": "Deny ETH Address", + "placeholder": "e.g. 0xe328aB96B7CbB55A6E1c1054678137bA09780acA", + "help": "Enter an ETH address and click the ADD button to append to the list. If an ETH address is in the deny list, download or compute of this asset will be denied for that ETH address.", + "type": "credentials" + }, { "name": "paymentCollector", "label": "Payment Collector Address", diff --git a/content/publish/form.json b/content/publish/form.json index 8c3a01668..93cf4f985 100644 --- a/content/publish/form.json +++ b/content/publish/form.json @@ -304,6 +304,20 @@ "type": "checkbox", "options": ["This asset uses user defined parameters"], "required": false + }, + { + "name": "allow", + "label": "Allow ETH Address", + "placeholder": "e.g. 0xe328aB96B7CbB55A6E1c1054678137bA09780acA", + "help": "Enter an ETH address and click the ADD button to append to the list. Only ETH addresses in the allow list can consume this asset. If the list is empty anyone can download or compute this asset.", + "type": "credentials" + }, + { + "name": "deny", + "label": "Deny ETH Address", + "placeholder": "e.g. 0xe328aB96B7CbB55A6E1c1054678137bA09780acA", + "help": "Enter an ETH address and click the ADD button to append to the list. If an ETH address is in the deny list, download or compute of this asset will be denied for that ETH address.", + "type": "credentials" } ] }, diff --git a/src/components/@shared/FormInput/InputElement/Credential/index.module.css b/src/components/@shared/FormInput/InputElement/Credential/index.module.css new file mode 100644 index 000000000..df7b52ed6 --- /dev/null +++ b/src/components/@shared/FormInput/InputElement/Credential/index.module.css @@ -0,0 +1,6 @@ +.addressListContainer { + display: flex; + flex-direction: column; + gap: calc(var(--spacer) / 4); + margin-top: calc(var(--spacer) / 2); +} diff --git a/src/components/@shared/FormInput/InputElement/Credential/index.tsx b/src/components/@shared/FormInput/InputElement/Credential/index.tsx new file mode 100644 index 000000000..7541cea4c --- /dev/null +++ b/src/components/@shared/FormInput/InputElement/Credential/index.tsx @@ -0,0 +1,86 @@ +import { useField } from 'formik' +import React, { useState, ChangeEvent, FormEvent, useEffect } from 'react' +import Button from '../../../atoms/Button' +import styles from './index.module.css' +import { isAddress } from 'ethers/lib/utils.js' +import { toast } from 'react-toastify' +import InputGroup from '../../InputGroup' +import InputElement from '..' +import { InputProps } from '../..' + +export default function Credentials(props: InputProps) { + const [field, meta, helpers] = useField(props.name) + const [addressList, setAddressList] = useState(field.value || []) + const [value, setValue] = useState('') + + useEffect(() => { + helpers.setValue(addressList) + }, [addressList]) + + function handleDeleteAddress(value: string) { + const newInput = addressList.filter((input) => input !== value) + setAddressList(newInput) + helpers.setValue(newInput) + } + + function handleAddValue(e: FormEvent) { + e.preventDefault() + if (!isAddress(value)) { + toast.error('Wallet address is invalid') + return + } + if (addressList.includes(value.toLowerCase())) { + toast.error('Wallet address already added into hte list') + return + } + setAddressList((addressList) => [...addressList, value.toLowerCase()]) + setValue('') + } + + return ( +
+ + ) => + setValue(e.target.value) + } + /> + + +
+ {addressList.length > 0 && + addressList.map((value, i) => { + return ( +
+ + + + +
+ ) + })} +
+
+ ) +} diff --git a/src/components/@shared/FormInput/InputElement/index.tsx b/src/components/@shared/FormInput/InputElement/index.tsx index 67df35d2f..0141b9149 100644 --- a/src/components/@shared/FormInput/InputElement/index.tsx +++ b/src/components/@shared/FormInput/InputElement/index.tsx @@ -17,6 +17,7 @@ import { extensions, oceanTheme } from '@utils/codemirror' import { ConsumerParameters } from './ConsumerParameters' import ServiceCredential from './ServiceCredential' import ComputeEnvSelection from './ComputeEnvSelection' +import Credentials from './Credential' const cx = classNames.bind(styles) @@ -212,6 +213,8 @@ const InputElement = forwardRef( ) case 'tags': return + case 'credentials': + return default: return prefix || postfix ? (
)} - + + credential.type === 'address') + ?.values || [], + deny: + credentials?.deny?.find((credential) => credential.type === 'address') + ?.values || [], assetState, service: { usesConsumerParameters: service?.consumerParameters?.length > 0, diff --git a/src/components/Asset/Edit/_types.ts b/src/components/Asset/Edit/_types.ts index 8b6288c92..2176ee0f7 100644 --- a/src/components/Asset/Edit/_types.ts +++ b/src/components/Asset/Edit/_types.ts @@ -14,6 +14,8 @@ export interface MetadataEditForm { tags?: string[] usesConsumerParameters?: boolean consumerParameters?: FormConsumerParameter[] + allow?: string[] + deny?: string[] assetState?: string service?: { usesConsumerParameters?: boolean diff --git a/src/components/Asset/Edit/_validation.ts b/src/components/Asset/Edit/_validation.ts index 582ebff76..79579feb6 100644 --- a/src/components/Asset/Edit/_validation.ts +++ b/src/components/Asset/Edit/_validation.ts @@ -47,6 +47,8 @@ export const validationSchema = Yup.object().shape({ .nullable() .transform((value) => value || null) }), + allow: Yup.array().of(Yup.string()).nullable(), + deny: Yup.array().of(Yup.string()).nullable(), paymentCollector: Yup.string().test( 'ValidAddress', 'Must be a valid Ethereum Address.', diff --git a/src/components/Publish/Services/index.tsx b/src/components/Publish/Services/index.tsx index 223a77224..b8cb2c5e6 100644 --- a/src/components/Publish/Services/index.tsx +++ b/src/components/Publish/Services/index.tsx @@ -102,6 +102,16 @@ export default function ServicesFields(): ReactElement { component={Input} name="services[0].usesConsumerParameters" /> + + {values.services[0].usesConsumerParameters && ( { + updatedCredentials[credentialType.type] = [ + ...updatedCredentials[credentialType.type].filter( + (credential) => credential?.type !== 'address' + ), + ...(credentialType.values.length > 0 + ? [{ type: 'address', values: credentialType.values }] + : []) + ] + }) + + return updatedCredentials +} + export async function transformPublishFormToDdo( values: FormPublishData, // Those 2 are only passed during actual publishing process @@ -121,7 +151,8 @@ export async function transformPublishFormToDdo( consumerParameters, gaiaXInformation } = metadata - const { access, files, links, providerUrl, timeout } = services[0] + const { access, files, links, providerUrl, timeout, allow, deny } = + services[0] const did = nftAddress ? generateDid(nftAddress, chainId) : '0x...' const currentTime = dateToStringNoMS(new Date()) @@ -227,6 +258,8 @@ export async function transformPublishFormToDdo( : undefined } + const newCredentials = generateCredentials(undefined, allow, deny) + const newDdo: DDO = { '@context': ['https://w3id.org/did/v1'], id: did, @@ -235,6 +268,7 @@ export async function transformPublishFormToDdo( chainId, metadata: newMetadata, services: [newService], + credentials: newCredentials, // Only added for DDO preview, reflecting Asset response, // again, we can assume if `datatokenAddress` is not passed, // we are on preview. diff --git a/src/components/Publish/_validation.ts b/src/components/Publish/_validation.ts index f4d4c5ede..77b46643d 100644 --- a/src/components/Publish/_validation.ts +++ b/src/components/Publish/_validation.ts @@ -80,7 +80,9 @@ const validationService = { otherwise: Yup.array() .nullable() .transform((value) => value || null) - }) + }), + allow: Yup.array().of(Yup.string()).nullable(), + deny: Yup.array().of(Yup.string()).nullable() } const validationPricing = {