I am using solidity and hardhat to create smart contract. Version 6.6.2. Here's how my smart contract looks like:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract CreatorRegistry {
struct Creator {
address walletAddress;
string name;
string b64Image;
}
Creator[] public creators;
mapping(address => bool) public isCreatorRegistered;
function signup(address _walletAddress, string memory _name, string memory _b64Image) public {
require(!isCreatorRegistered[_walletAddress], "Address is already registered");
Creator memory newCreator;
newCreator.walletAddress = _walletAddress;
newCreator.name = _name;
newCreator.b64Image = _b64Image;
creators.push(newCreator);
isCreatorRegistered[_walletAddress] = true;
}
function getCreator(uint256 _index) public view returns (
address walletAddress,
string memory name,
string memory b64Image
) {
require(_index < creators.length, "Invalid index");
Creator storage creator = creators[_index];
walletAddress = creator.walletAddress;
name = creator.name;
b64Image = creator.b64Image;
}
function getCreatorCount() public view returns (uint256) {
return creators.length;
}
}
And here's how I am trying to invoke the contract:
// javascript frontend
const newUser =
(await registrationContract?.getCreatorCount?.()) ??
'Oops we lost';
console.log('New User = ', newUser);
But it fails with the error:
Error: could not decode result data (value="0x", info={ "method": "getCreatorCount", "signature": "getCreatorCount()" }, code=BAD_DATA, version=6.6.2)
Can someone help me in understanding what's going wrong here?
From the error it seems like its not able to decode the data in javascript side or is it an issue at solidity side? How can I fix this?
Edit 1:
As @Yilmaz mentioned in comments: ?.
is an optional chaining operator in javascript which ensures not to crash the program if the attribute remains undefined. However just to be super sure, I did this as well:
const newUserCount =
(await registrationContract.getCreatorCount()) ??
'Oops we lost';
which failed too.
Edit 2 Github forum: There's a github forum which suggests that due to varied network this issue may show up: https://github.com/web3/web3.js/issues/1629
So I was making sure that I am referring to localhost network and right wallet address. Even after configuring that, I am still getting the same error:
This is how my registrationContract looks like:
'use client';
import { Contract, InterfaceAbi, ethers } from 'ethers';
import Web3Modal from 'web3modal';
import { ContractRunner } from 'ethers';
import {
contentCreatorABI,
creatorRegistryABI,
contentCreatorAddress,
creatorRegistryAddress,
} from './contractClient';
import { Web3Window } from './web3SignIn';
const getSmartContract = (
address: string,
abi: InterfaceAbi,
provider: ContractRunner
): Contract => new ethers.Contract(address, abi, provider);
const getContracts = async (): Promise<{
error: boolean;
message?: string;
data?: { creatorContract?: Contract; registrationContract: Contract };
}> => {
const { ethereum } = window as Web3Window;
if (!ethereum)
return {
error: true,
message: 'Please install metamask extension and try refreshing the page',
};
try {
const provider = new ethers.BrowserProvider(ethereum);
const signer = await provider.getSigner();
const creatorContract = getSmartContract(
contentCreatorAddress,
contentCreatorABI,
signer
);
const registrationContract = getSmartContract(
creatorRegistryAddress,
creatorRegistryABI,
signer
);
return { error: false, data: { registrationContract } };
} catch (err) {
return { error: true, message: (err as { message: string }).message };
}
};
export { getContracts };
For me the error was coming because I was getting response from solidity in uint8
which is equivalent to long in javascript and was required to correctly decrypted.
After decrypting it correctly, the issue went off.
Here's the final code:
at client side:
'use client';
import { Contract, InterfaceAbi, ethers } from 'ethers';
import { ContractRunner } from 'ethers';
import {
contentCreatorABI,
creatorRegistryABI,
contentCreatorAddress,
creatorRegistryAddress,
} from './contractClient';
import { Web3Window } from './web3SignIn';
const getSmartContract = (
address: string,
abi: InterfaceAbi,
provider: ContractRunner
): Contract => new ethers.Contract(address, abi, provider);
const getContracts = async (): Promise<{
error: boolean;
message?: string;
data?: { creatorContract: Contract; registrationContract: Contract };
}> => {
const { ethereum } = window as Web3Window;
if (!ethereum)
return {
error: true,
message: 'Please install metamask extension and try refreshing the page',
};
try {
const provider = new ethers.BrowserProvider(ethereum);
const signer = await provider.getSigner();
const creatorContract = getSmartContract(
contentCreatorAddress,
contentCreatorABI,
signer
);
const registrationContract = getSmartContract(
creatorRegistryAddress,
creatorRegistryABI,
signer
);
return { error: false, data: { registrationContract, creatorContract } };
} catch (err) {
return { error: true, message: (err as { message: string }).message };
}
};
export { getContracts };
And here's how I invoke it:
'use client';
import { loginWithWallet } from '@@/solidity/web3SignIn';
import useWeb3Store from '@@/store/web3Walltet';
import classes from './MetaMaskLogin.module.css';
import { useEffect, useState } from 'react';
import { getContracts } from '@@/solidity/contracts';
import { useRouter } from 'next/navigation';
import { NormalImage } from '@@/components/NormalImage';
const MetaMaskLogin = () => {
const {
setAccount,
setNameAndImg,
setCreatorContract,
setRegistrationContract,
registrationContract,
account,
} = useWeb3Store();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [firstTime, setFirstTime] = useState(false);
const [uName, setUName] = useState('');
const [imgUrl, setImgUrl] = useState(
'https://www.gravatar.com/avatar/00000000000000000000000000000000'
);
const router = useRouter();
const loginToMetaMask = async () => {
setLoading(true);
loginWithWallet()
.then((account) => {
if (account) {
setAccount(account);
} else
setError(
'Unexpected error occured while logging in. Please refresh the page and retry.'
);
})
.catch((error) => {
setError(`Error occured while logging in: ${error.message}`);
})
.finally(() => setLoading(false));
};
const getUserDetails = async () => {
if (registrationContract)
try {
const newUserCount =
(await registrationContract.getCreatorByAddress(account)) ??
'Oops we lost';
const [_, name, b64Image] = newUserCount;
if (name === 'Not Found') setFirstTime(true);
else {
setFirstTime(false);
setNameAndImg(name, b64Image);
//TODO: Redirect to main page
router.push('/');
}
} catch (err) {
console.log('Error occured with smart contract = ', err);
}
};
useEffect(() => {
if (account) {
setLoading(true);
getContracts()
.then(async ({ error, data, message }) => {
if (!error && data) {
const { creatorContract, registrationContract } = data;
setCreatorContract(creatorContract);
setRegistrationContract(registrationContract);
} else
setError(
`Error occured while fetching smart contracts: ${
message ?? 'Unknown error'
}`
);
})
.catch((error) => setError(error.message))
.finally(() => setLoading(false));
}
}, [account, setCreatorContract, setRegistrationContract]);
useEffect(() => {
getUserDetails();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [registrationContract]);
const registerUser = async () => {
setLoading(true);
if (registrationContract) {
const user = await registrationContract.signup(account, uName, imgUrl);
await user.wait();
setFirstTime(false);
//TODO: Redirect to main page
setLoading(false);
router.push('/');
}
};
return (
<div className={classes.container}>
<NormalImage
className={classes.metamaskLogo}
src='/meta-mask.png'
/>
{loading ? (
<p className={classes.SignInText}>Loading...</p>
) : (
<button
className={'StandardButton'}
onClick={loginToMetaMask}
>
Login to Metamask
</button>
)}
{error != null && <p className={classes.ErrorText}>{error}</p>}
{firstTime && (
<>
<input
type='text'
className={classes.Input}
placeholder='Please Input your Name'
value={uName}
onChange={(e) => setUName(e.target.value)}
/>
<input
type='url'
className={classes.Input}
placeholder='Please Input your Image Avatar URL'
value={imgUrl}
onChange={(e) => setImgUrl(e.target.value)}
/>
<button
className='StandardButton'
onClick={registerUser}
>
Complete Registration
</button>
</>
)}
</div>
);
};
export default MetaMaskLogin;
At server side:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract CreatorRegistry {
struct Creator {
address walletAddress;
string name;
string b64Image;
}
Creator[] public creators;
mapping(address => bool) public isCreatorRegistered;
function signup(address _walletAddress, string memory _name, string memory _b64Image) public {
require(!isCreatorRegistered[_walletAddress], "Address is already registered");
Creator memory newCreator;
newCreator.walletAddress = _walletAddress;
newCreator.name = _name;
newCreator.b64Image = _b64Image;
creators.push(newCreator);
isCreatorRegistered[_walletAddress] = true;
}
function getCreator(uint256 _index) public view returns (
address walletAddress,
string memory name,
string memory b64Image
) {
require(_index < creators.length, "Invalid index");
Creator storage creator = creators[_index];
walletAddress = creator.walletAddress;
name = creator.name;
b64Image = creator.b64Image;
}
function getCreatorCount() public view returns (uint256) {
return creators.length;
}
function getCreatorByAddress(address _walletAddress) public view returns (
address walletAddress,
string memory name,
string memory b64Image
) {
for (uint256 i = 0; i < creators.length; i++) {
Creator storage creator = creators[i];
if (creator.walletAddress == _walletAddress) {
walletAddress = creator.walletAddress;
name = creator.name;
b64Image = creator.b64Image;
return (walletAddress, name, b64Image);
}
}
// Creator not found
return(walletAddress, "Not Found", "Not Found");
}
}