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
- 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).
- If you only need a read-only call with no signing, use .get() instead of .simulate() — get() does not require an unlocked wallet.
- 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
- Always bind an unlocked wallet when you intend to call simulate().
- Prefer .get() for pure read-only contract calls — it needs no signer.
- Keep a helper that returns an unlocked wallet for simulation contexts.
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.