Post-Intent Hooks
Overview
OrchestratorV3 was retired on 2026-11-05. This page preserves its historical hook interfaces and examples; do not use them to signal or fulfill new intents. OrchestratorV4, which serves new intents, has no hooks: it transfers funds to to without a callback, or holds the payout under Stake to Take. Run follow-up actions such as bridging or splitting after settlement.
A post-intent hook runs when an OrchestratorV3 intent is fulfilled, before funds reach the recipient, for example to split a payout or forward funds to another contract. The taker sets the hook when signaling (SignalIntentParams.postIntentHook). Without a hook, funds go to intent.to.
Interface
interface IPostIntentHookV2 {
struct HookIntentContext {
address owner;
address to;
address escrow;
uint256 depositId;
uint256 amount;
uint256 timestamp;
bytes32 paymentMethod;
bytes32 fiatCurrency;
uint256 conversionRate;
bytes32 payeeId;
bytes signalHookData; // SignalIntentParams.data
}
struct HookExecutionContext {
bytes32 intentHash;
address token; // deposit token
uint256 executableAmount; // amount after all fees
HookIntentContext intent;
}
function execute(
HookExecutionContext calldata _ctx,
bytes calldata _fulfillHookData // FulfillIntentParams.postIntentHookData
) external;
}
Source: IPostIntentHookV2.sol
Execution model
On a successful fulfillIntent, OrchestratorV3:
- Pays protocol, manager and referral fees from the release amount, leaving
executableAmount. - Approves the hook for exactly
executableAmountof the deposit token. - Calls
hook.execute(ctx, postIntentHookData). - Checks that the hook pulled exactly
executableAmountand that the orchestrator's token balance did not increase, then resets the allowance to zero.
If a check fails or the hook reverts, the whole fulfillment reverts.
Hook data
SignalIntentParams.datais stored with the intent and passed assignalHookData. Use it for fixed configuration such as split recipients.FulfillIntentParams.postIntentHookDatais passed at fulfillment. Use it for inputs known only then.
Example: split payout
contract SplitHookV2 is IPostIntentHookV2 {
address public immutable orchestrator;
address public immutable builder;
constructor(address _orchestrator, address _builder) {
orchestrator = _orchestrator;
builder = _builder;
}
function execute(HookExecutionContext calldata ctx, bytes calldata) external override {
require(msg.sender == orchestrator, "only orchestrator");
uint256 fee = ctx.executableAmount * 5 / 100;
IERC20(ctx.token).transferFrom(msg.sender, ctx.intent.to, ctx.executableAmount - fee);
IERC20(ctx.token).transferFrom(msg.sender, builder, fee);
}
}
Pulling less than executableAmount, or sending tokens back to the orchestrator, reverts the fulfillment.
Guidelines
- Restrict
executeto the orchestrator and pull exactlyexecutableAmount. Use OpenZeppelinSafeERC20for tokens that returnfalse. - Keep execution short and deterministic; a reverting hook blocks fulfillment.
- Do not rely on re-entering the orchestrator;
fulfillIntentis non-reentrant. - The legacy V1
IPostIntentHookandPostIntentHookRegistryapply only to the deprecated V1 orchestrator.