# Authorization

Several SwapCore functions take an `onBehalfOf` parameter — the address that owns the resulting state change — which is separate from `msg.sender`, the address that pays for the call. This is the integration hook for **bundlers, routers, vault adapters, and wrapper contracts**: the end user signs and funds the transaction (perhaps through a bundler), while a contract address takes the action on their behalf.

For every `onBehalfOf` entry point except `buySwap`, `msg.sender` must be pre-authorized by `onBehalfOf`. The check is a single mapping lookup (`isAuthorized`) plus a self-path fallback.

Source: `SwapCore.sol`

## The `onBehalfOf` pattern

Internally, SwapCore runs every guarded call through this helper:

```solidity
function _isSenderAuthorized(address onBehalfOf) internal view returns (bool) {
    return msg.sender == onBehalfOf || isAuthorized[onBehalfOf][msg.sender];
}
```

So the rule is simple: **you can always act on your own behalf, and you can delegate that right to any other address by calling `setAuthorization(delegate, true)`**. Revoke with `setAuthorization(delegate, false)`.

A failure on this check reverts with `E206`.

### Functions that require authorization

| Function                                                          | Who the caller can be                                         |
| ----------------------------------------------------------------- | ------------------------------------------------------------- |
| [`supplyCollateral`](/protocol/liquidity.md#supplycollateral)     | `onBehalfOf`, or a delegate they've authorized                |
| [`withdrawCollateral`](/protocol/liquidity.md#withdrawcollateral) | `onBehalfOf`, or a delegate they've authorized                |
| [`exitSwapEarly`](/protocol/swaps.md#exitswapearly)               | `onBehalfOf`, or a delegate they've authorized                |
| [`transferSwapPosition`](/protocol/swaps.md#transferswapposition) | Current swap owner, or a delegate they've authorized          |
| [`claimEscrow`](/protocol/swaps.md#claimescrow)                   | Original `swap.userAddress`, or a delegate they've authorized |

### Functions that do NOT require authorization

* [`buySwap`](/protocol/swaps.md#buyswap) — opening a new position is beneficial to `onBehalfOf`, and the caller is the one paying collateral and fees. Allowing anyone to open a swap for anyone else is safe and useful (bundlers, pre-funded relayers).
* [`makePayment`](/protocol/swaps.md#makepayment), [`liquidateSwap`](/protocol/swaps.md#liquidateswap), [`updateMarketRateIndex`](/protocol/views.md#updatemarketrateindex) — all permissionless, no per-user auth.
* Market admin functions (`createMarket`, `transferMarketOwnership`, etc.) — gated on `marketOwner` (the `marketOwner` modifier), which is a separate mechanism.

### Why `supplyCollateral` requires auth even though it "helps" `onBehalfOf`

A dust deposit from an attacker would stamp `lpPositions[marketId][victim].lastDepositBlock = block.number`, which makes the victim's own `withdrawCollateral` revert with `E413` in the same block. Without the auth check, an attacker could repeatedly front-run a victim's withdrawal and DoS it one block at a time. The auth gate closes that off at zero cost.

### What the `onBehalfOf` → `msg.sender` split enables

* **Bundlers with Permit2.** The bundler holds approvals from the end user, pulls tokens from its own balance (funded via permit), and calls `supplyCollateral(marketId, amount, endUser, minShares)`. Shares go to the end user; the bundler simply forwarded the call.
* **Wrapper NFTs.** `SwapPositionWrapper` holds actual swap ownership on-chain while issuing an ERC-721 to the end user. On transfer, the wrapper calls `transferSwapPosition(swapId, newOwner, wrapperAddress)` — it's authorized because it owns the underlying swap.
* **Vault adapters.** Morpho vault adapters use the same trick: the adapter is the on-chain LP, the vault's users get accounting via the vault's own share token.

***

## setAuthorization

```solidity
function setAuthorization(address authorized, bool status) external;
```

**Who calls:** anyone — the caller is authorizing (or revoking) `authorized` to act on **their own** behalf. There is no admin path; every account manages its own authorization list.

**What it does:** sets `isAuthorized[msg.sender][authorized] = status`. That's it — a single storage write, no external calls, no checks other than ensuring the value actually changes (it doesn't even check that; writes are idempotent).

**Parameters:**

| Parameter    | Meaning                                                    |
| ------------ | ---------------------------------------------------------- |
| `authorized` | The address you're granting or revoking authorization for. |
| `status`     | `true` to grant, `false` to revoke.                        |

**State changes / events:**

* `isAuthorized[msg.sender][authorized] = status`.
* Emits `AuthorizationSet(msg.sender, authorized, status)`.

**Reverts:** none directly. The function does not validate `authorized != address(0)` — authorizing the zero address is a no-op since `msg.sender == address(0)` is unreachable, so it simply produces a dead entry.

**Example — authorizing a bundler once, then letting it act repeatedly:**

```solidity
// Done once from the end-user's EOA:
swapCore.setAuthorization(bundler, true);

// From then on, the bundler can do things like:
swapCore.supplyCollateral(marketId, amount, endUser, minShares);
swapCore.withdrawCollateral(marketId, amount, false, endUser, minOut);
swapCore.exitSwapEarly(requests, endUser);
```

When the bundler is retired or compromised, revoke:

```solidity
swapCore.setAuthorization(bundler, false);
```

There is no multi-authorization atomic helper; if you want to authorize multiple addresses in one transaction, call `setAuthorization` for each via a multicall wrapper on your side.

**See also:** [`isAuthorized`](#isauthorized).

***

## isAuthorized

```solidity
mapping(address => mapping(address => bool)) public isAuthorized;
```

Solidity auto-generates a getter:

```solidity
function isAuthorized(address owner, address operator) external view returns (bool);
```

**Who calls:** anyone — UIs, indexers, other contracts that want to know whether they're currently permitted to call guarded functions on behalf of `owner`.

**What it returns:** `true` if `owner` has previously called `setAuthorization(operator, true)` and has not since revoked. **Returning `true` from this function is not the same as `_isSenderAuthorized` returning true** — the internal helper also accepts `owner == operator` as an implicit self-authorization, but the public mapping does not set that bit. A contract checking the mapping directly should reproduce the `operator == owner || isAuthorized[owner][operator]` logic if it wants to mirror SwapCore's rule.

**State:** `isAuthorized` is per-address, not per-market and not per-function. Once you authorize an operator, they can act for you on any market and any guarded function.

**See also:** [`setAuthorization`](#setauthorization).


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.kairosswap.com/protocol/authorization.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
