Testing NFT transfer with Hardhat: connect() method issue

I’m facing difficulties testing my NFT transfer functionality using Hardhat because the connect() method doesn’t seem to work as expected. Every time I attempt to transfer an NFT between accounts, it defaults to the deployer’s address instead of the intended user account.

Here’s the testing code I’m working with:

describe("NFT Transfer Tests", function () {
  it("Should successfully transfer NFT from one account to another", async function () {
    const { bridge, owner, userAccount } = await loadFixture(deployFixture);
    const { token, tokenOwner } = await loadFixture(deployMockNFTFixture);

    await token.connect(userAccount.address);
    await token.mint(userAccount.address);
    console.log(await token.balanceOf(userAccount.address));
    console.log(await token.ownerOf(1));

    console.log("bridge.target: ", bridge.target);
    console.log("owner.address: ", owner.address);

    await token.connect(userAccount.address);
    await token.transferFrom(userAccount.address, bridge.target, 1);
    
    console.log(await token.balanceOf(bridge.target));
    console.log(await token.ownerOf(1));
  });
});

The issue arises when the transferFrom method triggers an error: ERC721: caller is not token owner or approved. Despite using connect(userAccount.address), it still processes the transaction from the deployer’s address.

I attempted to resolve this by adding an approval step beforehand:

await token.connect(userAccount.address);
await token.approve(bridge.target, 1);
await token.transferFrom(userAccount.address, bridge.target, 1);

However, I encounter the error: ERC721: approve caller is not token owner or approved for all.

What could be wrong that prevents the msg.sender from being updated? The NFT is correctly minted to userAccount, but I’m stuck on transferring it from that account. What should I check?

Classic hardhat gotcha! You’re using .address in your connect() call - that won’t work. The connect method needs the actual signer object, not the address string. Change token.connect(userAccount.address) to just token.connect(userAccount). Also double-check that userAccount is a real signer from ethers.getSigners(), not just some address you made up.

Oh wow, I’ve been down this exact rabbit hole before! :sweat_smile: The .address issue others mentioned is spot on, but I’m curious about something else in your setup…

Have you checked what’s happening in your deployMockNFTFixture? There might be some weird interaction between your two fixtures. Is the token contract from deployMockNFTFixture getting deployed with the same network/provider context as the accounts from deployFixture?

Also, why are you calling connect() twice in some places? You do await token.connect(userAccount.address) before the mint, then again before the transferFrom. Any particular reason for that pattern?

One thing that helped me debug similar issues was adding console.log("Current signer:", await token.signer?.getAddress()) right after each connect call. Sometimes the signer wasn’t getting set right even after fixing the .address thing.

What does your deployMockNFTFixture look like? The token contract might be getting deployed with a different signer context that’s interfering with the connect calls later on.

Your problem is with the connect() method. You’re passing userAccount.address (just a string) instead of the actual signer. Change await token.connect(userAccount.address) to await token.connect(userAccount) - drop the .address part.

I hit this exact issue when I started with Hardhat testing. connect() needs the signer object to set the transaction sender properly, not just the address string. When you pass the address, it doesn’t change the signer context and transactions default to the deployer.

Make sure your deployFixture returns the signer objects correctly for userAccount - this’ll bite you if it’s wrong. You can check by logging the signer address after connecting: console.log(await token.connect(userAccount).signer.getAddress()).