You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Web3调用acceptBId接受NFT出价报未指定from地址错误排查

问题描述

运行NFT出价接受功能时抛出如下错误:

Error: No "from" address specified in neither the given options, nor the default options. At Object.ContractNoFromAddressDefinedError

  • 功能背景:该代码此前可正常运行,作用是接受其他账户对已创建NFT的出价,出现报错后尝试给web3.currentProvider添加await处理仍未解决问题
  • 复现规律:报错仅在页面重新打开后首次操作时触发;如果修改代码触发热更新保存、不重新加载页面,功能可正常运行,能正常弹出MetaMask交易确认通知,初步怀疑和.enable()方法逻辑、钱包授权待确认状态相关。

原功能实现代码如下:

async function bidAccept() {
    var web3 = new Web3(window.ethereum);
    var tyye = "";
    if (tokendet.type === "erc721") {
      tyye = 721;
    } else {
      tyye = 1155;
    }
    try {
      setacceptbitstatus(true);
      var passAmt = parseFloat(YouWillPayFee) + parseFloat(YouWillGet);
      passAmt = passAmt.toFixed(config.toFixed);
      if (typeof window !== 'undefined' && typeof window.web3 !== 'undefined') {
        await window.web3.currentProvider.enable();
        web3 = new Web3(window.web3.currentProvider);  
      }
      var CoursetroContract = new web3.eth.Contract(DETH, dethContractAddr);
      var tokenBal = await CoursetroContract.methods
        .balanceOf(acceptbidaddress)
        .call();

      passAmt = web3.utils.toWei(passAmt).toString();

      var payAmt = parseFloat(passAmt);
      if (tokenBal >= payAmt) {
        if (tokendet.type === "erc721") {
          CoursetroContract = new web3.eth.Contract(BEP721, singleContract);
          var result = await CoursetroContract.methods
            .acceptBId(
              bidCurrency,
              acceptbidaddress,
              passAmt,
              productdet.tokenCounts
            )
            .send({ from: useraddress });
        } else {
          CoursetroContract = new web3.eth.Contract(BEP1155, multicontractAddr);
          var result = await CoursetroContract.methods
            .acceptBId(
              bidCurrency,
              acceptbidaddress,
              passAmt,
              productdet.tokenCounts,
              qtyVal
            )
            .send({ from: useraddress });
        }
        setacceptbitstatus(false);

        var adr = acceptbidaddress;
        var postdata = {
          tokenCounts: productdet.tokenCounts,
          tokenQuan: acceptcount,
          tokenOwner: productdet.tokenOwner,
          newTokenOwner: adr,
          type: tokendet.type,
        };
        if (tokendet.type === "erc721") {
          postdata = {
            tokenCounts: productdet.tokenCounts,
            tokenQuan: acceptcount,
            oldOwner: productdet.tokenOwner,
            newTokenOwner: acceptbidaddress,
            type: tokendet.type,
            acceptbidId: acceptbidId,
            contractAddress: singleContract,
            acceptbid: "yes",
            tokenPrice: totalpay,
            servicefee: servicePer,
          };
          var updated = await postMethod(
            baseurl + "api/singletoken-bit-buy",
            postdata
          );
          toastAlert("success", "Bid Accepted Successfully", "success");
          setTimeout(() => {
            window.location.href = "/Author?user=" + useraddress;
          }, 1500);
        } else if (tokendet.type === "erc1155") {
          postdata = {
            tokenCounts: productdet.tokenCounts,
            tokenQuan: acceptcount,
            oldOwner: productdet.tokenOwner,
            newTokenOwner: acceptbidaddress,
            type: tokendet.type,
            acceptbidId: acceptbidId,
            contractAddress: multicontractAddr,
            acceptbid: "yes",
            tokenPrice: totalpay,
            servicefee: servicePer,
          };
          var updated = await postMethod(
            baseurl + "api/multitoken-bit-buy",
            postdata
          );
          toastAlert("success", "Bid Accepted Successfully", "success");
          setTimeout(() => {
            window.location.href = "/Author?user=" + useraddress;
          }, 1500);
        }
      } else {
        setacceptbitstatus(false);
        toastAlert("error", "Bidder doesnt have Enough Balance ", "success");
      }
    } catch (err) {
      console.log(err);
      setacceptbitstatus(false);
      toastAlert("error", "Failed to accept", "success");
    }
  }
问题根因
  • 钱包初始化时序错误:页面冷启动首次加载时,全局变量useraddress还未完成赋值,就被直接传入合约send方法的from参数,导致参数为空触发报错;热更新时页面上下文保留了之前操作拿到的地址和provider实例,所以不会触发问题。
  • 接口使用过时:代码里调用的window.web3.currentProvider.enable()是MetaMask早期废弃的接口,首次加载时无法稳定返回授权账户列表,还存在window.ethereum和旧版window.web3注入对象混用的问题,两个对象状态不同步会进一步加剧时序异常。
  • 缺少必要校验:没有在发送交易前校验当前连接的钱包地址是否存在,直接使用未初始化的变量发起合约调用。
修复方案
  • 统一使用window.ethereum作为钱包provider,移除旧版window.web3的混用逻辑,替换废弃的enable()方法,改用官方推荐的eth_requestAccounts方法请求钱包授权,直接从授权返回值里拿到当前用户地址。
  • 不要依赖外部全局的useraddress变量,在bidAccept方法内拿到授权地址后做非空校验,确认地址有效后再传入合约调用参数。
  • 修正原代码里toast提示类型传错的小问题,错误提示的type参数统一传error。
修正后代码
async function bidAccept() {
    if (!window.ethereum) {
        toastAlert("error", "Please install MetaMask wallet", "error");
        return;
    }
    const web3 = new Web3(window.ethereum);
    const contractType = tokendet.type === "erc721" ? 721 : 1155;
    try {
        setacceptbitstatus(true);
        // 显式请求钱包授权,直接获取当前连接的账户地址
        const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
        const currentUserAddress = accounts[0];
        if (!currentUserAddress) {
            throw new Error("Please connect your wallet first");
        }

        let passAmt = parseFloat(YouWillPayFee) + parseFloat(YouWillGet);
        passAmt = passAmt.toFixed(config.toFixed);
        
        const dethContract = new web3.eth.Contract(DETH, dethContractAddr);
        const tokenBal = await dethContract.methods.balanceOf(acceptbidaddress).call();
        passAmt = web3.utils.toWei(passAmt).toString();
        const payAmt = parseFloat(passAmt);

        if (tokenBal < payAmt) {
            setacceptbitstatus(false);
            toastAlert("error", "Bidder does not have enough balance", "error");
            return;
        }

        let txResult;
        let nftContract;
        const bidParams = [bidCurrency, acceptbidaddress, passAmt, productdet.tokenCounts];
        if (contractType === 721) {
            nftContract = new web3.eth.Contract(BEP721, singleContract);
            txResult = await nftContract.methods.acceptBId(...bidParams).send({ from: currentUserAddress });
        } else {
            nftContract = new web3.eth.Contract(BEP1155, multicontractAddr);
            bidParams.push(qtyVal);
            txResult = await nftContract.methods.acceptBId(...bidParams).send({ from: currentUserAddress });
        }
        setacceptbitstatus(false);

        const basePostData = {
            tokenCounts: productdet.tokenCounts,
            tokenQuan: acceptcount,
            oldOwner: productdet.tokenOwner,
            newTokenOwner: acceptbidaddress,
            type: tokendet.type,
            acceptbidId: acceptbidId,
            acceptbid: "yes",
            tokenPrice: totalpay,
            servicefee: servicePer,
        };

        let apiPath, postData;
        if (contractType === 721) {
            apiPath = "api/singletoken-bit-buy";
            postData = { ...basePostData, contractAddress: singleContract };
        } else {
            apiPath = "api/multitoken-bit-buy";
            postData = { ...basePostData, contractAddress: multicontractAddr };
        }

        await postMethod(baseurl + apiPath, postData);
        toastAlert("success", "Bid Accepted Successfully", "success");
        setTimeout(() => {
            window.location.href = "/Author?user=" + currentUserAddress;
        }, 1500);

    } catch (err) {
        console.log(err);
        setacceptbitstatus(false);
        toastAlert("error", `Failed to accept bid: ${err.message || "unknown error"}`, "error");
    }
}
额外优化建议
  • 建议把钱包连接、地址获取逻辑放到页面初始化阶段执行,不要等用户点击接受出价按钮才触发授权,进一步降低时序问题概率。
  • 增加window.ethereum的accountsChanged事件监听,钱包切换账户时实时更新当前用户地址,避免交易发起地址和用户当前选中地址不一致的问题。

内容的提问来源于stack exchange,提问作者ksd030687d

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 17:27:52