FuelLabs/fuels-ts · error · FuelError

ABI_MAIN_METHOD_MISSING

ABI_MAIN_METHOD_MISSING

Error message

An unlocked wallet is required to simulate a contract call.

What it means

Thrown by InvocationScope.simulate() when the bound account cannot sign a witness for a dry-run. simulate() must produce a signed witness via populateTransactionWitnessesSignature(), a method that exists only on BaseWalletUnlocked / WalletUnlocked. A read-only Account, a WalletLocked, or a Predicate lacks that method, so the SDK refuses to simulate. Note the code ABI_MAIN_METHOD_MISSING is mislabeled; the dedicated UNLOCKED_WALLET_REQUIRED code would describe it more accurately.

Source

Thrown at packages/program/src/functions/base-invocation-scope.ts:560

        buildPreConfirmationFunctionResult<T>({
          funcScope: this.functionInvocationScopes,
          isMultiCall: this.isMultiCall,
          program: this.program,
          transactionResponse: response,
        }),
    };
  }

  /**
   * Simulates a transaction.
   *
   * @returns The result of the invocation call.
   */
  async simulate<T = TReturn>(): Promise<DryRunResult<T>> {
    assert(this.program.account, 'Wallet is required!');

    if (!('populateTransactionWitnessesSignature' in this.program.account)) {
      throw new FuelError(
        ErrorCode.ABI_MAIN_METHOD_MISSING,
        'An unlocked wallet is required to simulate a contract call.'
      );
    }
    const transactionRequest = await this.fundWithRequiredCoins();

    const callResult = await this.program.account.simulateTransaction(transactionRequest, {
      estimateTxDependencies: false,
    });

    return buildDryRunResult<T>({
      funcScopes: this.functionInvocationScopes,
      callResult,
      isMultiCall: this.isMultiCall,
    });
  }

  /**

View on GitHub (pinned to b3f37c91ac)

Solutions

  1. Pass an unlocked wallet: Wallet.fromPrivateKey(pk) or Wallet.generate(), or await WalletLocked.unlock(password), then attach it to the contract via new Contract(id, abi, unlockedWallet).
  2. If you only need a read-only call with no signing, use .get() instead of .simulate() — get() does not require an unlocked wallet.
  3. If simulating with a predicate, manually populate the witness and use provider.dryRun() / provider.simulateTransaction() rather than InvocationScope.simulate().

Example fix

// before
const locked = Wallet.fromAddress(addr);
const contract = new Contract(id, abi, locked);
await contract.functions.balance().simulate(); // throws

// after
const unlocked = Wallet.fromPrivateKey(pk);
const contract = new Contract(id, abi, unlocked);
await contract.functions.balance().simulate();
Defensive patterns

Strategy: validation

Validate before calling

// Before calling .simulate(), confirm the account can sign a witness.
function canSimulate(account: unknown): boolean {
  return !!account && typeof (account as any).populateTransactionWitnessesSignature === 'function';
}

if (!canSimulate(contract.account)) {
  throw new Error('Pass a WalletUnlocked to the Contract to use simulate(); use .get() for read-only calls.');
}
await contract.functions.fn().simulate();

Type guard

import type { BaseWalletUnlocked } from '@fuel-ts/account';

function isUnlockedWallet(a: unknown): a is BaseWalletUnlocked {
  return !!a && typeof (a as BaseWalletUnlocked).populateTransactionWitnessesSignature === 'function';
}

Try / catch

try {
  await contract.functions.fn().simulate();
} catch (e) {
  if (e instanceof FuelError && e.code === ErrorCode.ABI_MAIN_METHOD_MISSING) {
    // swap in an unlocked wallet, or fall back to .get()
  } else throw e;
}

Prevention

When it happens

Trigger: Calling contract.functions.<fn>().simulate() (or .simulate<T>()) on a contract whose .account is a WalletLocked (Wallet.fromAddress(addr)) or a bare Account; passing a Predicate or an Account instance as the contract's account; calling .simulate() before unlocking a wallet.

Common situations: Reading contract state with a wallet you only have the address for; test setups that inject a stub Account; switching from .get() (no signing) to .simulate() (signing required) without swapping in an unlocked wallet.

Related errors


AI-assisted analysis of FuelLabs/fuels-ts@b3f37c91ac (2026-08-12). Data as JSON: /api/errors/b307b034a650755d. Report an issue: GitHub.