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 exactInputSingle

  • OneInchExternalAction — 1inch Router arbitrary calldata

  • OdosExternalAction — 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 fee

  • IWrapper internal immutable wrapper — WETH/Native wrapper

  • address 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:

  1. Read input token/amount from circomData and deltaAmounts[0].

  2. If feeToken == inputToken, subtract flat fee from the input amount.

  3. Execute DEX swap via callRouter.

  4. Compute relayFee = hinkalHelper.calculateRelayFee(0, flatFee, variableRate) and pay it in feeToken.

  5. If feeToken == outputToken, subtract relayFee from the received amount.

  6. Transfer output amount to msg.sender (Hinkal), and create a single UTXO 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 encodes uint24 fee (pool fee tier).

  • If outputToken == address(0), swap to wrapper and unwrap to ETH at the end.

  • If inputToken == address(0), deposit ETH to wrapper first.

  • Approves router unlimited allowance for inputToken.

  • Calls ISwapRouter.exactInputSingle with amountOutMinimum=0 (slippage is enforced at Hinkal-level via slippageValues).

  • 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 as msg.value to router.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 for outputToken.

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 if outputToken or inputToken is native.

  • Approves router for inputToken.

  • Calls router.call(externalActionMetadata); expects returned bytes encoding uint256 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