> ## Documentation Index
> Fetch the complete documentation index at: https://docs.trustware.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Destination call options

> ERC-20 destination calls, dynamic landed amounts, fallback addresses, deposit-address routes, and posthook error handling.

The [vault destinations walkthrough](/guides/vault-destinations) covers the primary recipe: a predetermined destination-token amount funding a native-value call. This page covers what varies from it.

<Note>
  Read [deposit into vault destinations](/guides/vault-destinations) first. Every
  example here assumes that six-step flow and changes only the posthook. The full
  `hooks.postHook` field reference is there too.
</Note>

Jump to your case:

* your function pulls an ERC-20 instead of taking native value: [ERC-20 destination calls](#erc-20-destination-calls)
* the amount is unknown at build time: [dynamic landed-balance mode](#dynamic-landed-balance-mode)
* funds need somewhere to land if the call fails: [fallback behavior](#fallback-behavior)
* source funds arrive by plain payment: [deposit-address routes](#deposit-address-routes)
* the destination is not a vault: [call other destination contracts](#call-other-destination-contracts)

## Source token approvals

Source token approvals and destination call funding are two different mechanisms. Do not conflate them.

**Source token approval** is a wallet action on the source chain. For an ERC-20 source asset, the route response can include `route.execution.approvals[]`, each entry carrying the `tokenAddress`, `spender`, and `amount` to approve before the route transaction can succeed. The spender is a provider contract, never a Trustware one.

`sendRouteTransaction` handles these on EVM routes: it reads the current allowance, approves the exact amount when one is missing, waits for confirmation, then requests the route signature. It never grants an unlimited allowance. If the allowance read fails, it approves anyway rather than sending a transaction it knows would revert.

Two consequences for your UI:

* The user can see more than one wallet prompt. Tell them an approval may come first, then the deposit itself.
* Each approval must confirm on chain before the route signature is requested, so the call can stay pending for a minute or more.

If you sign yourself, through the REST API or a custom signer, implement the same sequence: read `route.execution.approvals[]`, grant each allowance, wait for confirmation, then send `route.execution.transaction`. Every field is optional, so skip any entry missing a `spender`, `tokenAddress`, or non-zero `amount`.

**Destination call funding** is `toApprovalAddress`, covered next. It is not a wallet prompt.

## ERC-20 destination calls

If your destination function pulls tokens with `transferFrom` instead of taking native value, set `toApprovalAddress` to the address allowed to pull `fundToken`. That is normally the contract performing the `transferFrom`, usually the vault itself.

```ts theme={null}
const erc20VaultAbi = parseAbi([
  "function depositFor(address recipient, address token, uint256 amount)",
]);

const erc20FundAmount = "5000000"; // 5 USDC, destination base units
const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";

const callData = encodeFunctionData({
  abi: erc20VaultAbi,
  functionName: "depositFor",
  args: ["0xYourRecipientAddress", USDC_BASE, BigInt(erc20FundAmount)],
});

const route = await Trustware.buildRoute({
  fromChain: "42161",
  toChain: "8453",
  fromToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
  toToken: USDC_BASE,
  fromAmount: "3000000000000000",
  fromAddress: await Trustware.getAddress(),
  toAddress: "0xYourRecipientAddress",
  hooks: {
    postHook: {
      target: "0xYourVaultAddress",
      callData,
      fundToken: USDC_BASE,
      fundAmount: erc20FundAmount,
      toApprovalAddress: "0xYourVaultAddress",
      estimatedGas: "200000",
    },
  },
});
```

`depositFor` is illustrative. Substitute your own pull-based deposit function and ABI.

Note there is no `value` field. The call is funded with an ERC-20, so nothing native is attached.

How the allowance is granted depends on the provider that resolves the route. Some prepend an ERC-20 `approve()` ahead of your call, patching the approval amount alongside your call's amount in dynamic landed-balance mode. Others hand it to their own execution engine. You set the same field either way.

<Warning>
  Do not set `toApprovalAddress` for native-value destination calls. Native
  assets need no allowance, and the API rejects a request that pairs
  `toApprovalAddress` with a native `fundToken`.
</Warning>

## Dynamic landed-balance mode

The exact amount arriving on the destination chain is not knowable at build time, since it depends on execution-time pricing and slippage. Dynamic landed-balance mode lets the provider patch the real landed amount into your calldata, so the call acts on exactly what arrived.

<Note>
  This mode is **provider dependent**. A request using it is only routed to a
  provider that implements amount patching, so enabling it narrows provider
  selection. For the widest eligibility, use a predetermined `fundAmount`.
</Note>

Set `fullAmount: true` and tell the provider which ABI argument holds the amount.

```ts theme={null}
const erc20VaultAbi = parseAbi([
  "function depositFor(address recipient, address token, uint256 amount)",
]);

const USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";

const callData = encodeFunctionData({
  abi: erc20VaultAbi,
  functionName: "depositFor",
  // The amount encoded here is a placeholder. The provider replaces it.
  args: ["0xYourRecipientAddress", USDC_BASE, 0n],
});

const route = await Trustware.buildRoute({
  fromChain: "42161",
  toChain: "8453",
  fromToken: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
  toToken: USDC_BASE,
  fromAmount: "3000000000000000",
  fromAddress: await Trustware.getAddress(),
  toAddress: "0xYourRecipientAddress",
  hooks: {
    postHook: {
      target: "0xYourVaultAddress",
      callData,
      fundToken: USDC_BASE,
      fullAmount: true,
      amountInputPos: 2,
      toApprovalAddress: "0xYourVaultAddress",
      estimatedGas: "200000",
    },
  },
});
```

`amountInputPos` is the zero-based index of the ABI argument the provider overwrites. In `depositFor(address recipient, address token, uint256 amount)` the amount is the third argument, so it is `2`. Index `0` is valid, so do not treat a falsy index as unset.

Do not send `fundAmount` in this mode. `buildRoute` rejects the request before it is sent if `fullAmount` is `true` and `amountInputPos` is missing.

## Fallback behavior

`toFallbackAddress` names an address that receives the funds if the destination call fails.

<Warning>
  `toFallbackAddress` is **provider dependent**. It is forwarded to the provider
  that resolves the route and implemented there. Not every provider offers an
  equivalent, so never treat fallback as a guarantee or build a recovery flow that
  assumes it applies.
</Warning>

```ts theme={null}
import { encodeFunctionData, parseAbi } from "viem";

const vaultAbi = parseAbi([
  "function depositNativeFor(address recipient) payable",
]);

const fundAmount = "2000000000000000"; // 0.002 ETH in wei

const callData = encodeFunctionData({
  abi: vaultAbi,
  functionName: "depositNativeFor",
  args: ["0xYourRecipientAddress"],
});

const hooks = {
  postHook: {
    target: "0xYourVaultAddress",
    callData,
    value: fundAmount,
    fundAmount,
    estimatedGas: "150000",
    toFallbackAddress: "0xYourTreasuryAddress",
  },
};
```

There is no destination-call status field, so the route status alone will not tell you whether a fallback ran. Read your destination contract, and the fallback address balance to distinguish the two outcomes.

## Deposit-address routes

`Trustware.buildDepositAddress()` accepts the same `hooks.postHook` shape as `buildRoute`. Use it when the source funds arrive by plain payment to an address rather than from a wallet you can prompt.

<Note>
  Deposit-address routing is **provider dependent**, and so are deposit-address
  routes with a posthook. This path needs a provider that supports both, so it
  is not available on every route.
</Note>

Client-side posthook validation is identical on both calls. `buildDepositAddress()` takes the same request body as `buildRoute()` and returns a deposit address instead of a signable transaction.

## Call other destination contracts

Nothing about `hooks.postHook` is vault specific. It executes an arbitrary encoded call, so the same pattern covers staking contracts, margin accounts, lending pools, and anything else you control. Only the ABI and the function you encode change.

Two questions decide which sections apply to you:

* **Does your function accept native value or pull an ERC-20?** Native value uses `value`. An ERC-20 pull uses `toApprovalAddress` and no `value`.
* **Do you know the amount in advance?** If yes, use a predetermined `fundAmount`. If not, use dynamic landed-balance mode and accept that it narrows provider selection.

## Errors and failure handling

`buildRoute` and `buildDepositAddress` reject a structurally incomplete posthook before sending. These four are thrown as a plain `Error` with no `code`, so `instanceof TrustwareError` and `error.code` branching do not apply.

| Cause                                     | Message                                                              |
| ----------------------------------------- | -------------------------------------------------------------------- |
| `target` missing or blank                 | `hooks.postHook.target is required.`                                 |
| `callData` missing or blank               | `hooks.postHook.callData is required.`                               |
| `fullAmount` set without `amountInputPos` | `hooks.postHook.amountInputPos is required when fullAmount is true.` |
| Neither `fundAmount` nor `fullAmount` set | `hooks.postHook.fundAmount is required unless fullAmount is set.`    |

`fullAmount` and `amountInputPos` are provider dependent, as covered in [dynamic landed-balance mode](#dynamic-landed-balance-mode).

These checks only confirm completeness. The API is authoritative for what the client cannot check: calldata correctness, destination compatibility, and provider eligibility. A rejected posthook returns `400` with a message describing the problem.

Two more plain `Error` cases come from the approval step inside `sendRouteTransaction`: `Approval transaction reverted` and `Timed out waiting for approval confirmation`. Treat both as recoverable and let the user retry.

```ts theme={null}
try {
  const route = await Trustware.buildRoute({ /* ... */ });
  const txHash = await Trustware.sendRouteTransaction(route, 42161);
  await Trustware.submitReceipt(route.intentId, txHash);

  const finalStatus = await Trustware.pollStatus(route.intentId);
  if (finalStatus.status !== "success") {
    showRouteIncomplete(route.intentId, finalStatus.status);
    return;
  }

  await verifyVaultCredit(route.intentId, finalStatus.destTxHash);
} catch (error) {
  reportDepositError(error);
}
```

Everything else follows the normal [error handling](/events-errors/error-handling) patterns.

## Related reference

<CardGroup cols={2}>
  <Card title="Deposit into vault destinations" icon="vault" href="/guides/vault-destinations">
    The six-step walkthrough, the `hooks.postHook` field reference, and the complete example.
  </Card>

  <Card title="Headless core" icon="terminal" href="/integration/headless-core">
    The full `Trustware` namespace API, including `buildRoute`, `sendRouteTransaction`, and `pollStatus`.
  </Card>

  <Card title="POST /route" icon="code" href="/api-reference/route">
    The REST request and response schema, including `hooks.postHook` and `route.execution.approvals`.
  </Card>

  <Card title="Error handling" icon="triangle-exclamation" href="/events-errors/error-handling">
    Posthook validation errors, approval failures, and the imperative try/catch pattern.
  </Card>
</CardGroup>
