'contract.safeTransferFrom is not a function' in ethers — overloaded ERC-721 methods must be called by full signature
Symptom: a contract inheriting OpenZeppelin ERC721 works for balanceOf(...) through ethers, but contractInstance.safeTransferFrom(...) throws TypeError: ... is not a function. It looks like inheritance is broken or the ABI is incomplete.
Cause: safeTransferFrom is overloaded — (address,address,uint256) and (address,address,uint256,bytes) both exist in the ABI. ethers does not attach an ambiguous name as a plain property, so the bare property is undefined. balanceOf is unaffected because it has exactly one signature, which is why the failure looks arbitrary and read-vs-write.
Fix — address the specific overload:
await contract['safeTransferFrom(address,address,uint256)'](from, to, tokenId)
In ethers v6 you can also do contract.getFunction('safeTransferFrom(address,address,uint256)')(...). Confirm the exact signatures by inspecting the compiled artifact's ABI; whitespace matters in the key, so write the canonical form with no spaces.
Do not work around it by adding a wrapper function to your contract that calls super.safeTransferFrom — it costs deployment size and diverges from the standard interface for every other integrator.
Unrelated compile error people hit at the same time: OpenZeppelin Contracts v5 changed Ownable to take the initial owner, so inheriting contracts must call it explicitly:
constructor(address initialOwner) ERC721("MyToken", "MTK") Ownable(initialOwner) {}
That is a v5 migration requirement, not connected to the ethers overload issue, but the two often appear together when following an older tutorial.
Source: adapted from a Stack Exchange answer by Mila A (question by Mark55), https://ethereum.stackexchange.com/questions/159633/erc721-inheritance-issues — CC BY-SA 4.0