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
onlyAllowedRecipient
so only the registered Hinkal contract can call.
function swap(CircomData circomData, int256[] deltaAmounts)
internal returns (UTXO[] utxoSet)
Flow:
Read input token/amount from
circomData
anddeltaAmounts[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
, subtractrelayFee
from the received amount.Transfer output amount to
msg.sender
(Hinkal), and create a singleUTXO
with 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)
externalActionMetadata
encodesuint24 fee
(pool fee tier).If
outputToken == address(0)
, swap towrapper
and unwrap to ETH at the end.If
inputToken == address(0)
, deposit ETH towrapper
first.Approves router unlimited allowance for
inputToken
.Calls
ISwapRouter.exactInputSingle
withamountOutMinimum=0
(slippage is enforced at Hinkal-level viaslippageValues
).Returns
swappedAmount
from 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
outputToken
balance before call.If input is ETH, forwards
inputAmount
asmsg.value
torouter.call(externalActionMetadata)
; else approves and calls without value.Requires success with distinct revert messages for native vs ERC20.
Computes
swappedAmount
as 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
wrapper
ifoutputToken
orinputToken
is 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
feeToken
is 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
transferERC20TokenOrETH
so ETH and ERC20 paths are unified.
Security Considerations
onlyAllowedRecipient
prevents arbitrary callers from abusing approvals/funds.No internal slippage check: circuit/Hinkal-level
slippageValues
enforce 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