Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create group chat comp contract verify #772

Merged
78 changes: 75 additions & 3 deletions packages/uiweb/src/lib/components/chat/CreateGroup/AddCriteria.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ import {
TypeKeys,
ReadonlyInputType,
} from '../types';
import { MdError } from 'react-icons/md';
import useToast from '../reusables/NewToast';
import { tokenFetchHandler } from '../helpers/tokenHelpers';


import {
CriteriaValidationErrorType,
Data,
Expand Down Expand Up @@ -65,13 +70,16 @@ const AddCriteria = ({
const [url, setUrl] = useState<string>('');
const [guildId, setGuildId] = useState<string>('');
const [specificRoleId, setSpecificRoleId] = useState<string>('');
const [unit, setUnit] = useState('TOKEN')
const [decimals, setDecimals] = useState(18)

const [quantity, setQuantity] = useState<{ value: number; range: number }>({
value: 0,
range: 0,
});
const { env } = useChatData();
const theme = useContext(ThemeContext);
const groupInfoToast = useToast()

const isMobile = useMediaQuery(device.mobileL);

Expand Down Expand Up @@ -323,7 +331,8 @@ const AddCriteria = ({
contract: `${selectedChain}:${contract}`,
amount: quantity.value,
comparison: dropdownQuantityRangeValues[quantity.range].value,
decimals: 18,
decimals: category === CATEGORY.ERC20 ? decimals : undefined,
token:unit
};
} else if (category === CATEGORY.INVITE) {
const _inviteRoles = [];
Expand Down Expand Up @@ -367,7 +376,11 @@ const AddCriteria = ({
if (Object.keys(errors).length) {
setValidationErrors(errors);
} else {
criteriaState.addNewRule(rule);
const isSuccess = criteriaState.addNewRule(rule);
if(!isSuccess){
showError("Selected Criteria was already added")
return
}
if (handlePrevious) {
handlePrevious();
}
Expand Down Expand Up @@ -404,6 +417,15 @@ const AddCriteria = ({
)
);

if(pushData.token){
setUnit(pushData.token)
}

if(pushData.decimals){
setDecimals(decimals)
}

// TODO: make helper function for this
const contractAndChain: string[] = (
pushData.contract || 'eip155:1:0x'
).split(':');
Expand Down Expand Up @@ -442,6 +464,52 @@ const AddCriteria = ({
}
}, []);

const getSeletedType = ()=>{
return dropdownTypeValues[selectedTypeValue].value || "PUSH"
}

const getSelectedCategory =()=>{
const category:string = (dropdownCategoryValues["PUSH"] as DropdownValueType[])[
selectedCategoryValue
].value || CATEGORY.ERC20

return category
}

const getSelectedChain = () =>{
return dropdownChainsValues[selectedChainValue].value || "eip155:1"
}


// Fetch the contract info
useEffect(()=>{
// TODO: optimize to reduce this call call when user is typing
(async()=>{
const _type = getSeletedType();
const _category:string = getSelectedCategory()
const _chainInfo = getSelectedChain()

await tokenFetchHandler(
contract,
_type,
_category,
_chainInfo,
setUnit,
setDecimals,
)

})()
},[contract,selectedCategoryValue,selectedChainValue])

const showError = (errorMessage: string) => {
groupInfoToast.showMessageToast({
toastTitle: 'Error',
toastMessage: errorMessage,
toastType: 'ERROR',
getToastIcon: (size) => <MdError size={size} color="red" />,
});
};

return (
<Section
flexDirection="column"
Expand Down Expand Up @@ -512,14 +580,18 @@ const AddCriteria = ({
inputValue={contract}
onInputChange={(e: any) => setContract(e.target.value)}
placeholder="e.g. 0x123..."
error={!!validationErrors?.tokenError}
/>
{!!validationErrors?.tokenError && (
<ErrorSpan>{validationErrors?.tokenError}</ErrorSpan>
)}
<QuantityInput
dropDownValues={dropdownQuantityRangeValues}
labelName="Quantity"
inputValue={quantity}
onInputChange={onQuantityChange}
placeholder="e.g. 1.45678"
unit={'TOKEN'}
unit={unit}
/>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ThemeContext } from '../theme/ThemeProvider';
import Dropdown, { DropdownValueType } from '../reusables/DropDown';
import { ConditionArray, ConditionData, IChatTheme } from '../exportedTypes';
import { useClickAway } from '../../../hooks';
import { CATEGORY, CRITERIA_TYPE, CriteriaType, TOKEN_NFT_COMPARISION, TokenNftComparision } from '../types';
import { CATEGORY, CRITERIA_TYPE, CriteriaType, PushData, TOKEN_NFT_COMPARISION, TokenNftComparision } from '../types';

import EditSvg from '../../../icons/EditSvg.svg';
import RemoveSvg from '../../../icons/RemoveSvg.svg';
Expand Down Expand Up @@ -94,6 +94,19 @@ const CriteriaSection = ({ criteria }: { criteria: ConditionData }) => {
return (GUILD_COMPARISON_OPTIONS.find(option => option.value === criteria?.data?.['comparison']))?.heading;

}

const getTokenSymbol = (conditionData:ConditionData)=>{
if(conditionData.data){
const data:PushData = conditionData.data;
if(data.token){
// TODO truncate the token symbol
return data.token
}
}

return conditionData.category
}

return (
<Section gap="8px">
<Span
Expand All @@ -112,7 +125,7 @@ const CriteriaSection = ({ criteria }: { criteria: ConditionData }) => {
{getTokenNftComparisionLabel()}{' '}
</Span>
{/* need to fetch token symbol */}
{criteria?.data?.['amount']} {criteria.category}
{criteria?.data?.['amount']} {getTokenSymbol(criteria)}
</Span>
)}
{criteria.category === CATEGORY.INVITE && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@ export const CreateGroupModal: React.FC<CreateGroupModalProps> = ({
}
}, [activeComponent]);

const [groupInputDetails, setGroupInputDetails] =
useState<GroupInputDetailsType>({
groupName: '',
groupDescription: '',
groupImage: '',
});
const useDummyGroupInfo = true;
const [groupInputDetails, setGroupInputDetails] = useState<GroupInputDetailsType>({
groupName: useDummyGroupInfo ? 'This is duumy group name' : '',
groupDescription: useDummyGroupInfo ? 'This is dummy group description for testing' : '',
groupImage: useDummyGroupInfo ? ProfilePicture : ''
})

const renderComponent = () => {
switch (activeComponent) {
Expand Down
38 changes: 36 additions & 2 deletions packages/uiweb/src/lib/components/chat/helpers/tokenGatedGroup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import axios from 'axios';
import { CriteriaStateType, CriteriaValidationErrorType, Data, GuildData, Rule, TYPE } from '../types';
import { CATEGORY, CriteriaStateType, CriteriaValidationErrorType, Data, GuildData, PushData, Rule, TYPE } from '../types';
import { fetchERC20Info, fetchERC721nfo } from './tokenHelpers';
import { ethers } from "ethers";

const handleDefineCondition = (
entryCriteria: CriteriaStateType,
Expand Down Expand Up @@ -67,12 +69,44 @@ const validateGUILDData = async (condition: Rule): Promise<CriteriaValidationErr
return errors;
};

const validateTokenData = async (condition:Rule):Promise<CriteriaValidationErrorType> =>{
const data:PushData = condition.data;
const _contract = data.contract || ""
const _eip155Format = _contract.split(":")

if(_eip155Format.length !==3){
return {tokenError:"Invalid contract address"}
}

const [chainId, address] = [parseInt(_eip155Format[1]), _eip155Format[2]]

if(!ethers.utils.isAddress(address)){
return {tokenError:`Invalid contract address`}
}

const [err] = condition.category === CATEGORY.ERC721 ?
await fetchERC721nfo(address, chainId) : await fetchERC20Info(address, chainId);

if(err){
return {tokenError:`Invalid ${condition.category} contract`}
}

return {}
}

const validationCriteria = async (condition: Rule):Promise<CriteriaValidationErrorType> => {
if(condition.type === TYPE.GUILD)
{
return validateGUILDData(condition);
}else{
if(condition.category === CATEGORY.INVITE){
return {}
}else if (condition.category === CATEGORY.CustomEndpoint){
return {}
}else{
return validateTokenData(condition)
}
}

return {};
}
export { handleDefineCondition ,validationCriteria};
30 changes: 30 additions & 0 deletions packages/uiweb/src/lib/components/chat/helpers/tokenHelpers/abi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export const NFTContractABI = [
{
inputs: [],
name: "name",
outputs: [{ internalType: "string", name: "", type: "string" }],
stateMutability: "view",
type: "function",
},
];

export const TokenContractABI = [
{
constant: true,
inputs: [],
name: "symbol",
outputs: [{ internalType: "string", name: "", type: "string" }],
payable: false,
stateMutability: "view",
type: "function",
},
MdTeach marked this conversation as resolved.
Show resolved Hide resolved
{
constant: true,
inputs: [],
name: "decimals",
outputs: [{ internalType: "uint8", name: "", type: "uint8" }],
payable: false,
stateMutability: "view",
type: "function",
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const getInfuraUrlFor = (network: string, key: string) =>
`https://${network}.infura.io/v3/${key}`;

const getRpcURL = (network: string, key: string) => {
return getInfuraUrlFor(network, key);
};

export const getChainRPC = (chainId: number): string => {
// TODO: use key as secrect
const key = "183499af0dd447c782ffe67f3ef7fa11";
switch (chainId) {
case 1:
return getRpcURL("mainnet", key);
case 137:
return getRpcURL("polygon-mainnet", key);
case 10:
return getRpcURL("optimism-mainnet", key);
case 56:
return "https://bsc-dataseed.binance.org/";
case 5:
return getRpcURL("goerli", key);
case 420:
return getRpcURL("optimism-goerli", key);
case 80001:
return getRpcURL("polygon-mumbai", key);
case 97:
return "https://data-seed-prebsc-1-s1.binance.org:8545";
default:
return getRpcURL("mainnet", key);
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { ethers } from "ethers";
import { getChainRPC } from "./chain";
import { NFTContractABI, TokenContractABI } from "./abi";

interface ERC20InfoType {
symbol: string;
decimals: number;
}

export const fetchERC20Info = async (
contractAddress: string,
chainId: number
): Promise<[boolean, ERC20InfoType]> => {
try {
const rpcURL = getChainRPC(chainId);
const provider = new ethers.providers.JsonRpcProvider(rpcURL);
const contract:any = new ethers.Contract(
contractAddress,
TokenContractABI,
provider
);

const [symbol, decimals] = await Promise.all([
contract.symbol(),
contract.decimals(),
]);

return [false, { symbol: symbol, decimals: decimals }];
} catch {
return [true, { symbol: "", decimals: 0 }];
}
};

export const fetchERC721nfo = async (
contractAddress: string,
chainId: number
): Promise<[boolean, string]> => {
try {
const rpcURL = getChainRPC(chainId);
const provider = new ethers.providers.JsonRpcProvider(rpcURL);

const contract:any = new ethers.Contract(
contractAddress,
NFTContractABI,
provider
);

const name = await contract.name();
return [false, name];
} catch {
return [true, ""];
}
};
Loading