FuelLabs/fuels-ts · error · FuelError
TRANSACTION_ERROR
TRANSACTION_ERROR
Error message
The target function ${this.func.name} cannot accept forwarded funds as it's not marked as 'payable'. What it means
Thrown by FunctionInvocationScope.callParams() when a `forward` amount is supplied but the target Sway function's ABI attributes do not include 'payable'. Forwarding native coins to a contract call requires the Sway function to be annotated #[payable]; without it the VM would reject the funds, so the SDK blocks it up front.
Source
Thrown at packages/program/src/functions/invocation-scope.ts:84
return this;
}
/**
* Sets the call parameters for the function invocation.
*
* @param callParams - The call parameters.
* @returns The instance of FunctionInvocationScope.
* @throws If the function is not payable and forward is set.
*/
callParams(callParams: CallParams) {
if (!this.hasCallParamsGasLimit && callParams?.gasLimit !== undefined) {
this.hasCallParamsGasLimit = true;
}
this.callParameters = callParams;
if (callParams?.forward) {
if (!this.func.attributes.find((attr) => attr.name === 'payable')) {
throw new FuelError(
ErrorCode.TRANSACTION_ERROR,
`The target function ${this.func.name} cannot accept forwarded funds as it's not marked as 'payable'.`
);
}
this.forward = coinQuantityfy(callParams.forward);
}
// Update transaction script with new forward params
this.setArguments(...this.args);
// Update required coins
this.updateRequiredCoins();
return this;
}
}
View on GitHub (pinned to b3f37c91ac)
Solutions
- Mark the Sway function #[payable] (storage(read, write) may also be needed) and redeploy + regenerate types.
- If you did not intend to send coins, remove the `forward` entry from callParams.
- Re-run typegen against the current contract ABI to ensure the payable attribute is present in the JSON ABI used by the SDK.
Example fix
// Sway
// before
entry fn deposit(amount: u64) { ... }
// after
#[payable]
entry fn deposit(amount: u64) { ... } Defensive patterns
Strategy: validation
Validate before calling
function isPayable(func: { attributes: Array<{ name: string }> }): boolean {
return !!func.attributes?.some((a) => a.name === 'payable');
}
if (callParams?.forward && !isPayable(scope.func)) {
throw new Error(`${scope.func.name} is not payable; remove forward or mark it #[payable] in Sway.`);
} Type guard
function isPayableFunction(func: { attributes: Array<{ name: string }> }): boolean {
return !!func.attributes?.some((a) => a.name === 'payable');
} Try / catch
try {
await contract.functions.deposit().callParams({ forward: [amt, assetId] }).call();
} catch (e) {
if (e instanceof FuelError && e.code === ErrorCode.TRANSACTION_ERROR && /payable/.test(e.message)) {
// mark the Sway function #[payable], redeploy, regenerate types
} else throw e;
} Prevention
- Annotate receiving Sway functions with #[payable] before exposing a deposit path.
- Regenerate types after changing payable attributes so the SDK ABI reflects them.
- Only pass forward for functions you know are payable.
When it happens
Trigger: Calling .callParams({ forward: [amount, assetId] }) on a function whose ABI has no payable attribute; forwarding to a read/View or non-payable function; ABI regenerated from a contract where #[payable] was removed.
Common situations: Depositing or paying into a function that the developer forgot to mark #[payable] in Sway; using the wrong ABI (stale typegen) where payable was stripped; sending base asset to a pure function.
Related errors
- INVALID_CONFIGURABLE_CONSTANTS
- ABI_MAIN_METHOD_MISSING
- CONFIGURABLE_NOT_FOUND
- ABI_TYPES_AND_VALUES_MISMATCH
- ENCODE_ERROR
AI-assisted analysis of FuelLabs/fuels-ts@b3f37c91ac (2026-08-12).
Data as JSON: /api/errors/e76a82a52fa6340c.
Report an issue: GitHub.