> For the complete documentation index, see [llms.txt](https://hinkal-team.gitbook.io/hinkal/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hinkal-team.gitbook.io/hinkal/technical-description/evm-tron-smart-contracts/externalactionswap.md).

# 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

```solidity
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.

```solidity
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**

```solidity
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**

```solidity
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**

```solidity
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.
