{"record":{"id":"b307b034a650755d","repo":"FuelLabs/fuels-ts","slug":"abi-main-method-missing-b307b0","errorCode":"ABI_MAIN_METHOD_MISSING","errorMessage":"An unlocked wallet is required to simulate a contract call.","messagePattern":"An unlocked wallet is required to simulate a contract call\\.","errorType":"exception","errorClass":"FuelError","httpStatus":null,"severity":"error","filePath":"packages/program/src/functions/base-invocation-scope.ts","lineNumber":560,"sourceCode":"        buildPreConfirmationFunctionResult<T>({\n          funcScope: this.functionInvocationScopes,\n          isMultiCall: this.isMultiCall,\n          program: this.program,\n          transactionResponse: response,\n        }),\n    };\n  }\n\n  /**\n   * Simulates a transaction.\n   *\n   * @returns The result of the invocation call.\n   */\n  async simulate<T = TReturn>(): Promise<DryRunResult<T>> {\n    assert(this.program.account, 'Wallet is required!');\n\n    if (!('populateTransactionWitnessesSignature' in this.program.account)) {\n      throw new FuelError(\n        ErrorCode.ABI_MAIN_METHOD_MISSING,\n        'An unlocked wallet is required to simulate a contract call.'\n      );\n    }\n    const transactionRequest = await this.fundWithRequiredCoins();\n\n    const callResult = await this.program.account.simulateTransaction(transactionRequest, {\n      estimateTxDependencies: false,\n    });\n\n    return buildDryRunResult<T>({\n      funcScopes: this.functionInvocationScopes,\n      callResult,\n      isMultiCall: this.isMultiCall,\n    });\n  }\n\n  /**","sourceCodeStart":542,"sourceCodeEnd":578,"githubUrl":"https://github.com/FuelLabs/fuels-ts/blob/b3f37c91aca4aa9d5e4c0d3967f66237190826ea/packages/program/src/functions/base-invocation-scope.ts#L542-L578","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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()."],"exampleFix":"// before\nconst locked = Wallet.fromAddress(addr);\nconst contract = new Contract(id, abi, locked);\nawait contract.functions.balance().simulate(); // throws\n\n// after\nconst unlocked = Wallet.fromPrivateKey(pk);\nconst contract = new Contract(id, abi, unlocked);\nawait contract.functions.balance().simulate();","handlingStrategy":"validation","validationCode":"// Before calling .simulate(), confirm the account can sign a witness.\nfunction canSimulate(account: unknown): boolean {\n  return !!account && typeof (account as any).populateTransactionWitnessesSignature === 'function';\n}\n\nif (!canSimulate(contract.account)) {\n  throw new Error('Pass a WalletUnlocked to the Contract to use simulate(); use .get() for read-only calls.');\n}\nawait contract.functions.fn().simulate();","typeGuard":"import type { BaseWalletUnlocked } from '@fuel-ts/account';\n\nfunction isUnlockedWallet(a: unknown): a is BaseWalletUnlocked {\n  return !!a && typeof (a as BaseWalletUnlocked).populateTransactionWitnessesSignature === 'function';\n}","tryCatchPattern":"try {\n  await contract.functions.fn().simulate();\n} catch (e) {\n  if (e instanceof FuelError && e.code === ErrorCode.ABI_MAIN_METHOD_MISSING) {\n    // swap in an unlocked wallet, or fall back to .get()\n  } else throw e;\n}","preventionTips":["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."],"tags":["wallet","simulation","signing","typescript"],"backgroundTag":null,"analyzedSha":"b3f37c91aca4aa9d5e4c0d3967f66237190826ea","analyzedAt":"2026-08-12T20:30:56.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}