ExternalActionSwap
Overview
ExternalActionSwap defines a common swap action interface used by Hinkal external actions. It receives user intent from HinkalInLogic.handleRunExternalAction, executes a swap on a target router (Uniswap, 1inch, Odos), pays the relay, returns tokens back to Hinkal, and reports resulting UTXOs.
Concrete adapters:
UniswapExternalAction— Uniswap V3 exactInputSingleOneInchExternalAction— 1inch Router arbitrary calldataOdosExternalAction— Odos Router encoded calldata
Inheritance
Transferer— ERC20/ETH helpers (approve, transfers, balances)ExternalActionBaseV2— recipient allowlist and ownership controls
Key Storage
IHinkalHelper public hinkalHelper— to calculate relay feeIWrapper internal immutable wrapper— WETH/Native wrapperaddress public immutable router— target DEX/aggregator router
Entry Point from Hinkal
function runAction(CircomData circomData, int256[] deltaAmounts)
external onlyAllowedRecipient returns (UTXO[] utxoSet)Delegates to
swap(circomData, deltaAmounts).Protected by
onlyAllowedRecipientso only the registered Hinkal contract can call.
function swap(CircomData circomData, int256[] deltaAmounts)
internal returns (UTXO[] utxoSet)Flow:
Read input token/amount from
circomDataanddeltaAmounts[0].If
feeToken == inputToken, subtract flat fee from the input amount.Execute DEX swap via
callRouter.Compute
relayFee = hinkalHelper.calculateRelayFee(0, flatFee, variableRate)and pay it infeeToken.If
feeToken == outputToken, subtractrelayFeefrom the received amount.Transfer output amount to
msg.sender(Hinkal), and create a singleUTXOwith output token, amount, and stealth info.
Note: ETH is supported via the wrapper (WETH) for routers that require ERC20s.
Adapters
UniswapExternalAction
function callRouter(address inputToken, uint256 inputAmount, address outputToken, bytes externalActionMetadata)
internal override returns (uint256 swappedAmount)externalActionMetadataencodesuint24 fee(pool fee tier).If
outputToken == address(0), swap towrapperand unwrap to ETH at the end.If
inputToken == address(0), deposit ETH towrapperfirst.Approves router unlimited allowance for
inputToken.Calls
ISwapRouter.exactInputSinglewithamountOutMinimum=0(slippage is enforced at Hinkal-level viaslippageValues).Returns
swappedAmountfrom router; unwraps to ETH if necessary.
Usage scenario (Uniswap V3):
Input: TOKEN A, Output: TOKEN B,
fee = 3000(0.3%).Metadata:
abi.encode(uint24(3000)).
OneInchExternalAction
function callRouter(address inputToken, uint256 inputAmount, address outputToken, bytes externalActionMetadata)
internal override returns (uint256 swappedAmount)Measures
outputTokenbalance before call.If input is ETH, forwards
inputAmountasmsg.valuetorouter.call(externalActionMetadata); else approves and calls without value.Requires success with distinct revert messages for native vs ERC20.
Computes
swappedAmountas the post-call balance delta foroutputToken.
Metadata: raw calldata produced off-chain by 1inch API for the specific chain and tokens.
OdosExternalAction
function callRouter(address inputToken, uint256 inputAmount, address outputToken, bytes externalActionMetadata)
internal override returns (uint256 swappedAmount)Wraps/unwraps ETH via
wrapperifoutputTokenorinputTokenis native.Approves router for
inputToken.Calls
router.call(externalActionMetadata); expects returned bytes encodinguint256 swappedAmount.Reverts on failure.
Metadata: raw calldata produced off-chain by Odos API; must return the output amount.
Fees and Accounting
Input-side: If the
feeTokenis the input token, the flat fee is removed from input before the swap.Output-side: Relay fee is computed and, if
feeToken == outputToken, deducted from the swap result before returning to Hinkal.Hinkal later enforces slippage and balance equations; this adapter focuses on execution and fee pass-through.
ETH Handling
Uses
wrapper.deposit{value: inputAmount}()when input is native.Uses
wrapper.withdraw(swappedAmount)when output is native.Transfers via
transferERC20TokenOrETHso ETH and ERC20 paths are unified.
Security Considerations
onlyAllowedRecipientprevents arbitrary callers from abusing approvals/funds.No internal slippage check: circuit/Hinkal-level
slippageValuesenforce minimum outputs.Unlimited approval is given per-call to routers; this contract is trust-minimized by recipient gating and Hinkal balance checks.
Explicit revert messages for external call failures improve debuggability.
Example Integrations
Uniswap V3 single-hop swap using 0.3% pool.
1inch multi-hop route with permit2 where metadata includes router calldata.
Odos split-route aggregation returning exact amount out.
In all cases, Hinkal’s HinkalInLogic.handleRunExternalAction handles pre-swap token movements and invokes runAction, then Hinkal inserts commitments from the returned UTXOs.
Last updated