Read deposit into 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.- your function pulls an ERC-20 instead of taking native value: ERC-20 destination calls
- the amount is unknown at build time: dynamic landed-balance mode
- funds need somewhere to land if the call fails: fallback behavior
- source funds arrive by plain payment: deposit-address routes
- the destination is not a vault: 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 includeroute.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.
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 withtransferFrom 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.
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.
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.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.fullAmount: true and tell the provider which ABI argument holds the amount.
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.
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.
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.
buildDepositAddress() takes the same request body as buildRoute() and returns a deposit address instead of a signable transaction.
Call other destination contracts
Nothing abouthooks.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 usestoApprovalAddressand novalue. - 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.
fullAmount and amountInputPos are provider dependent, as covered in 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.
Related reference
Deposit into vault destinations
The six-step walkthrough, the
hooks.postHook field reference, and the complete example.Headless core
The full
Trustware namespace API, including buildRoute, sendRouteTransaction, and pollStatus.POST /route
The REST request and response schema, including
hooks.postHook and route.execution.approvals.Error handling
Posthook validation errors, approval failures, and the imperative try/catch pattern.
