From 8768bb16e2e67cd06f46712a4319a1269e3f5a54 Mon Sep 17 00:00:00 2001 From: kalashshah <202051096@iiitvadodara.ac.in> Date: Wed, 31 Jul 2024 10:57:12 +0530 Subject: [PATCH] feat: add erc1155 to token gating group conditions --- packages/uiweb/package.json | 2 +- .../chat/CreateGroup/AddCriteria.tsx | 35 +++++++- .../chat/CreateGroup/ConditionsComponent.tsx | 31 ++++++-- .../chat/helpers/addCriteriaUtilities.ts | 79 +++++++++---------- .../chat/helpers/getRulesToCondtionArray.ts | 1 - .../chat/helpers/tokenGatedGroup.ts | 24 ++++-- .../chat/helpers/tokenHelpers/abi.ts | 8 +- .../chat/helpers/tokenHelpers/fetch.ts | 61 +++++++++++++- .../src/lib/components/chat/types/index.ts | 3 + .../chat/types/tokenGatedGroupCreationType.ts | 2 + .../src/lib/hooks/useTokenSymbolLoader.ts | 9 ++- packages/uiweb/yarn.lock | 2 +- 12 files changed, 193 insertions(+), 64 deletions(-) diff --git a/packages/uiweb/package.json b/packages/uiweb/package.json index d59dda4ba..2ccb5f19b 100644 --- a/packages/uiweb/package.json +++ b/packages/uiweb/package.json @@ -38,7 +38,7 @@ "uuid": "^9.0.1" }, "peerDependencies": { - "@pushprotocol/restapi": "1.7.19", + "@pushprotocol/restapi": "1.7.25", "@pushprotocol/socket": "^0.5.0", "react": ">=16.8.0", "styled-components": "^6.0.8" diff --git a/packages/uiweb/src/lib/components/chat/CreateGroup/AddCriteria.tsx b/packages/uiweb/src/lib/components/chat/CreateGroup/AddCriteria.tsx index 87a3d811a..de59d1b82 100644 --- a/packages/uiweb/src/lib/components/chat/CreateGroup/AddCriteria.tsx +++ b/packages/uiweb/src/lib/components/chat/CreateGroup/AddCriteria.tsx @@ -37,6 +37,7 @@ import { checkIfCustomEndpoint, checkIfGuild, checkIfPushInvite, + checkIfTokenId, checkIfTokenNFT, fetchContractInfo, getCategoryDropdownValues, @@ -56,6 +57,7 @@ const AddCriteria = ({ handlePrevious, onClose, criteriaStateManager }: ModalHea const [guildComparison, setGuildComparison] = useState(''); const [selectedChainValue, setSelectedChainValue] = useState(0); const [contract, setContract] = useState(''); + const [tokenId, setTokenId] = useState(''); const [inviteCheckboxes, setInviteCheckboxes] = useState<{ admin: boolean; owner: boolean; @@ -154,6 +156,12 @@ const AddCriteria = ({ handlePrevious, onClose, criteriaStateManager }: ModalHea title: 'Custom Endpoint', function: () => setSelectedCategoryValue(3), }, + { + id: 4, + value: CATEGORY.ERC1155, + title: 'Token ERC1155', + function: () => setSelectedCategoryValue(4), + }, ], GUILD: { value: CATEGORY.ROLES, @@ -170,6 +178,10 @@ const AddCriteria = ({ handlePrevious, onClose, criteriaStateManager }: ModalHea value: SUBCATEGORY.HOLDER, title: 'Holder', }, + ERC1155: { + value: SUBCATEGORY.HOLDER, + title: 'Holder', + }, INVITE: { value: SUBCATEGORY.DEFAULT, title: 'Default', @@ -256,7 +268,7 @@ const AddCriteria = ({ handlePrevious, onClose, criteriaStateManager }: ModalHea let subCategory = 'DEFAULT'; if (_type === 'PUSH') { - if (category === CATEGORY.ERC20 || category === CATEGORY.ERC721) { + if (category === CATEGORY.ERC20 || category === CATEGORY.ERC721 || category === CATEGORY.ERC1155) { subCategory = SUBCATEGORY.HOLDER; } else if (category === CATEGORY.CustomEndpoint) { subCategory = 'GET'; @@ -283,6 +295,7 @@ const AddCriteria = ({ handlePrevious, onClose, criteriaStateManager }: ModalHea dropdownQuantityRangeValues, selectedChainValue, dropdownChainsValues, + tokenId: Number(tokenId) }), }; @@ -320,7 +333,7 @@ const AddCriteria = ({ handlePrevious, onClose, criteriaStateManager }: ModalHea const pushData = oldValue.data as PushData; // sub category - if (oldValue.category === CATEGORY.ERC20 || oldValue.category === CATEGORY.ERC721) { + if (oldValue.category === CATEGORY.ERC20 || oldValue.category === CATEGORY.ERC721 || oldValue.category === CATEGORY.ERC1155) { if (pushData.token) { setUnit(pushData.token); } @@ -335,6 +348,7 @@ const AddCriteria = ({ handlePrevious, onClose, criteriaStateManager }: ModalHea dropdownChainsValues.findIndex((obj) => obj.value === contractAndChain[0] + ':' + contractAndChain[1]) ); setContract(contractAndChain.length === 3 ? contractAndChain[2] : ''); + setTokenId(pushData.tokenId?.toString() || '') setQuantity({ value: pushData.amount || 0, range: dropdownQuantityRangeValues.findIndex((obj) => obj.value === pushData.comparison), @@ -374,6 +388,7 @@ const AddCriteria = ({ handlePrevious, onClose, criteriaStateManager }: ModalHea setDecimals, selectedChainValue, dropdownChainsValues, + tokenId: Number(tokenId) }); }, 2000); return () => clearTimeout(getData); @@ -536,6 +551,22 @@ const AddCriteria = ({ handlePrevious, onClose, criteriaStateManager }: ModalHea /> {!!validationErrors?.tokenError && {validationErrors?.tokenError}} + {checkIfTokenId({ dropdownCategoryValues, dropdownTypeValues, selectedCategoryValue, selectedTypeValue }) && ( +
+ setTokenId(e.target.value)} + placeholder="e.g. 2" + error={!!validationErrors?.tokenId} + /> + {!!validationErrors?.tokenId && {validationErrors?.tokenId}} +
+ )}
{ const checkIfNftToken = () => { if ( criteria?.category === CATEGORY.ERC721 || - criteria?.category === CATEGORY.ERC20 + criteria?.category === CATEGORY.ERC20 || + criteria?.category === CATEGORY.ERC1155 ) return true; return false; }; + const checkIfIDToken = () => { + return criteria?.category === CATEGORY.ERC1155; + } + const getGuildRole = () => { if (!criteria?.data?.['comparison']) { return 'SPECIFIC'; @@ -123,12 +128,26 @@ const CriteriaSection = ({ criteria }: { criteria: ConditionData }) => { justifyContent="space-between" alignItems="center" > - - - {getTokenNftComparisionLabel()}{' '} + {checkIfIDToken() ? +
+ + {getTokenNftComparisionLabel()}{' '} + +
+ + {criteria?.data?.['amount']} {tokenSymbol} + + ID: {criteria?.data?.['tokenId']} +
+
+ : + + + {getTokenNftComparisionLabel()}{' '} + + {criteria?.data?.['amount']} {tokenSymbol} - {criteria?.data?.['amount']} {tokenSymbol} -
+ } { NETWORK_ICON_DETAILS[ diff --git a/packages/uiweb/src/lib/components/chat/helpers/addCriteriaUtilities.ts b/packages/uiweb/src/lib/components/chat/helpers/addCriteriaUtilities.ts index 92ebbd9fe..455e70920 100644 --- a/packages/uiweb/src/lib/components/chat/helpers/addCriteriaUtilities.ts +++ b/packages/uiweb/src/lib/components/chat/helpers/addCriteriaUtilities.ts @@ -23,9 +23,7 @@ export const getCategoryDropdownValues = ({ dropdownTypeValues, selectedTypeValue, }: InputFunctionParams) => { - return dropdownCategoryValues![ - dropdownTypeValues![selectedTypeValue!]?.value as TypeKeys - ]; + return dropdownCategoryValues![dropdownTypeValues![selectedTypeValue!]?.value as TypeKeys]; }; export const getSelectedCategoryValue = ({ @@ -39,8 +37,7 @@ export const getSelectedCategoryValue = ({ dropdownTypeValues, selectedTypeValue, }); - if (Array.isArray(category)) - return (category as DropdownValueType[])[selectedCategoryValue!].value!; + if (Array.isArray(category)) return (category as DropdownValueType[])[selectedCategoryValue!].value!; else return category.value! as SubCategoryKeys; }; @@ -60,8 +57,7 @@ export const getSelectedSubCategoryValue = ({ dropdownSubCategoryValues, selectedTypeValue, }); - if (Array.isArray(subCategory)) - return (subCategory as DropdownValueType[])[selectedCategoryValue!].value!; + if (Array.isArray(subCategory)) return (subCategory as DropdownValueType[])[selectedCategoryValue!].value!; else return subCategory.value! as SubCategoryKeys; }; @@ -77,7 +73,24 @@ export const checkIfTokenNFT = ({ selectedTypeValue, selectedCategoryValue, }); - if (category === CATEGORY.ERC20 || category === CATEGORY.ERC721) return true; + if (category === CATEGORY.ERC20 || category === CATEGORY.ERC721 || category === CATEGORY.ERC1155) return true; + + return false; +}; + +export const checkIfTokenId = ({ + dropdownCategoryValues, + dropdownTypeValues, + selectedCategoryValue, + selectedTypeValue, +}: InputFunctionParams) => { + const category = getSelectedCategoryValue({ + dropdownCategoryValues, + dropdownTypeValues, + selectedTypeValue, + selectedCategoryValue, + }); + if (category === CATEGORY.ERC1155) return true; return false; }; @@ -118,10 +131,7 @@ export const checkIfPushInvite = ({ return false; }; -export const checkIfGuild = ( - dropdownTypeValues: Array, - selectedTypeValue: number -) => { +export const checkIfGuild = (dropdownTypeValues: Array, selectedTypeValue: number) => { const accessType = dropdownTypeValues[selectedTypeValue].value; if (accessType === TYPE.GUILD) { return true; @@ -147,35 +157,23 @@ export const getSubCategoryDropdownValues = ({ }); if (Array.isArray(category)) return dropdownSubCategoryValues[ - (category as DropdownValueType[])[selectedCategoryValue!] - .value as SubCategoryKeys + (category as DropdownValueType[])[selectedCategoryValue!].value as SubCategoryKeys ]; else return dropdownSubCategoryValues[category.value as SubCategoryKeys]; }; -export const getSeletedType = ({ - dropdownTypeValues, - selectedTypeValue, -}: InputFunctionParams) => { +export const getSeletedType = ({ dropdownTypeValues, selectedTypeValue }: InputFunctionParams) => { return dropdownTypeValues![selectedTypeValue!].value || 'PUSH'; }; -export const getSelectedCategory = ({ - dropdownCategoryValues, - selectedCategoryValue, -}: InputFunctionParams) => { +export const getSelectedCategory = ({ dropdownCategoryValues, selectedCategoryValue }: InputFunctionParams) => { const category: string = - (dropdownCategoryValues!['PUSH'] as DropdownValueType[])[ - selectedCategoryValue! - ].value || CATEGORY.ERC20; + (dropdownCategoryValues!['PUSH'] as DropdownValueType[])[selectedCategoryValue!].value || CATEGORY.ERC20; return category; }; -export const getSelectedChain = ( - dropdownChainsValues: Array, - selectedChainValue: number -) => { +export const getSelectedChain = (dropdownChainsValues: Array, selectedChainValue: number) => { return dropdownChainsValues[selectedChainValue].value || 'eip155:1'; }; @@ -184,6 +182,7 @@ type FetchContractInfoParamType = { setUnit: Dispatch>; setDecimals: Dispatch>; contract: string; + tokenId: number; dropdownChainsValues: Array; selectedChainValue: number; } & InputFunctionParams; @@ -199,6 +198,7 @@ export const fetchContractInfo = async ({ setDecimals, selectedChainValue, dropdownChainsValues, + tokenId }: FetchContractInfoParamType) => { setValidationErrors((prev: any) => ({ ...prev, tokenError: undefined })); @@ -209,14 +209,7 @@ export const fetchContractInfo = async ({ }); const _chainInfo = getSelectedChain(dropdownChainsValues, selectedChainValue); - await tokenFetchHandler( - contract, - _type, - _category, - _chainInfo, - setUnit, - setDecimals - ); + await tokenFetchHandler(contract, _type, _category, _chainInfo, setUnit, setDecimals, tokenId); }; type GetCriteriaDataParamType = { @@ -228,6 +221,7 @@ type GetCriteriaDataParamType = { selectedChainValue: number; decimals: number; unit: string; + tokenId: number; url: string; guildId: string; specificRoleId: string; @@ -240,7 +234,7 @@ type GetCriteriaDataParamType = { value: number; range: number; }; -} ; +}; export const getCriteriaData = ({ type, @@ -257,17 +251,18 @@ export const getCriteriaData = ({ dropdownQuantityRangeValues, selectedChainValue, dropdownChainsValues, + tokenId, }: GetCriteriaDataParamType): Data => { if (type === 'PUSH') { - if (category === CATEGORY.ERC20 || category === CATEGORY.ERC721) { - const selectedChain = - dropdownChainsValues[selectedChainValue].value || 'eip155:1'; + if (category === CATEGORY.ERC20 || category === CATEGORY.ERC721 || category === CATEGORY.ERC1155) { + const selectedChain = dropdownChainsValues[selectedChainValue].value || 'eip155:1'; return { contract: `${selectedChain}:${contract}`, amount: quantity.value, comparison: dropdownQuantityRangeValues[quantity.range].value, - decimals: category === CATEGORY.ERC20 ? decimals : undefined, + decimals: (category === CATEGORY.ERC20 || category === CATEGORY.ERC1155) ? decimals : undefined, token: unit, + tokenId }; } else if (category === CATEGORY.INVITE) { const _inviteRoles = []; diff --git a/packages/uiweb/src/lib/components/chat/helpers/getRulesToCondtionArray.ts b/packages/uiweb/src/lib/components/chat/helpers/getRulesToCondtionArray.ts index c24da7465..8a7b59e02 100644 --- a/packages/uiweb/src/lib/components/chat/helpers/getRulesToCondtionArray.ts +++ b/packages/uiweb/src/lib/components/chat/helpers/getRulesToCondtionArray.ts @@ -1,6 +1,5 @@ import { ConditionArray } from '../exportedTypes'; import * as PushAPI from '@pushprotocol/restapi'; -import { fetchERC20Info, fetchERC721nfo } from './tokenHelpers'; export interface GroupRulesType { CHAT: ConditionArray[]; diff --git a/packages/uiweb/src/lib/components/chat/helpers/tokenGatedGroup.ts b/packages/uiweb/src/lib/components/chat/helpers/tokenGatedGroup.ts index aa626cb40..f194833e0 100644 --- a/packages/uiweb/src/lib/components/chat/helpers/tokenGatedGroup.ts +++ b/packages/uiweb/src/lib/components/chat/helpers/tokenGatedGroup.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { ethers } from 'ethers'; -import { fetchERC20Info, fetchERC721nfo } from './tokenHelpers'; +import { fetchERC1155Info, fetchERC20Info, fetchERC721nfo } from './tokenHelpers'; import { CATEGORY, CriteriaStateType, @@ -124,10 +124,15 @@ const validateTokenData = async (condition: Rule): Promise) => void, - setDecimals: (value: React.SetStateAction) => void + setDecimals: (value: React.SetStateAction) => void, + tokenId: number ): Promise<[boolean, string]> => { const isValid = ethers.utils.isAddress(contract); @@ -23,7 +25,7 @@ export const tokenFetchHandler = async ( } if (!isValid) { - if(category === CATEGORY.ERC20){ + if(category === CATEGORY.ERC20 || category === CATEGORY.ERC1155) { setUnit('TOKEN'); }else{ setUnit('NFT'); @@ -50,7 +52,7 @@ export const tokenFetchHandler = async ( return [false, '']; } - } else { + } else if(category === CATEGORY.ERC721){ // erc 721 logic const [isErr, tokenInfo] = await fetchERC721nfo(contract, _chainId); if (isErr) { @@ -64,6 +66,21 @@ export const tokenFetchHandler = async ( setUnit(tokenInfo); return [false, '']; } + } else { + // erc 1155 logic + const [isErr, tokenInfo] = await fetchERC1155Info(contract, _chainId, tokenId); + if (isErr) { + // handle error + const errMessage = `${contract} is invalid ERC1155 on chain ${_chainId}`; + setUnit('TOKEN'); + setDecimals(18); + return [true, errMessage]; + } else { + // set the token info + setUnit(tokenInfo); + setDecimals(18); + return [false, '']; + } } }; @@ -112,3 +129,39 @@ export const fetchERC721nfo = async ( return [true, ""]; } }; + +export const fetchERC1155Info = async ( + contractAddress: string, + chainId: number, + tokenId: number, +): Promise<[boolean, string]> => { + try { + const rpcURL = getChainRPC(chainId); + const provider = new ethers.providers.JsonRpcProvider(rpcURL); + + const contract = new ethers.Contract( + contractAddress, + ERC1155ContractABI, + provider + ); + + const ERC1155_INTERFACE_ID = '0xd9b67a26'; + const isERC1155 = await contract.supportsInterface(ERC1155_INTERFACE_ID); + + if(isERC1155 && tokenId !== undefined) { + try { + const uri: string | undefined = await contract.uri(tokenId); + const uriToCall = uri?.toString().replace('{id}', tokenId.toString()); + const response = await axios.get(uriToCall ?? ''); + const name = response.data?.name; + return [false, name || 'ERC1155']; + } catch (error) { + return [false, "ERC1155"]; + } + } + + return [!isERC1155, "ERC1155"]; + } catch { + return [true, "ERC1155"]; + } +}; diff --git a/packages/uiweb/src/lib/components/chat/types/index.ts b/packages/uiweb/src/lib/components/chat/types/index.ts index bee046a06..2300697f0 100644 --- a/packages/uiweb/src/lib/components/chat/types/index.ts +++ b/packages/uiweb/src/lib/components/chat/types/index.ts @@ -42,6 +42,7 @@ export type TypeKeys = typeof TYPE[keyof typeof TYPE]; export const CATEGORY = { ERC20: 'ERC20', ERC721: 'ERC721', + ERC1155: 'ERC1155', INVITE: 'INVITE', CustomEndpoint: 'CustomEndpoint', ROLES: 'ROLES', @@ -50,6 +51,7 @@ export const CATEGORY = { export const UNIT = { ERC20: 'TOKEN', ERC721: 'NFT', + ERC1155: 'TOKEN', } as const; export type UnitKeys = typeof UNIT[keyof typeof UNIT]; export const SUBCATEGORY = { @@ -89,6 +91,7 @@ export type TokenNftComparision = keyof typeof TOKEN_NFT_COMPARISION; export const CRITERIA_TYPE = { ERC20: 'Token', ERC721: 'NFT', + ERC1155: 'Token', INVITE: 'Invite', CustomEndpoint: 'URL', ROLES: 'Guild ID', diff --git a/packages/uiweb/src/lib/components/chat/types/tokenGatedGroupCreationType.ts b/packages/uiweb/src/lib/components/chat/types/tokenGatedGroupCreationType.ts index 4a91e8ec4..6e1aa6050 100644 --- a/packages/uiweb/src/lib/components/chat/types/tokenGatedGroupCreationType.ts +++ b/packages/uiweb/src/lib/components/chat/types/tokenGatedGroupCreationType.ts @@ -6,6 +6,7 @@ export interface PushData { comparison?:string; url?: string; token?:string; + tokenId?:number; } export interface GuildData { @@ -87,6 +88,7 @@ export type CriteriaValidationErrorType = { //token error tokenError?:string tokenAmount?:string; + tokenId?:string; //custom endpoint errors url?:string; } diff --git a/packages/uiweb/src/lib/hooks/useTokenSymbolLoader.ts b/packages/uiweb/src/lib/hooks/useTokenSymbolLoader.ts index 497887a4f..c00be83cd 100644 --- a/packages/uiweb/src/lib/hooks/useTokenSymbolLoader.ts +++ b/packages/uiweb/src/lib/hooks/useTokenSymbolLoader.ts @@ -4,6 +4,7 @@ import { CATEGORY, PushData } from '../components/chat/types'; import { fetchERC20Info, fetchERC721nfo, + fetchERC1155Info } from '../components/chat/helpers/tokenHelpers'; export const useTokenSymbolLoader = ( @@ -48,6 +49,11 @@ export const useTokenSymbolLoader = ( if (!isErr) { updateTokenValue(tokenInfo); } + } else if (category === CATEGORY.ERC1155) { + const [isErr, tokenInfo] = await fetchERC1155Info(address, chainId, data.tokenId ?? 0); + if (!isErr) { + updateTokenValue(tokenInfo); + } } } } @@ -61,7 +67,8 @@ const isTokenType = (conditionData: ConditionData): boolean => { if (conditionData.type === 'PUSH') { if ( conditionData.category === CATEGORY.ERC20 || - conditionData.category === CATEGORY.ERC721 + conditionData.category === CATEGORY.ERC721 || + conditionData.category === CATEGORY.ERC1155 ) { if (conditionData.data) { return true; diff --git a/packages/uiweb/yarn.lock b/packages/uiweb/yarn.lock index 5a0babdfe..db89cc339 100644 --- a/packages/uiweb/yarn.lock +++ b/packages/uiweb/yarn.lock @@ -1285,7 +1285,7 @@ __metadata: react-twitter-embed: "npm:^4.0.4" uuid: "npm:^9.0.1" peerDependencies: - "@pushprotocol/restapi": 1.7.19 + "@pushprotocol/restapi": 1.7.25 "@pushprotocol/socket": ^0.5.0 react: ">=16.8.0" styled-components: ^6.0.8