调用智能合约函数时出现TypeError: Cannot read property 'apply' of undefined错误
I’ve run into this exact error before working with Truffle contracts in Angular, so let’s break down what’s causing it and how to fix it step by step.
What’s Causing This Error?
This error pops up when your Truffle contract instance tries to call the sendAsync method on an Ethereum provider that’s either undefined or doesn’t support the old sendAsync interface (like modern MetaMask, which uses EIP-1193’s request method instead). Looking at your CarService.ts code, the most likely culprits are missing provider initialization or version mismatches between your dependencies.
Step-by-Step Fixes
1. Ensure Your Web3 Service Provides a Valid Provider
First, check your Web3Service to make sure it’s correctly fetching the Ethereum provider (e.g., from MetaMask). Modern MetaMask no longer injects web3.currentProvider by default—you need to use window.ethereum instead. Here’s how to update your Web3Service:
// web3.service.ts import { Injectable } from '@angular/core'; import Web3 from 'web3'; @Injectable() export class Web3Service { web3: Web3 | undefined; constructor() { this.initWeb3(); } async initWeb3() { // Check if MetaMask or another provider is available if (window.ethereum) { try { // Request account access if needed await window.ethereum.request({ method: 'eth_requestAccounts' }); this.web3 = new Web3(window.ethereum); } catch (error) { console.error('User denied account access', error); } } else if (window.web3) { // Fallback for older dApp browsers this.web3 = new Web3(window.web3.currentProvider); } else { console.error('No Ethereum provider found'); } } async ensureWeb3IsReady(): Promise<Web3> { if (!this.web3) { await this.initWeb3(); } return this.web3 as Web3; } }
2. Properly Initialize Your Truffle Contract with the Provider
In your CarServiceService, don’t try to set the provider synchronously in the constructor—wait for the Web3 provider to be ready first. Use async/await with the ensureWeb3IsReady method from your Web3Service:
// CarService.ts import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { fromPromise } from 'rxjs/observable/fromPromise'; import { Web3Service } from './web3.service'; import contract from '@truffle/contract'; // Use maintained @truffle/contract package const carArtifacts = require('../../../build/contracts/Car.json'); @Injectable() export class CarServiceService { private CarContract: any; constructor(private web3Service: Web3Service) { this.CarContract = contract(carArtifacts); } async initContract() { const web3 = await this.web3Service.ensureWeb3IsReady(); // Set the provider for the contract this.CarContract.setProvider(web3.currentProvider); // Fetch the deployed contract instance return await this.CarContract.deployed(); } // Example function to call a contract method getCarDetails(carId: number): Observable<any> { return fromPromise(this.initContract().then(async (instance: any) => { return await instance.getCar(carId); })); } }
3. Switch to the Maintained Truffle Contract Package
The old truffle-contract package is deprecated—switch to @truffle/contract which supports modern EIP-1193 providers. Run these commands to update:
npm uninstall truffle-contract npm install @truffle/contract
Update your import statement as shown in the code above to match.
4. Verify Dependency Version Compatibility
Make sure your web3 and @truffle/contract versions are compatible. As of 2024, these versions work reliably together:
web3: ^1.10.0@truffle/contract: ^4.6.31
Check your package.json and adjust versions if needed.
Final Checks
- Ensure MetaMask is installed and unlocked when testing your app.
- If using a local testnet (like Ganache), confirm your provider points to the correct network URL (e.g.,
http://localhost:7545).
内容的提问来源于stack exchange,提问作者Sarah

