Skip to main content

Trigger Contracts

Vela supports an advanced pattern where an external smart contract (a "trigger") can automatically enqueue a follow-up request as a side effect of a normal request completing successfully. This enables use cases like anonymous execution pools, where processing a user's request causes an on-chain action to be taken from a shared pool address rather than from the user's own address.

Deploying with a Trigger​

Use submitDeployRequestWithTrigger(protocolVersion, payload, triggerAddress) instead of submitDeployRequest. The trigger contract must be deployed separately before the application deployment, and must not already be registered to another app. The trigger must extend AbstractTrigger; implementing ITrigger directly is not supported. AbstractTrigger restricts calls to ProcessorEndpoint (via the _onlyProcessorEndpoint guard) and provides the non-overridable withdraw() sweep described below.

What Happens During stateUpdate​

After a request for a trigger-wired application completes successfully, ProcessorEndpoint runs four trigger steps. If the request fails (result FAILED), the trigger is not invoked. execute, withdraw and getTrustProcessPayload each run in their own try/catch, so a revert in any of them does not block the state update. Step 1 is different: the endpoint pushes claimable withdrawals to the trigger with a direct transfer, not a callback. _invokeTrigger calls _claim for every claimable token address, so both ETH and allowlisted ERC-20 withdrawals addressed to the trigger are pushed here. If the trigger cannot receive ETH, stateUpdate reverts with TransferFailed; a failing ERC-20 safeTransfer also reverts stateUpdate (with a SafeERC20 error, not TransferFailed). The trigger must therefore be able to receive both ETH and the allowlisted ERC-20s.

StepActionWhat it does
1Direct transfer (ETH and ERC-20, no callback)Withdrawals whose recipient is the trigger contract address are pushed into the trigger with a direct transfer — ETH via a value transfer and allowlisted ERC-20s via safeTransfer. Funds move from the endpoint's custody into the trigger contract.
2execute(appEventData)The plaintext AppEvent array from the WASM execution is passed to the trigger. The trigger contract uses this data to perform an on-chain action. Emits TriggerExecuted.
3withdraw()Sweeps all ETH and token balances held by the trigger back into the endpoint's custody. AbstractTrigger provides this as a non-overridable sweep; since triggers must extend AbstractTrigger, implementations do not provide withdraw() themselves. Emits TriggerWithdraw.
4getTrustProcessPayload(...)If this returns non-empty bytes, a TRUSTPROCESS request (type 4) is enqueued in a separate high-priority queue. An empty return means no follow-up is enqueued.

The TRUSTPROCESS Request​

  • Processed before any normal request in the queue. The trigger queue has higher priority than the standard request queue.
  • Routed to the WASM application's trusted_request export.
  • The payload is plaintext. It originates from the on-chain trigger contract, not from a user, so ECDH decryption is not applied.
  • No application fee is charged and the minimum-fee check is skipped.

Avoiding Infinite Chains​

A TRUSTPROCESS can itself invoke the trigger again (chaining). Every trigger implementation must include a termination condition. The standard pattern is for getTrustProcessPayload to return empty bytes when appEventData.events.length == 0, so a trusted request that emits no AppEvents does not enqueue another.

For a fully worked example, see docs/4_trigger-contract-app.md in the vela-starterkit repository.